diff --git a/Cargo.lock b/Cargo.lock index 5b6fffaa49..8e6b7357d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "byteorder" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" + [[package]] name = "cache-padded" version = "1.1.1" @@ -301,6 +307,15 @@ dependencies = [ "vec_map", ] +[[package]] +name = "cmake" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.24.0" @@ -431,6 +446,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if 1.0.0", + "dirs-sys-next", +] + [[package]] name = "dirs-sys" version = "0.3.5" @@ -438,10 +463,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" dependencies = [ "libc", - "redox_users", + "redox_users 0.3.5", "winapi", ] +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.0", + "winapi", +] + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + [[package]] name = "env_logger" version = "0.8.3" @@ -461,6 +509,16 @@ version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +dependencies = [ + "cmake", + "pkg-config", +] + [[package]] name = "fastrand" version = "1.4.0" @@ -470,6 +528,36 @@ dependencies = [ "instant", ] +[[package]] +name = "float-ord" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" + +[[package]] +name = "font-kit" +version = "0.10.0" +source = "git+https://github.com/zed-industries/font-kit?rev=8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1#8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" +dependencies = [ + "bitflags", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs-next", + "dwrote", + "float-ord", + "freetype", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "servo-fontconfig", + "walkdir", + "winapi", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -512,6 +600,27 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7684cf33bb7f28497939e8c7cf17e3e4e3b8d9a0080ffa4f8ae2f515442ee855" +[[package]] +name = "freetype" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" +dependencies = [ + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + [[package]] name = "futures-core" version = "0.3.12" @@ -563,6 +672,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + [[package]] name = "glob" version = "0.3.0" @@ -579,15 +699,20 @@ dependencies = [ "cc", "cocoa", "core-foundation", + "core-graphics", "core-text", "ctor", + "font-kit", "foreign-types 0.5.0", "log", "metal", "num_cpus", "objc", + "ordered-float", + "parking_lot", "pathfinder_color", "pathfinder_geometry", + "rand", "smol", "tree-sitter", ] @@ -644,6 +769,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.14" @@ -767,12 +901,46 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +[[package]] +name = "ordered-float" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "766f840da25490628d8e63e529cd21c014f6600c6b8517add12a6fa6167a6218" +dependencies = [ + "num-traits", +] + [[package]] name = "parking" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +[[package]] +name = "parking_lot" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +dependencies = [ + "cfg-if 1.0.0", + "instant", + "libc", + "redox_syscall 0.2.5", + "smallvec", + "winapi", +] + [[package]] name = "pathfinder_color" version = "0.5.0" @@ -813,6 +981,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827" +[[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + [[package]] name = "polling" version = "2.0.2" @@ -826,6 +1000,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -844,23 +1024,82 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom 0.2.2", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" dependencies = [ - "getrandom", - "redox_syscall", + "getrandom 0.1.16", + "redox_syscall 0.1.57", "rust-argon2", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom 0.2.2", + "redox_syscall 0.2.5", +] + [[package]] name = "regex" version = "1.4.3" @@ -906,12 +1145,27 @@ dependencies = [ "semver", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scoped-tls" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "semver" version = "0.9.0" @@ -927,6 +1181,27 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +dependencies = [ + "libc", + "servo-fontconfig-sys", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +dependencies = [ + "expat-sys", + "freetype-sys", + "pkg-config", +] + [[package]] name = "shlex" version = "0.1.1" @@ -963,6 +1238,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "smallvec" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" + [[package]] name = "smol" version = "1.2.5" @@ -1093,6 +1374,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -1154,13 +1446,26 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "zed" version = "0.1.0" dependencies = [ + "anyhow", + "arrayvec", "dirs", "gpui", + "lazy_static", "libc", "log", + "rand", "simplelog", ] diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index e1451fe22d..dae6a45c57 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -8,8 +8,11 @@ version = "0.1.0" async-task = {git = "https://github.com/zedit-io/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} ctor = "0.1" num_cpus = "1.13" +ordered-float = "2.1.1" +parking_lot = "0.11.1" pathfinder_color = "0.5" pathfinder_geometry = "0.5" +rand = "0.8.3" smol = "1.2" tree-sitter = "0.17" @@ -21,7 +24,9 @@ cc = "1.0.67" anyhow = "1" cocoa = "0.24" core-foundation = "0.9" +core-graphics = "0.22.2" core-text = "19.2" +font-kit = {git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1"} foreign-types = "0.5" log = "0.4" metal = "0.21" diff --git a/gpui/src/app.rs b/gpui/src/app.rs new file mode 100644 index 0000000000..2cc662fa6c --- /dev/null +++ b/gpui/src/app.rs @@ -0,0 +1,3179 @@ +use crate::{ + elements::Element, + executor, + keymap::{self, Keystroke}, + util::post_inc, +}; +use anyhow::{anyhow, Result}; +use keymap::MatchResult; +use parking_lot::Mutex; +use smol::{channel, prelude::*}; +use std::{ + any::{type_name, Any, TypeId}, + borrow, + cell::RefCell, + collections::{HashMap, HashSet, VecDeque}, + fmt::{self, Debug}, + hash::{Hash, Hasher}, + marker::PhantomData, + mem, + rc::{self, Rc}, + sync::{Arc, Weak}, +}; + +pub trait Entity: 'static + Send + Sync { + type Event; +} + +pub trait View: Entity { + fn ui_name() -> &'static str; + fn render<'a>(&self, app: &AppContext) -> Box; + fn on_focus(&mut self, _ctx: &mut ViewContext) {} + fn on_blur(&mut self, _ctx: &mut ViewContext) {} + fn keymap_context(&self, _: &AppContext) -> keymap::Context { + Self::default_keymap_context() + } + fn default_keymap_context() -> keymap::Context { + let mut ctx = keymap::Context::default(); + ctx.set.insert(Self::ui_name().into()); + ctx + } +} + +pub trait ModelAsRef { + fn model(&self, handle: &ModelHandle) -> &T; +} + +pub trait UpdateModel { + fn update_model(&mut self, handle: &ModelHandle, update: F) -> S + where + T: Entity, + F: FnOnce(&mut T, &mut ModelContext) -> S; +} + +pub trait ViewAsRef { + fn view(&self, handle: &ViewHandle) -> &T; +} + +pub trait UpdateView { + fn update_view(&mut self, handle: &ViewHandle, update: F) -> S + where + T: View, + F: FnOnce(&mut T, &mut ViewContext) -> S; +} + +#[derive(Clone)] +pub struct App(Rc>); + +impl App { + #[cfg(test)] + pub fn run>(f: impl FnOnce(App) -> F) -> T { + let foreground = Rc::new(executor::Foreground::new().unwrap()); + let app = Self(Rc::new(RefCell::new( + MutableAppContext::with_foreground_executor(foreground.clone()), + ))); + app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0)); + smol::block_on(foreground.run(f(app))) + } + + pub fn new() -> Result { + let app = Self(Rc::new(RefCell::new(MutableAppContext::new()?))); + app.0.borrow_mut().weak_self = Some(Rc::downgrade(&app.0)); + Ok(app) + } + + pub fn on_window_invalidated( + &self, + window_id: usize, + callback: F, + ) { + self.0 + .borrow_mut() + .on_window_invalidated(window_id, callback); + } + + pub fn add_action(&self, name: S, handler: F) + where + S: Into, + V: View, + T: Any, + F: 'static + FnMut(&mut V, &T, &mut ViewContext), + { + self.0.borrow_mut().add_action(name, handler); + } + + pub fn add_global_action(&self, name: S, handler: F) + where + S: Into, + T: 'static + Any, + F: 'static + FnMut(&T, &mut MutableAppContext), + { + self.0.borrow_mut().add_global_action(name, handler); + } + + pub fn dispatch_action( + &self, + window_id: usize, + responder_chain: Vec, + name: &str, + arg: T, + ) { + self.0.borrow_mut().dispatch_action( + window_id, + &responder_chain, + name, + Box::new(arg).as_ref(), + ); + } + + pub fn dispatch_global_action(&self, name: &str, arg: T) { + self.0 + .borrow_mut() + .dispatch_global_action(name, Box::new(arg).as_ref()); + } + + pub fn add_bindings>(&self, bindings: T) { + self.0.borrow_mut().add_bindings(bindings); + } + + pub fn dispatch_keystroke( + &self, + window_id: usize, + responder_chain: Vec, + keystroke: &Keystroke, + ) -> Result { + let mut state = self.0.borrow_mut(); + state.dispatch_keystroke(window_id, responder_chain, keystroke) + } + + pub fn add_model(&mut self, build_model: F) -> ModelHandle + where + T: Entity, + F: FnOnce(&mut ModelContext) -> T, + { + let mut state = self.0.borrow_mut(); + state.pending_flushes += 1; + let handle = state.add_model(build_model); + state.flush_effects(); + handle + } + + fn read_model(&self, handle: &ModelHandle, read: F) -> S + where + T: Entity, + F: FnOnce(&T, &AppContext) -> S, + { + let state = self.0.borrow(); + read(state.model(handle), &state.ctx) + } + + pub fn add_window(&mut self, build_root_view: F) -> (usize, ViewHandle) + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + self.0.borrow_mut().add_window(build_root_view) + } + + pub fn window_ids(&self) -> Vec { + self.0.borrow().window_ids().collect() + } + + pub fn root_view(&self, window_id: usize) -> Option> { + self.0.borrow().root_view(window_id) + } + + pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + let mut state = self.0.borrow_mut(); + state.pending_flushes += 1; + let handle = state.add_view(window_id, build_view); + state.flush_effects(); + handle + } + + pub fn add_option_view( + &mut self, + window_id: usize, + build_view: F, + ) -> Option> + where + T: View, + F: FnOnce(&mut ViewContext) -> Option, + { + let mut state = self.0.borrow_mut(); + state.pending_flushes += 1; + let handle = state.add_option_view(window_id, build_view); + state.flush_effects(); + handle + } + + pub fn read T>(&mut self, callback: F) -> T { + callback(self.0.borrow().ctx()) + } + + pub fn update T>(&mut self, callback: F) -> T { + let mut state = self.0.borrow_mut(); + state.pending_flushes += 1; + let result = callback(&mut *state); + state.flush_effects(); + result + } + + fn read_view(&self, handle: &ViewHandle, read: F) -> S + where + T: View, + F: FnOnce(&T, &AppContext) -> S, + { + let state = self.0.borrow(); + read(state.view(handle), state.ctx()) + } + + #[cfg(test)] + pub fn finish_pending_tasks(&self) -> impl Future { + self.0.borrow().finish_pending_tasks() + } +} + +impl UpdateModel for App { + fn update_model(&mut self, handle: &ModelHandle, update: F) -> S + where + T: Entity, + F: FnOnce(&mut T, &mut ModelContext) -> S, + { + let mut state = self.0.borrow_mut(); + state.pending_flushes += 1; + let result = state.update_model(handle, update); + state.flush_effects(); + result + } +} + +impl UpdateView for App { + fn update_view(&mut self, handle: &ViewHandle, update: F) -> S + where + T: View, + F: FnOnce(&mut T, &mut ViewContext) -> S, + { + let mut state = self.0.borrow_mut(); + state.pending_flushes += 1; + let result = state.update_view(handle, update); + state.flush_effects(); + result + } +} + +type ActionCallback = + dyn FnMut(&mut dyn AnyView, &dyn Any, &mut MutableAppContext, usize, usize) -> bool; + +type GlobalActionCallback = dyn FnMut(&dyn Any, &mut MutableAppContext); + +pub struct MutableAppContext { + ctx: AppContext, + actions: HashMap>>>, + global_actions: HashMap>>, + keystroke_matcher: keymap::Matcher, + next_entity_id: usize, + next_window_id: usize, + next_task_id: usize, + weak_self: Option>>, + subscriptions: HashMap>, + observations: HashMap>, + window_invalidations: HashMap, + invalidation_callbacks: + HashMap>, + foreground: Rc, + background: Arc, + task_callbacks: HashMap, + task_done: (channel::Sender, channel::Receiver), + pending_effects: VecDeque, + pending_flushes: usize, + flushing_effects: bool, +} + +impl MutableAppContext { + pub fn new() -> Result { + Ok(Self::with_foreground_executor(Rc::new( + executor::Foreground::new()?, + ))) + } + + fn with_foreground_executor(foreground: Rc) -> Self { + Self { + ctx: AppContext { + models: HashMap::new(), + windows: HashMap::new(), + ref_counts: Arc::new(Mutex::new(RefCounts::default())), + }, + actions: HashMap::new(), + global_actions: HashMap::new(), + keystroke_matcher: keymap::Matcher::default(), + next_entity_id: 0, + next_window_id: 0, + next_task_id: 0, + weak_self: None, + subscriptions: HashMap::new(), + observations: HashMap::new(), + window_invalidations: HashMap::new(), + invalidation_callbacks: HashMap::new(), + foreground, + background: Arc::new(executor::Background::new()), + task_callbacks: HashMap::new(), + task_done: channel::unbounded(), + pending_effects: VecDeque::new(), + pending_flushes: 0, + flushing_effects: false, + } + } + + pub fn ctx(&self) -> &AppContext { + &self.ctx + } + + pub fn foreground_executor(&self) -> Rc { + self.foreground.clone() + } + + pub fn on_window_invalidated( + &mut self, + window_id: usize, + callback: F, + ) { + self.invalidation_callbacks + .insert(window_id, Box::new(callback)); + } + + pub fn add_action(&mut self, name: S, mut handler: F) + where + S: Into, + V: View, + T: Any, + F: 'static + FnMut(&mut V, &T, &mut ViewContext), + { + let name = name.into(); + let name_clone = name.clone(); + let handler = Box::new( + move |view: &mut dyn AnyView, + arg: &dyn Any, + app: &mut MutableAppContext, + window_id: usize, + view_id: usize| { + match arg.downcast_ref() { + Some(arg) => { + let mut ctx = ViewContext::new(app, window_id, view_id); + handler( + view.as_any_mut() + .downcast_mut() + .expect("downcast is type safe"), + arg, + &mut ctx, + ); + ctx.halt_action_dispatch + } + None => { + log::error!("Could not downcast argument for action {}", name_clone); + false + } + } + }, + ); + + self.actions + .entry(TypeId::of::()) + .or_default() + .entry(name) + .or_default() + .push(handler); + } + + pub fn add_global_action(&mut self, name: S, mut handler: F) + where + S: Into, + T: 'static + Any, + F: 'static + FnMut(&T, &mut MutableAppContext), + { + let name = name.into(); + let name_clone = name.clone(); + let handler = Box::new(move |arg: &dyn Any, app: &mut MutableAppContext| { + if let Some(arg) = arg.downcast_ref() { + handler(arg, app); + } else { + log::error!("Could not downcast argument for action {}", name_clone); + } + }); + + self.global_actions.entry(name).or_default().push(handler); + } + + pub fn window_ids(&self) -> impl Iterator + '_ { + self.ctx.windows.keys().cloned() + } + + pub fn root_view(&self, window_id: usize) -> Option> { + self.ctx + .windows + .get(&window_id) + .and_then(|window| window.root_view.as_ref().unwrap().clone().downcast::()) + } + + pub fn root_view_id(&self, window_id: usize) -> Option { + self.ctx.root_view_id(window_id) + } + + pub fn focused_view_id(&self, window_id: usize) -> Option { + self.ctx.focused_view_id(window_id) + } + + pub fn render_view(&self, window_id: usize, view_id: usize) -> Result> { + self.ctx.render_view(window_id, view_id) + } + + pub fn render_views(&self, window_id: usize) -> Result>> { + self.ctx.render_views(window_id) + } + + pub fn dispatch_action( + &mut self, + window_id: usize, + responder_chain: &[usize], + name: &str, + arg: &dyn Any, + ) -> bool { + self.pending_flushes += 1; + let mut halted_dispatch = false; + + for view_id in responder_chain.iter().rev() { + if let Some(mut view) = self + .ctx + .windows + .get_mut(&window_id) + .and_then(|w| w.views.remove(view_id)) + { + let type_id = view.as_any().type_id(); + + if let Some((name, mut handlers)) = self + .actions + .get_mut(&type_id) + .and_then(|h| h.remove_entry(name)) + { + for handler in handlers.iter_mut().rev() { + let halt_dispatch = handler(view.as_mut(), arg, self, window_id, *view_id); + if halt_dispatch { + halted_dispatch = true; + break; + } + } + self.actions + .get_mut(&type_id) + .unwrap() + .insert(name, handlers); + } + + self.ctx + .windows + .get_mut(&window_id) + .unwrap() + .views + .insert(*view_id, view); + + if halted_dispatch { + break; + } + } + } + + if !halted_dispatch { + self.dispatch_global_action(name, arg); + } + + self.flush_effects(); + halted_dispatch + } + + fn dispatch_global_action(&mut self, name: &str, arg: &dyn Any) { + if let Some((name, mut handlers)) = self.global_actions.remove_entry(name) { + self.pending_flushes += 1; + for handler in handlers.iter_mut().rev() { + handler(arg, self); + } + self.global_actions.insert(name, handlers); + self.flush_effects(); + } + } + + fn add_bindings>(&mut self, bindings: T) { + self.keystroke_matcher.add_bindings(bindings); + } + + pub fn dispatch_keystroke( + &mut self, + window_id: usize, + responder_chain: Vec, + keystroke: &Keystroke, + ) -> Result { + log::info!( + "dispatch_keystroke {} {:?} {:?}", + window_id, + responder_chain, + keystroke + ); + + let mut context_chain = Vec::new(); + let mut context = keymap::Context::default(); + for view_id in &responder_chain { + if let Some(view) = self + .ctx + .windows + .get(&window_id) + .and_then(|w| w.views.get(view_id)) + { + context.extend(view.keymap_context(self.ctx())); + context_chain.push(context.clone()); + } else { + return Err(anyhow!( + "View {} in responder chain does not exist", + view_id + )); + } + } + + let mut pending = false; + for (i, ctx) in context_chain.iter().enumerate().rev() { + match self + .keystroke_matcher + .push_keystroke(keystroke.clone(), responder_chain[i], ctx) + { + MatchResult::None => {} + MatchResult::Pending => pending = true, + MatchResult::Action { name, arg } => { + if self.dispatch_action( + window_id, + &responder_chain[0..=i], + &name, + arg.as_ref().map(|arg| arg.as_ref()).unwrap_or(&()), + ) { + return Ok(true); + } + } + } + } + + Ok(pending) + } + + pub fn add_model(&mut self, build_model: F) -> ModelHandle + where + T: Entity, + F: FnOnce(&mut ModelContext) -> T, + { + self.pending_flushes += 1; + let model_id = post_inc(&mut self.next_entity_id); + let mut ctx = ModelContext::new(self, model_id); + let model = build_model(&mut ctx); + self.ctx.models.insert(model_id, Box::new(model)); + self.flush_effects(); + ModelHandle::new(model_id, &self.ctx.ref_counts) + } + + pub fn add_window(&mut self, build_root_view: F) -> (usize, ViewHandle) + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + let window_id = post_inc(&mut self.next_window_id); + self.ctx.windows.insert(window_id, Window::default()); + + let root_handle = self.add_view(window_id, build_root_view); + self.ctx.windows.get_mut(&window_id).unwrap().root_view = Some(root_handle.clone().into()); + self.focus(window_id, root_handle.id()); + + self.emit_ui_update(UiUpdate::OpenWindow { + window_id, + width: 1024.0, + height: 768.0, + }); + + (window_id, root_handle) + } + + pub fn add_view(&mut self, window_id: usize, build_view: F) -> ViewHandle + where + T: View, + F: FnOnce(&mut ViewContext) -> T, + { + self.add_option_view(window_id, |ctx| Some(build_view(ctx))) + .unwrap() + } + + pub fn add_option_view( + &mut self, + window_id: usize, + build_view: F, + ) -> Option> + where + T: View, + F: FnOnce(&mut ViewContext) -> Option, + { + let view_id = post_inc(&mut self.next_entity_id); + self.pending_flushes += 1; + let mut ctx = ViewContext::new(self, window_id, view_id); + let handle = if let Some(view) = build_view(&mut ctx) { + if let Some(window) = self.ctx.windows.get_mut(&window_id) { + window.views.insert(view_id, Box::new(view)); + } else { + panic!("Window does not exist"); + } + self.window_invalidations + .entry(window_id) + .or_default() + .updated + .insert(view_id); + Some(ViewHandle::new(window_id, view_id, &self.ctx.ref_counts)) + } else { + None + }; + self.flush_effects(); + handle + } + + fn remove_dropped_entities(&mut self) { + loop { + let (dropped_models, dropped_views) = self.ctx.ref_counts.lock().take_dropped(); + if dropped_models.is_empty() && dropped_views.is_empty() { + break; + } + + for model_id in dropped_models { + self.ctx.models.remove(&model_id); + self.subscriptions.remove(&model_id); + self.observations.remove(&model_id); + } + + for (window_id, view_id) in dropped_views { + self.subscriptions.remove(&view_id); + self.observations.remove(&view_id); + if let Some(window) = self.ctx.windows.get_mut(&window_id) { + self.window_invalidations + .entry(window_id) + .or_default() + .removed + .push(view_id); + window.views.remove(&view_id); + } + } + } + } + + fn flush_effects(&mut self) { + self.pending_flushes -= 1; + + if !self.flushing_effects && self.pending_flushes == 0 { + self.flushing_effects = true; + + while let Some(effect) = self.pending_effects.pop_front() { + match effect { + Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload), + Effect::ModelNotification { model_id } => self.notify_model_observers(model_id), + Effect::ViewNotification { window_id, view_id } => { + self.notify_view_observers(window_id, view_id) + } + Effect::Focus { window_id, view_id } => { + self.focus(window_id, view_id); + } + } + } + + self.flushing_effects = false; + self.remove_dropped_entities(); + self.update_windows(); + } + } + + fn update_windows(&mut self) { + let mut invalidations = HashMap::new(); + std::mem::swap(&mut invalidations, &mut self.window_invalidations); + + for (window_id, invalidation) in invalidations { + if let Some(mut callback) = self.invalidation_callbacks.remove(&window_id) { + callback(invalidation, self); + self.invalidation_callbacks.insert(window_id, callback); + } + } + } + + fn emit_event(&mut self, entity_id: usize, payload: Box) { + if let Some(subscriptions) = self.subscriptions.remove(&entity_id) { + for mut subscription in subscriptions { + let alive = match &mut subscription { + Subscription::FromModel { model_id, callback } => { + if let Some(mut model) = self.ctx.models.remove(model_id) { + callback(model.as_any_mut(), payload.as_ref(), self, *model_id); + self.ctx.models.insert(*model_id, model); + true + } else { + false + } + } + Subscription::FromView { + window_id, + view_id, + callback, + } => { + if let Some(mut view) = self + .ctx + .windows + .get_mut(&window_id) + .and_then(|window| window.views.remove(view_id)) + { + callback( + view.as_any_mut(), + payload.as_ref(), + self, + *window_id, + *view_id, + ); + self.ctx + .windows + .get_mut(&window_id) + .unwrap() + .views + .insert(*view_id, view); + true + } else { + false + } + } + }; + + if alive { + self.subscriptions + .entry(entity_id) + .or_default() + .push(subscription); + } + } + } + } + + fn notify_model_observers(&mut self, observed_id: usize) { + if let Some(observations) = self.observations.remove(&observed_id) { + if self.ctx.models.contains_key(&observed_id) { + for mut observation in observations { + let alive = match &mut observation { + Observation::FromModel { model_id, callback } => { + if let Some(mut model) = self.ctx.models.remove(model_id) { + callback(model.as_any_mut(), observed_id, self, *model_id); + self.ctx.models.insert(*model_id, model); + true + } else { + false + } + } + Observation::FromView { + window_id, + view_id, + callback, + } => { + if let Some(mut view) = self + .ctx + .windows + .get_mut(window_id) + .and_then(|w| w.views.remove(view_id)) + { + callback( + view.as_any_mut(), + observed_id, + self, + *window_id, + *view_id, + ); + self.ctx + .windows + .get_mut(window_id) + .unwrap() + .views + .insert(*view_id, view); + true + } else { + false + } + } + }; + + if alive { + self.observations + .entry(observed_id) + .or_default() + .push(observation); + } + } + } + } + } + + fn notify_view_observers(&mut self, window_id: usize, view_id: usize) { + self.window_invalidations + .entry(window_id) + .or_default() + .updated + .insert(view_id); + } + + fn focus(&mut self, window_id: usize, focused_id: usize) { + if self + .ctx + .windows + .get(&window_id) + .and_then(|w| w.focused_view) + .map_or(false, |cur_focused| cur_focused == focused_id) + { + return; + } + + self.pending_flushes += 1; + + if let Some((blurred_id, mut blurred)) = + self.ctx.windows.get_mut(&window_id).and_then(|w| { + let blurred_view = w.focused_view; + w.focused_view = Some(focused_id); + blurred_view.and_then(|id| w.views.remove(&id).map(|view| (id, view))) + }) + { + blurred.on_blur(self, window_id, blurred_id); + self.ctx + .windows + .get_mut(&window_id) + .unwrap() + .views + .insert(blurred_id, blurred); + } + + if let Some(mut focused) = self + .ctx + .windows + .get_mut(&window_id) + .and_then(|w| w.views.remove(&focused_id)) + { + focused.on_focus(self, window_id, focused_id); + self.ctx + .windows + .get_mut(&window_id) + .unwrap() + .views + .insert(focused_id, focused); + } + + self.flush_effects(); + } + + fn spawn_local(&mut self, future: F) -> usize + where + F: 'static + Future, + { + let task_id = post_inc(&mut self.next_task_id); + let app = self.weak_self.as_ref().unwrap().clone(); + self.foreground + .spawn(async move { + let output = future.await; + if let Some(app) = app.upgrade() { + app.borrow_mut() + .relay_task_output(task_id, Box::new(output)); + } + }) + .detach(); + task_id + } + + fn spawn_stream_local(&mut self, mut stream: F, done_tx: channel::Sender<()>) -> usize + where + F: 'static + Stream + Unpin, + { + let task_id = post_inc(&mut self.next_task_id); + let app = self.weak_self.as_ref().unwrap().clone(); + self.foreground + .spawn(async move { + loop { + match stream.next().await { + item @ Some(_) => { + if let Some(app) = app.upgrade() { + let mut app = app.borrow_mut(); + if app.relay_task_output(task_id, Box::new(item)) { + app.stream_completed(task_id); + break; + } + } else { + break; + } + } + item @ None => { + if let Some(app) = app.upgrade() { + let mut app = app.borrow_mut(); + app.relay_task_output(task_id, Box::new(item)); + app.stream_completed(task_id); + } + let _ = done_tx.send(()).await; + break; + } + } + } + }) + .detach(); + task_id + } + + fn relay_task_output(&mut self, task_id: usize, output: Box) -> bool { + self.pending_flushes += 1; + let task_callback = self.task_callbacks.remove(&task_id).unwrap(); + + let halt = match task_callback { + TaskCallback::OnModelFromFuture { model_id, callback } => { + if let Some(mut model) = self.ctx.models.remove(&model_id) { + callback( + model.as_any_mut(), + output, + self, + model_id, + self.foreground.clone(), + ); + self.ctx.models.insert(model_id, model); + } + self.task_done(task_id); + true + } + TaskCallback::OnModelFromStream { + model_id, + mut callback, + } => { + if let Some(mut model) = self.ctx.models.remove(&model_id) { + let halt = callback(model.as_any_mut(), output, self, model_id); + self.ctx.models.insert(model_id, model); + self.task_callbacks.insert( + task_id, + TaskCallback::OnModelFromStream { model_id, callback }, + ); + halt + } else { + true + } + } + TaskCallback::OnViewFromFuture { + window_id, + view_id, + callback, + } => { + if let Some(mut view) = self + .ctx + .windows + .get_mut(&window_id) + .and_then(|w| w.views.remove(&view_id)) + { + callback( + view.as_mut(), + output, + self, + window_id, + view_id, + self.foreground.clone(), + ); + self.ctx + .windows + .get_mut(&window_id) + .unwrap() + .views + .insert(view_id, view); + } + self.task_done(task_id); + true + } + TaskCallback::OnViewFromStream { + window_id, + view_id, + mut callback, + } => { + if let Some(mut view) = self + .ctx + .windows + .get_mut(&window_id) + .and_then(|w| w.views.remove(&view_id)) + { + let halt = callback(view.as_mut(), output, self, window_id, view_id); + self.ctx + .windows + .get_mut(&window_id) + .unwrap() + .views + .insert(view_id, view); + self.task_callbacks.insert( + task_id, + TaskCallback::OnViewFromStream { + window_id, + view_id, + callback, + }, + ); + halt + } else { + true + } + } + }; + self.flush_effects(); + halt + } + + fn stream_completed(&mut self, task_id: usize) { + self.task_callbacks.remove(&task_id); + self.task_done(task_id); + } + + fn task_done(&self, task_id: usize) { + let task_done = self.task_done.0.clone(); + self.foreground + .spawn(async move { + let _ = task_done.send(task_id).await; + }) + .detach() + } + + #[cfg(test)] + pub fn finish_pending_tasks(&self) -> impl Future { + let mut pending_tasks = self.task_callbacks.keys().cloned().collect::>(); + let task_done = self.task_done.1.clone(); + + async move { + while !pending_tasks.is_empty() { + if let Ok(task_id) = task_done.recv().await { + pending_tasks.remove(&task_id); + } else { + break; + } + } + } + } +} + +impl ModelAsRef for MutableAppContext { + fn model(&self, handle: &ModelHandle) -> &T { + if let Some(model) = self.ctx.models.get(&handle.model_id) { + model + .as_any() + .downcast_ref() + .expect("Downcast is type safe") + } else { + panic!("Circular model reference"); + } + } +} + +impl UpdateModel for MutableAppContext { + fn update_model(&mut self, handle: &ModelHandle, update: F) -> S + where + T: Entity, + F: FnOnce(&mut T, &mut ModelContext) -> S, + { + if let Some(mut model) = self.ctx.models.remove(&handle.model_id) { + self.pending_flushes += 1; + let mut ctx = ModelContext::new(self, handle.model_id); + let result = update( + model + .as_any_mut() + .downcast_mut() + .expect("Downcast is type safe"), + &mut ctx, + ); + self.ctx.models.insert(handle.model_id, model); + self.flush_effects(); + result + } else { + panic!("Circular model update"); + } + } +} + +impl ViewAsRef for MutableAppContext { + fn view(&self, handle: &ViewHandle) -> &T { + if let Some(window) = self.ctx.windows.get(&handle.window_id) { + if let Some(view) = window.views.get(&handle.view_id) { + view.as_any().downcast_ref().expect("Downcast is type safe") + } else { + panic!("Circular view reference"); + } + } else { + panic!("Window does not exist"); + } + } +} + +impl UpdateView for MutableAppContext { + fn update_view(&mut self, handle: &ViewHandle, update: F) -> S + where + T: View, + F: FnOnce(&mut T, &mut ViewContext) -> S, + { + self.pending_flushes += 1; + let mut view = if let Some(window) = self.ctx.windows.get_mut(&handle.window_id) { + if let Some(view) = window.views.remove(&handle.view_id) { + view + } else { + panic!("Circular view update"); + } + } else { + panic!("Window does not exist"); + }; + + let mut ctx = ViewContext::new(self, handle.window_id, handle.view_id); + let result = update( + view.as_any_mut() + .downcast_mut() + .expect("Downcast is type safe"), + &mut ctx, + ); + self.ctx + .windows + .get_mut(&handle.window_id) + .unwrap() + .views + .insert(handle.view_id, view); + self.flush_effects(); + result + } +} + +pub struct AppContext { + models: HashMap>, + windows: HashMap, + ref_counts: Arc>, +} + +impl AppContext { + pub fn root_view_id(&self, window_id: usize) -> Option { + self.windows + .get(&window_id) + .and_then(|window| window.root_view.as_ref().map(|v| v.id())) + } + + pub fn focused_view_id(&self, window_id: usize) -> Option { + self.windows + .get(&window_id) + .and_then(|window| window.focused_view) + } + + pub fn render_view(&self, window_id: usize, view_id: usize) -> Result> { + self.windows + .get(&window_id) + .and_then(|w| w.views.get(&view_id)) + .map(|v| v.render(self)) + .ok_or(anyhow!("view not found")) + } + + pub fn render_views(&self, window_id: usize) -> Result>> { + self.windows + .get(&window_id) + .map(|w| { + w.views + .iter() + .map(|(id, view)| view.render(self)) + .collect::>() + }) + .ok_or(anyhow!("window not found")) + } +} + +impl ModelAsRef for AppContext { + fn model(&self, handle: &ModelHandle) -> &T { + if let Some(model) = self.models.get(&handle.model_id) { + model + .as_any() + .downcast_ref() + .expect("downcast should be type safe") + } else { + panic!("circular model reference"); + } + } +} + +impl ViewAsRef for AppContext { + fn view(&self, handle: &ViewHandle) -> &T { + if let Some(window) = self.windows.get(&handle.window_id) { + if let Some(view) = window.views.get(&handle.view_id) { + view.as_any() + .downcast_ref() + .expect("downcast should be type safe") + } else { + panic!("circular view reference"); + } + } else { + panic!("window does not exist"); + } + } +} + +#[derive(Default)] +struct Window { + views: HashMap>, + root_view: Option, + focused_view: Option, +} + +#[derive(Default, Clone)] +pub struct WindowInvalidation { + pub updated: HashSet, + pub removed: Vec, +} + +pub enum Effect { + Event { + entity_id: usize, + payload: Box, + }, + ModelNotification { + model_id: usize, + }, + ViewNotification { + window_id: usize, + view_id: usize, + }, + Focus { + window_id: usize, + view_id: usize, + }, +} + +pub trait AnyModel: Send + Sync { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +impl AnyModel for T +where + T: Entity, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +pub trait AnyView: Send + Sync { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; + fn ui_name(&self) -> &'static str; + fn render<'a>(&self, app: &AppContext) -> Box; + fn on_focus(&mut self, app: &mut MutableAppContext, window_id: usize, view_id: usize); + fn on_blur(&mut self, app: &mut MutableAppContext, window_id: usize, view_id: usize); + fn keymap_context(&self, app: &AppContext) -> keymap::Context; +} + +impl AnyView for T +where + T: View, +{ + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn ui_name(&self) -> &'static str { + T::ui_name() + } + + fn render<'a>(&self, app: &AppContext) -> Box { + View::render(self, bump, app) + } + + fn on_focus(&mut self, app: &mut MutableAppContext, window_id: usize, view_id: usize) { + let mut ctx = ViewContext::new(app, window_id, view_id); + View::on_focus(self, &mut ctx); + } + + fn on_blur(&mut self, app: &mut MutableAppContext, window_id: usize, view_id: usize) { + let mut ctx = ViewContext::new(app, window_id, view_id); + View::on_blur(self, &mut ctx); + } + + fn keymap_context(&self, app: &AppContext) -> keymap::Context { + View::keymap_context(self, app) + } +} + +pub struct ModelContext<'a, T: ?Sized> { + app: &'a mut MutableAppContext, + model_id: usize, + model_type: PhantomData, + halt_stream: bool, +} + +impl<'a, T: Entity> ModelContext<'a, T> { + fn new(app: &'a mut MutableAppContext, model_id: usize) -> Self { + Self { + app, + model_id, + model_type: PhantomData, + halt_stream: false, + } + } + + pub fn app(&self) -> &AppContext { + &self.app.ctx + } + + pub fn app_mut(&mut self) -> &mut MutableAppContext { + self.app + } + + pub fn background_executor(&self) -> Arc { + self.app.background.clone() + } + + pub fn halt_stream(&mut self) { + self.halt_stream = true; + } + + pub fn model_id(&self) -> usize { + self.model_id + } + + pub fn add_model(&mut self, build_model: F) -> ModelHandle + where + S: Entity, + F: FnOnce(&mut ModelContext) -> S, + { + self.app.add_model(build_model) + } + + pub fn subscribe(&mut self, handle: &ModelHandle, mut callback: F) + where + S::Event: 'static, + F: 'static + FnMut(&mut T, &S::Event, &mut ModelContext), + { + self.app + .subscriptions + .entry(handle.model_id) + .or_default() + .push(Subscription::FromModel { + model_id: self.model_id, + callback: Box::new(move |model, payload, app, model_id| { + let model = model.downcast_mut().expect("downcast is type safe"); + let payload = payload.downcast_ref().expect("downcast is type safe"); + let mut ctx = ModelContext::new(app, model_id); + callback(model, payload, &mut ctx); + }), + }); + } + + pub fn emit(&mut self, payload: T::Event) { + self.app.pending_effects.push_back(Effect::Event { + entity_id: self.model_id, + payload: Box::new(payload), + }); + } + + pub fn observe(&mut self, handle: &ModelHandle, mut callback: F) + where + S: Entity, + F: 'static + FnMut(&mut T, ModelHandle, &mut ModelContext), + { + self.app + .observations + .entry(handle.model_id) + .or_default() + .push(Observation::FromModel { + model_id: self.model_id, + callback: Box::new(move |model, observed_id, app, model_id| { + let model = model.downcast_mut().expect("downcast is type safe"); + let observed = ModelHandle::new(observed_id, &app.ctx.ref_counts); + let mut ctx = ModelContext::new(app, model_id); + callback(model, observed, &mut ctx); + }), + }); + } + + pub fn notify(&mut self) { + self.app + .pending_effects + .push_back(Effect::ModelNotification { + model_id: self.model_id, + }); + } + + pub fn spawn_local(&mut self, future: S, callback: F) -> impl Future + where + S: 'static + Future, + F: 'static + FnOnce(&mut T, S::Output, &mut ModelContext) -> U, + U: 'static, + { + let (tx, rx) = channel::bounded(1); + + let task_id = self.app.spawn_local(future); + + self.app.task_callbacks.insert( + task_id, + TaskCallback::OnModelFromFuture { + model_id: self.model_id, + callback: Box::new(move |model, output, app, model_id, executor| { + let model = model.downcast_mut().unwrap(); + let output = *output.downcast().unwrap(); + let result = callback(model, output, &mut ModelContext::new(app, model_id)); + executor + .spawn(async move { tx.send(result).await }) + .detach(); + }), + }, + ); + + async move { rx.recv().await.unwrap() } + } + + pub fn spawn(&mut self, future: S, callback: F) -> impl Future + where + S: 'static + Future + Send, + S::Output: Send, + F: 'static + FnOnce(&mut T, S::Output, &mut ModelContext) -> U, + U: 'static, + { + let (tx, rx) = channel::bounded(1); + + self.app + .background + .spawn(async move { + if let Err(_) = tx.send(future.await).await { + log::error!("Error sending background task result to main thread",); + } + }) + .detach(); + + self.spawn_local(async move { rx.recv().await.unwrap() }, callback) + } + + pub fn spawn_stream_local( + &mut self, + stream: S, + mut callback: F, + ) -> impl Future + where + S: 'static + Stream + Unpin, + F: 'static + FnMut(&mut T, Option, &mut ModelContext), + { + let (tx, rx) = channel::bounded(1); + + let task_id = self.app.spawn_stream_local(stream, tx); + self.app.task_callbacks.insert( + task_id, + TaskCallback::OnModelFromStream { + model_id: self.model_id, + callback: Box::new(move |model, output, app, model_id| { + let model = model.downcast_mut().unwrap(); + let output = *output.downcast().unwrap(); + let mut ctx = ModelContext::new(app, model_id); + callback(model, output, &mut ctx); + ctx.halt_stream + }), + }, + ); + + async move { rx.recv().await.unwrap() } + } +} + +impl ModelAsRef for ModelContext<'_, M> { + fn model(&self, handle: &ModelHandle) -> &T { + self.app.model(handle) + } +} + +impl UpdateModel for ModelContext<'_, M> { + fn update_model(&mut self, handle: &ModelHandle, update: F) -> S + where + T: Entity, + F: FnOnce(&mut T, &mut ModelContext) -> S, + { + self.app.update_model(handle, update) + } +} + +pub struct ViewContext<'a, T: ?Sized> { + app: &'a mut MutableAppContext, + window_id: usize, + view_id: usize, + view_type: PhantomData, + halt_action_dispatch: bool, + halt_stream: bool, +} + +impl<'a, T: View> ViewContext<'a, T> { + fn new(app: &'a mut MutableAppContext, window_id: usize, view_id: usize) -> Self { + Self { + app, + window_id, + view_id, + view_type: PhantomData, + halt_action_dispatch: true, + halt_stream: false, + } + } + + pub fn handle(&self) -> WeakViewHandle { + WeakViewHandle::new(self.window_id, self.view_id) + } + + pub fn window_id(&self) -> usize { + self.window_id + } + + pub fn app(&self) -> &AppContext { + &self.app.ctx + } + + pub fn app_mut(&mut self) -> &mut MutableAppContext { + self.app + } + + pub fn focus(&mut self, handle: S) + where + S: Into, + { + let handle = handle.into(); + self.app.pending_effects.push_back(Effect::Focus { + window_id: handle.window_id, + view_id: handle.view_id, + }); + } + + pub fn focus_self(&mut self) { + self.app.pending_effects.push_back(Effect::Focus { + window_id: self.window_id, + view_id: self.view_id, + }); + } + + pub fn add_model(&mut self, build_model: F) -> ModelHandle + where + S: Entity, + F: FnOnce(&mut ModelContext) -> S, + { + self.app.add_model(build_model) + } + + pub fn add_view(&mut self, build_view: F) -> ViewHandle + where + S: View, + F: FnOnce(&mut ViewContext) -> S, + { + self.app.add_view(self.window_id, build_view) + } + + pub fn add_option_view(&mut self, build_view: F) -> Option> + where + S: View, + F: FnOnce(&mut ViewContext) -> Option, + { + self.app.add_option_view(self.window_id, build_view) + } + + pub fn subscribe_to_model(&mut self, handle: &ModelHandle, mut callback: F) + where + E: Entity, + E::Event: 'static, + F: 'static + FnMut(&mut T, ModelHandle, &E::Event, &mut ViewContext), + { + let emitter_handle = handle.downgrade(); + self.app + .subscriptions + .entry(handle.id()) + .or_default() + .push(Subscription::FromView { + window_id: self.window_id, + view_id: self.view_id, + callback: Box::new(move |view, payload, app, window_id, view_id| { + if let Some(emitter_handle) = emitter_handle.upgrade(app.ctx()) { + let model = view.downcast_mut().expect("downcast is type safe"); + let payload = payload.downcast_ref().expect("downcast is type safe"); + let mut ctx = ViewContext::new(app, window_id, view_id); + callback(model, emitter_handle, payload, &mut ctx); + } + }), + }); + } + + pub fn subscribe_to_view(&mut self, handle: &ViewHandle, mut callback: F) + where + V: View, + V::Event: 'static, + F: 'static + FnMut(&mut T, ViewHandle, &V::Event, &mut ViewContext), + { + let emitter_handle = handle.downgrade(); + + self.app + .subscriptions + .entry(handle.id()) + .or_default() + .push(Subscription::FromView { + window_id: self.window_id, + view_id: self.view_id, + callback: Box::new(move |view, payload, app, window_id, view_id| { + if let Some(emitter_handle) = emitter_handle.upgrade(app.ctx()) { + let model = view.downcast_mut().expect("downcast is type safe"); + let payload = payload.downcast_ref().expect("downcast is type safe"); + let mut ctx = ViewContext::new(app, window_id, view_id); + callback(model, emitter_handle, payload, &mut ctx); + } + }), + }); + } + + pub fn emit(&mut self, payload: T::Event) { + self.app.pending_effects.push_back(Effect::Event { + entity_id: self.view_id, + payload: Box::new(payload), + }); + } + + pub fn observe(&mut self, handle: &ModelHandle, mut callback: F) + where + S: Entity, + F: 'static + FnMut(&mut T, ModelHandle, &mut ViewContext), + { + self.app + .observations + .entry(handle.id()) + .or_default() + .push(Observation::FromView { + window_id: self.window_id, + view_id: self.view_id, + callback: Box::new(move |view, observed_id, app, window_id, view_id| { + let view = view.downcast_mut().expect("downcast is type safe"); + let observed = ModelHandle::new(observed_id, &app.ctx.ref_counts); + let mut ctx = ViewContext::new(app, window_id, view_id); + callback(view, observed, &mut ctx); + }), + }); + } + + pub fn notify(&mut self) { + self.app + .pending_effects + .push_back(Effect::ViewNotification { + window_id: self.window_id, + view_id: self.view_id, + }); + } + + pub fn propagate_action(&mut self) { + self.halt_action_dispatch = false; + } + + pub fn halt_stream(&mut self) { + self.halt_stream = true; + } + + pub fn spawn_local(&mut self, future: S, callback: F) -> impl Future + where + S: 'static + Future, + F: 'static + FnOnce(&mut T, S::Output, &mut ViewContext) -> U, + U: 'static, + { + let (tx, rx) = channel::bounded(1); + + let task_id = self.app.spawn_local(future); + + self.app.task_callbacks.insert( + task_id, + TaskCallback::OnViewFromFuture { + window_id: self.window_id, + view_id: self.view_id, + callback: Box::new(move |view, output, app, window_id, view_id, executor| { + let view = view.as_any_mut().downcast_mut().unwrap(); + let output = *output.downcast().unwrap(); + let result = + callback(view, output, &mut ViewContext::new(app, window_id, view_id)); + executor + .spawn(async move { tx.send(result).await }) + .detach(); + }), + }, + ); + + async move { rx.recv().await.unwrap() } + } + + pub fn spawn(&mut self, future: S, callback: F) -> impl Future + where + S: 'static + Future + Send, + S::Output: Send, + F: 'static + FnOnce(&mut T, S::Output, &mut ViewContext) -> U, + U: 'static, + { + let (tx, rx) = channel::bounded(1); + + self.app + .background + .spawn(async move { + if let Err(_) = tx.send(future.await).await { + log::error!("Error sending background task result to main thread",); + } + }) + .detach(); + + self.spawn_local(async move { rx.recv().await.unwrap() }, callback) + } + + pub fn spawn_stream_local( + &mut self, + stream: S, + mut callback: F, + ) -> impl Future + where + S: 'static + Stream + Unpin, + F: 'static + FnMut(&mut T, Option, &mut ViewContext), + { + let (tx, rx) = channel::bounded(1); + + let task_id = self.app.spawn_stream_local(stream, tx); + self.app.task_callbacks.insert( + task_id, + TaskCallback::OnViewFromStream { + window_id: self.window_id, + view_id: self.view_id, + callback: Box::new(move |view, output, app, window_id, view_id| { + let view = view.as_any_mut().downcast_mut().unwrap(); + let output = *output.downcast().unwrap(); + let mut ctx = ViewContext::new(app, window_id, view_id); + callback(view, output, &mut ctx); + ctx.halt_stream + }), + }, + ); + + async move { rx.recv().await.unwrap() } + } +} + +impl ModelAsRef for ViewContext<'_, V> { + fn model(&self, handle: &ModelHandle) -> &T { + self.app.model(handle) + } +} + +impl UpdateModel for ViewContext<'_, V> { + fn update_model(&mut self, handle: &ModelHandle, update: F) -> S + where + T: Entity, + F: FnOnce(&mut T, &mut ModelContext) -> S, + { + self.app.update_model(handle, update) + } +} + +impl ViewAsRef for ViewContext<'_, V> { + fn view(&self, handle: &ViewHandle) -> &T { + self.app.view(handle) + } +} + +impl UpdateView for ViewContext<'_, V> { + fn update_view(&mut self, handle: &ViewHandle, update: F) -> S + where + T: View, + F: FnOnce(&mut T, &mut ViewContext) -> S, + { + self.app.update_view(handle, update) + } +} + +pub trait Handle { + fn id(&self) -> usize; + fn location(&self) -> EntityLocation; +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] +pub enum EntityLocation { + Model(usize), + View(usize, usize), +} + +pub struct ModelHandle { + model_id: usize, + model_type: PhantomData, + ref_counts: Weak>, +} + +impl ModelHandle { + fn new(model_id: usize, ref_counts: &Arc>) -> Self { + ref_counts.lock().inc(model_id); + Self { + model_id, + model_type: PhantomData, + ref_counts: Arc::downgrade(ref_counts), + } + } + + fn downgrade(&self) -> WeakModelHandle { + WeakModelHandle::new(self.model_id) + } + + pub fn id(&self) -> usize { + self.model_id + } + + pub fn as_ref<'a, A: ModelAsRef>(&self, app: &'a A) -> &'a T { + app.model(self) + } + + pub fn read<'a, S, F>(&self, app: &'a App, read: F) -> S + where + F: FnOnce(&T, &AppContext) -> S, + { + app.read_model(self, read) + } + + pub fn update(&self, app: &mut A, update: F) -> S + where + A: UpdateModel, + F: FnOnce(&mut T, &mut ModelContext) -> S, + { + app.update_model(self, update) + } +} + +impl Clone for ModelHandle { + fn clone(&self) -> Self { + if let Some(ref_counts) = self.ref_counts.upgrade() { + ref_counts.lock().inc(self.model_id); + } + + Self { + model_id: self.model_id, + model_type: PhantomData, + ref_counts: self.ref_counts.clone(), + } + } +} + +impl PartialEq for ModelHandle { + fn eq(&self, other: &Self) -> bool { + self.model_id == other.model_id + } +} + +impl Eq for ModelHandle {} + +impl Hash for ModelHandle { + fn hash(&self, state: &mut H) { + self.model_id.hash(state); + } +} + +impl borrow::Borrow for ModelHandle { + fn borrow(&self) -> &usize { + &self.model_id + } +} + +impl Debug for ModelHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple(&format!("ModelHandle<{}>", type_name::())) + .field(&self.model_id) + .finish() + } +} + +unsafe impl Send for ModelHandle {} +unsafe impl Sync for ModelHandle {} + +impl Drop for ModelHandle { + fn drop(&mut self) { + if let Some(ref_counts) = self.ref_counts.upgrade() { + ref_counts.lock().dec_model(self.model_id); + } + } +} + +impl Handle for ModelHandle { + fn id(&self) -> usize { + self.model_id + } + + fn location(&self) -> EntityLocation { + EntityLocation::Model(self.model_id) + } +} + +pub struct WeakModelHandle { + model_id: usize, + model_type: PhantomData, +} + +impl WeakModelHandle { + fn new(model_id: usize) -> Self { + Self { + model_id, + model_type: PhantomData, + } + } + + pub fn upgrade(&self, app: &AppContext) -> Option> { + if app.models.contains_key(&self.model_id) { + Some(ModelHandle::new(self.model_id, &app.ref_counts)) + } else { + None + } + } +} + +pub struct ViewHandle { + window_id: usize, + view_id: usize, + view_type: PhantomData, + ref_counts: Weak>, +} + +impl ViewHandle { + fn new(window_id: usize, view_id: usize, ref_counts: &Arc>) -> Self { + ref_counts.lock().inc(view_id); + Self { + window_id, + view_id, + view_type: PhantomData, + ref_counts: Arc::downgrade(ref_counts), + } + } + + fn downgrade(&self) -> WeakViewHandle { + WeakViewHandle::new(self.window_id, self.view_id) + } + + pub fn window_id(&self) -> usize { + self.window_id + } + + pub fn id(&self) -> usize { + self.view_id + } + + pub fn as_ref<'a, A: ViewAsRef>(&self, app: &'a A) -> &'a T { + app.view(self) + } + + pub fn read<'a, F, S>(&self, app: &'a App, read: F) -> S + where + F: FnOnce(&T, &AppContext) -> S, + { + app.read_view(self, read) + } + + pub fn update(&self, app: &mut A, update: F) -> S + where + A: UpdateView, + F: FnOnce(&mut T, &mut ViewContext) -> S, + { + app.update_view(self, update) + } + + pub fn is_focused(&self, app: &AppContext) -> bool { + app.focused_view_id(self.window_id) + .map_or(false, |focused_id| focused_id == self.view_id) + } +} + +impl Clone for ViewHandle { + fn clone(&self) -> Self { + if let Some(ref_counts) = self.ref_counts.upgrade() { + ref_counts.lock().inc(self.view_id); + } + + Self { + window_id: self.window_id, + view_id: self.view_id, + view_type: PhantomData, + ref_counts: self.ref_counts.clone(), + } + } +} + +impl PartialEq for ViewHandle { + fn eq(&self, other: &Self) -> bool { + self.window_id == other.window_id && self.view_id == other.view_id + } +} + +impl Eq for ViewHandle {} + +impl Debug for ViewHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(&format!("ViewHandle<{}>", type_name::())) + .field("window_id", &self.window_id) + .field("view_id", &self.view_id) + .finish() + } +} + +impl Drop for ViewHandle { + fn drop(&mut self) { + if let Some(ref_counts) = self.ref_counts.upgrade() { + ref_counts.lock().dec_view(self.window_id, self.view_id); + } + } +} + +impl Handle for ViewHandle { + fn id(&self) -> usize { + self.view_id + } + + fn location(&self) -> EntityLocation { + EntityLocation::View(self.window_id, self.view_id) + } +} + +#[derive(Clone)] +pub struct AnyViewHandle { + window_id: usize, + view_id: usize, + view_type: TypeId, + ref_counts: Weak>, +} + +impl AnyViewHandle { + pub fn id(&self) -> usize { + self.view_id + } + + pub fn is(&self) -> bool { + TypeId::of::() == self.view_type + } + + pub fn downcast(self) -> Option> { + if self.is::() { + if let Some(ref_counts) = self.ref_counts.upgrade() { + return Some(ViewHandle::new(self.window_id, self.view_id, &ref_counts)); + } + } + None + } +} + +impl From<&ViewHandle> for AnyViewHandle { + fn from(handle: &ViewHandle) -> Self { + if let Some(ref_counts) = handle.ref_counts.upgrade() { + ref_counts.lock().inc(handle.view_id); + } + AnyViewHandle { + window_id: handle.window_id, + view_id: handle.view_id, + view_type: TypeId::of::(), + ref_counts: handle.ref_counts.clone(), + } + } +} + +impl From> for AnyViewHandle { + fn from(handle: ViewHandle) -> Self { + (&handle).into() + } +} + +pub struct WeakViewHandle { + window_id: usize, + view_id: usize, + view_type: PhantomData, +} + +impl WeakViewHandle { + fn new(window_id: usize, view_id: usize) -> Self { + Self { + window_id, + view_id, + view_type: PhantomData, + } + } + + pub fn upgrade(&self, app: &AppContext) -> Option> { + if app + .windows + .get(&self.window_id) + .and_then(|w| w.views.get(&self.view_id)) + .is_some() + { + Some(ViewHandle::new( + self.window_id, + self.view_id, + &app.ref_counts, + )) + } else { + None + } + } +} + +impl Clone for WeakViewHandle { + fn clone(&self) -> Self { + Self { + window_id: self.window_id, + view_id: self.view_id, + view_type: PhantomData, + } + } +} + +#[derive(Default)] +struct RefCounts { + counts: HashMap, + dropped_models: HashSet, + dropped_views: HashSet<(usize, usize)>, +} + +impl RefCounts { + fn inc(&mut self, model_id: usize) { + *self.counts.entry(model_id).or_insert(0) += 1; + } + + fn dec_model(&mut self, model_id: usize) { + if let Some(count) = self.counts.get_mut(&model_id) { + *count -= 1; + if *count == 0 { + self.counts.remove(&model_id); + self.dropped_models.insert(model_id); + } + } else { + panic!("Expected ref count to be positive") + } + } + + fn dec_view(&mut self, window_id: usize, view_id: usize) { + if let Some(count) = self.counts.get_mut(&view_id) { + *count -= 1; + if *count == 0 { + self.counts.remove(&view_id); + self.dropped_views.insert((window_id, view_id)); + } + } else { + panic!("Expected ref count to be positive") + } + } + + fn take_dropped(&mut self) -> (HashSet, HashSet<(usize, usize)>) { + let mut dropped_models = HashSet::new(); + let mut dropped_views = HashSet::new(); + std::mem::swap(&mut self.dropped_models, &mut dropped_models); + std::mem::swap(&mut self.dropped_views, &mut dropped_views); + (dropped_models, dropped_views) + } +} + +enum Subscription { + FromModel { + model_id: usize, + callback: Box, + }, + FromView { + window_id: usize, + view_id: usize, + callback: Box, + }, +} + +enum Observation { + FromModel { + model_id: usize, + callback: Box, + }, + FromView { + window_id: usize, + view_id: usize, + callback: Box, + }, +} + +enum TaskCallback { + OnModelFromFuture { + model_id: usize, + callback: Box< + dyn FnOnce( + &mut dyn Any, + Box, + &mut MutableAppContext, + usize, + Rc, + ), + >, + }, + OnModelFromStream { + model_id: usize, + callback: Box, &mut MutableAppContext, usize) -> bool>, + }, + OnViewFromFuture { + window_id: usize, + view_id: usize, + callback: Box< + dyn FnOnce( + &mut dyn AnyView, + Box, + &mut MutableAppContext, + usize, + usize, + Rc, + ), + >, + }, + OnViewFromStream { + window_id: usize, + view_id: usize, + callback: Box< + dyn FnMut(&mut dyn AnyView, Box, &mut MutableAppContext, usize, usize) -> bool, + >, + }, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::elements::*; + + #[test] + fn test_model_handles() { + struct Model { + other: Option>, + events: Vec, + } + + impl Entity for Model { + type Event = usize; + } + + impl Model { + fn new(other: Option>, ctx: &mut ModelContext) -> Self { + if let Some(other) = other.as_ref() { + ctx.observe(other, |me, _, _| { + me.events.push("notified".into()); + }); + ctx.subscribe(other, |me, event, _| { + me.events.push(format!("observed event {}", event)); + }); + } + + Self { + other, + events: Vec::new(), + } + } + } + + let mut app = App::new().unwrap(); + let app = &mut app; + + let handle_1 = app.add_model(|ctx| Model::new(None, ctx)); + let handle_2 = app.add_model(|ctx| Model::new(Some(handle_1.clone()), ctx)); + assert_eq!(app.0.borrow().ctx.models.len(), 2); + + handle_1.update(app, |model, ctx| { + model.events.push("updated".into()); + ctx.emit(1); + ctx.notify(); + ctx.emit(2); + }); + handle_1.read(app, |model, _| { + assert_eq!(model.events, vec!["updated".to_string()]); + }); + handle_2.read(app, |model, _| { + assert_eq!( + model.events, + vec![ + "observed event 1".to_string(), + "notified".to_string(), + "observed event 2".to_string(), + ] + ); + }); + + handle_2.update(app, |model, _| { + drop(handle_1); + model.other.take(); + }); + + let app_state = app.0.borrow(); + assert_eq!(app_state.ctx.models.len(), 1); + assert!(app_state.subscriptions.is_empty()); + assert!(app_state.observations.is_empty()); + } + + #[test] + fn test_subscribe_and_emit_from_model() { + #[derive(Default)] + struct Model { + events: Vec, + } + + impl Entity for Model { + type Event = usize; + } + + let mut app = App::new().unwrap(); + let app = &mut app; + + let handle_1 = app.add_model(|_| Model::default()); + let handle_2 = app.add_model(|_| Model::default()); + let handle_2b = handle_2.clone(); + + handle_1.update(app, |_, c| { + c.subscribe(&handle_2, move |model: &mut Model, event, c| { + model.events.push(*event); + + c.subscribe(&handle_2b, |model, event, _| { + model.events.push(*event * 2); + }); + }); + }); + + handle_2.update(app, |_, c| c.emit(7)); + handle_1.read(app, |model, _| assert_eq!(model.events, vec![7])); + + handle_2.update(app, |_, c| c.emit(5)); + handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5])); + } + + #[test] + fn test_observe_and_notify_from_model() { + #[derive(Default)] + struct Model { + count: usize, + events: Vec, + } + + impl Entity for Model { + type Event = (); + } + + let mut app = App::new().unwrap(); + + let app = &mut app; + let handle_1 = app.add_model(|_| Model::default()); + let handle_2 = app.add_model(|_| Model::default()); + let handle_2b = handle_2.clone(); + + handle_1.update(app, |_, c| { + c.observe(&handle_2, move |model, observed, c| { + model.events.push(observed.as_ref(c).count); + c.observe(&handle_2b, |model, observed, c| { + model.events.push(observed.as_ref(c).count * 2); + }); + }); + }); + + handle_2.update(app, |model, c| { + model.count = 7; + c.notify() + }); + handle_1.read(app, |model, _| assert_eq!(model.events, vec![7])); + + handle_2.update(app, |model, c| { + model.count = 5; + c.notify() + }); + handle_1.read(app, |model, _| assert_eq!(model.events, vec![7, 10, 5])) + } + + #[test] + fn test_spawn_from_model() { + #[derive(Default)] + struct Model { + count: usize, + } + + impl Entity for Model { + type Event = (); + } + + App::run(|mut app| async move { + let handle = app.add_model(|_| Model::default()); + handle + .update(&mut app, |_, c| { + c.spawn_local(async { 7 }, |model, output, _| { + model.count = output; + }) + }) + .await; + handle.read(&app, |model, _| assert_eq!(model.count, 7)); + + handle + .update(&mut app, |_, c| { + c.spawn(async { 14 }, |model, output, _| { + model.count = output; + }) + }) + .await; + handle.read(&app, |model, _| assert_eq!(model.count, 14)); + }); + } + + #[test] + fn test_spawn_stream_local_from_model() { + #[derive(Default)] + struct Model { + events: Vec>, + } + + impl Entity for Model { + type Event = (); + } + + App::run(|mut app| async move { + let handle = app.add_model(|_| Model::default()); + handle + .update(&mut app, |_, c| { + c.spawn_stream_local(smol::stream::iter(vec![1, 2, 3]), |model, output, _| { + model.events.push(output); + }) + }) + .await; + + handle.read(&app, |model, _| { + assert_eq!(model.events, [Some(1), Some(2), Some(3), None]) + }); + }) + } + + #[test] + fn test_view_handles() { + struct View { + other: Option>, + events: Vec, + } + + impl Entity for View { + type Event = usize; + } + + impl super::View for View { + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + impl View { + fn new(other: Option>, ctx: &mut ViewContext) -> Self { + if let Some(other) = other.as_ref() { + ctx.subscribe_to_view(other, |me, _, event, _| { + me.events.push(format!("observed event {}", event)); + }); + } + Self { + other, + events: Vec::new(), + } + } + } + + let mut app = App::new().unwrap(); + let app = &mut app; + + let (window_id, _) = app.add_window(|ctx| View::new(None, ctx)); + let handle_1 = app.add_view(window_id, |ctx| View::new(None, ctx)); + let handle_2 = app.add_view(window_id, |ctx| View::new(Some(handle_1.clone()), ctx)); + assert_eq!(app.0.borrow().ctx.windows[&window_id].views.len(), 3); + + handle_1.update(app, |view, ctx| { + view.events.push("updated".into()); + ctx.emit(1); + ctx.emit(2); + }); + handle_1.read(app, |view, _| { + assert_eq!(view.events, vec!["updated".to_string()]); + }); + handle_2.read(app, |view, _| { + assert_eq!( + view.events, + vec![ + "observed event 1".to_string(), + "observed event 2".to_string(), + ] + ); + }); + + handle_2.update(app, |view, _| { + drop(handle_1); + view.other.take(); + }); + + let app_state = app.0.borrow(); + assert_eq!(app_state.ctx.windows[&window_id].views.len(), 2); + assert!(app_state.subscriptions.is_empty()); + assert!(app_state.observations.is_empty()); + } + + #[test] + fn test_subscribe_and_emit_from_view() { + #[derive(Default)] + struct View { + events: Vec, + } + + impl Entity for View { + type Event = usize; + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + struct Model; + + impl Entity for Model { + type Event = usize; + } + + let mut app = App::new().unwrap(); + let app = &mut app; + + let (window_id, handle_1) = app.add_window(|_| View::default()); + let handle_2 = app.add_view(window_id, |_| View::default()); + let handle_2b = handle_2.clone(); + let handle_3 = app.add_model(|_| Model); + + handle_1.update(app, |_, c| { + c.subscribe_to_view(&handle_2, move |me, _, event, c| { + me.events.push(*event); + + c.subscribe_to_view(&handle_2b, |me, _, event, _| { + me.events.push(*event * 2); + }); + }); + + c.subscribe_to_model(&handle_3, |me, _, event, _| { + me.events.push(*event); + }) + }); + + handle_2.update(app, |_, c| c.emit(7)); + handle_1.read(app, |view, _| assert_eq!(view.events, vec![7])); + + handle_2.update(app, |_, c| c.emit(5)); + handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5])); + + handle_3.update(app, |_, c| c.emit(9)); + handle_1.read(app, |view, _| assert_eq!(view.events, vec![7, 10, 5, 9])); + } + + #[test] + fn test_dropping_subscribers() { + struct View; + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + struct Model; + + impl Entity for Model { + type Event = (); + } + + let mut app = App::new().unwrap(); + let app = &mut app; + + let (window_id, _) = app.add_window(|_| View); + let observing_view = app.add_view(window_id, |_| View); + let emitting_view = app.add_view(window_id, |_| View); + let observing_model = app.add_model(|_| Model); + let observed_model = app.add_model(|_| Model); + + observing_view.update(app, |_, ctx| { + ctx.subscribe_to_view(&emitting_view, |_, _, _, _| {}); + ctx.subscribe_to_model(&observed_model, |_, _, _, _| {}); + }); + observing_model.update(app, |_, ctx| { + ctx.subscribe(&observed_model, |_, _, _| {}); + }); + + app.update(|_| { + drop(observing_view); + drop(observing_model); + }); + + emitting_view.update(app, |_, ctx| ctx.emit(())); + observed_model.update(app, |_, ctx| ctx.emit(())); + } + + #[test] + fn test_observe_and_notify_from_view() { + #[derive(Default)] + struct View { + events: Vec, + } + + impl Entity for View { + type Event = usize; + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + #[derive(Default)] + struct Model { + count: usize, + } + + impl Entity for Model { + type Event = (); + } + + let mut app = App::new().unwrap(); + let app = &mut app; + let (_, view) = app.add_window(|_| View::default()); + let model = app.add_model(|_| Model::default()); + + view.update(app, |_, c| { + c.observe(&model, |me, observed, c| { + me.events.push(observed.as_ref(c).count) + }); + }); + + model.update(app, |model, c| { + model.count = 11; + c.notify(); + }); + view.read(app, |view, _| assert_eq!(view.events, vec![11])); + } + + #[test] + fn test_dropping_observers() { + struct View; + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + struct Model; + + impl Entity for Model { + type Event = (); + } + + let mut app = App::new().unwrap(); + let app = &mut app; + + let (window_id, _) = app.add_window(|_| View); + let observing_view = app.add_view(window_id, |_| View); + let observing_model = app.add_model(|_| Model); + let observed_model = app.add_model(|_| Model); + + observing_view.update(app, |_, ctx| { + ctx.observe(&observed_model, |_, _, _| {}); + }); + observing_model.update(app, |_, ctx| { + ctx.observe(&observed_model, |_, _, _| {}); + }); + + app.update(|_| { + drop(observing_view); + drop(observing_model); + }); + + observed_model.update(app, |_, ctx| ctx.notify()); + } + + #[test] + fn test_focus() { + #[derive(Default)] + struct View { + events: Vec, + } + + impl Entity for View { + type Event = String; + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + + fn on_focus(&mut self, ctx: &mut ViewContext) { + self.events.push("self focused".into()); + ctx.emit("focused".into()); + } + + fn on_blur(&mut self, ctx: &mut ViewContext) { + self.events.push("self blurred".into()); + ctx.emit("blurred".into()); + } + } + + let mut app = App::new().unwrap(); + let app = &mut app; + let (window_id, view_1) = app.add_window(|_| View::default()); + let view_2 = app.add_view(window_id, |_| View::default()); + + view_1.update(app, |_, ctx| { + ctx.subscribe_to_view(&view_2, |view_1, _, event, _| { + view_1.events.push(format!("view 2 {}", event)); + }); + ctx.focus(&view_2); + }); + + view_1.update(app, |_, ctx| { + ctx.focus(&view_1); + }); + + view_1.read(app, |view_1, _| { + assert_eq!( + view_1.events, + [ + "self focused".to_string(), + "self blurred".to_string(), + "view 2 focused".to_string(), + "self focused".to_string(), + "view 2 blurred".to_string(), + ], + ); + }); + } + + #[test] + fn test_spawn_from_view() { + #[derive(Default)] + struct View { + count: usize, + } + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + App::run(|mut app| async move { + let (_, handle) = app.add_window(|_| View::default()); + handle + .update(&mut app, |_, c| { + c.spawn_local(async { 7 }, |me, output, _| { + me.count = output; + }) + }) + .await; + handle.read(&app, |view, _| assert_eq!(view.count, 7)); + handle + .update(&mut app, |_, c| { + c.spawn(async { 14 }, |me, output, _| { + me.count = output; + }) + }) + .await; + handle.read(&app, |view, _| assert_eq!(view.count, 14)); + }); + } + + #[test] + fn test_spawn_stream_local_from_view() { + #[derive(Default)] + struct View { + events: Vec>, + } + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + App::run(|mut app| async move { + let (_, handle) = app.add_window(|_| View::default()); + handle + .update(&mut app, |_, c| { + c.spawn_stream_local(stream::iter(vec![1, 2, 3]), |me, output, _| { + me.events.push(output); + }) + }) + .await; + + handle.read(&app, |view, _| { + assert_eq!(view.events, [Some(1), Some(2), Some(3), None]) + }); + }); + } + + #[test] + fn test_dispatch_action() { + struct ViewA { + id: usize, + } + + impl Entity for ViewA { + type Event = (); + } + + impl View for ViewA { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + struct ViewB { + id: usize, + } + + impl Entity for ViewB { + type Event = (); + } + + impl View for ViewB { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + struct ActionArg { + foo: String, + } + + let mut app = App::new().unwrap(); + let actions = Rc::new(RefCell::new(Vec::new())); + + let actions_clone = actions.clone(); + app.add_global_action("action", move |_: &ActionArg, _: &mut MutableAppContext| { + actions_clone.borrow_mut().push("global a".to_string()); + }); + + let actions_clone = actions.clone(); + app.add_global_action("action", move |_: &ActionArg, _: &mut MutableAppContext| { + actions_clone.borrow_mut().push("global b".to_string()); + }); + + let actions_clone = actions.clone(); + app.add_action("action", move |view: &mut ViewA, arg: &ActionArg, ctx| { + assert_eq!(arg.foo, "bar"); + ctx.propagate_action(); + actions_clone.borrow_mut().push(format!("{} a", view.id)); + }); + + let actions_clone = actions.clone(); + app.add_action("action", move |view: &mut ViewA, _: &ActionArg, ctx| { + if view.id != 1 { + ctx.propagate_action(); + } + actions_clone.borrow_mut().push(format!("{} b", view.id)); + }); + + let actions_clone = actions.clone(); + app.add_action("action", move |view: &mut ViewB, _: &ActionArg, ctx| { + ctx.propagate_action(); + actions_clone.borrow_mut().push(format!("{} c", view.id)); + }); + + let actions_clone = actions.clone(); + app.add_action("action", move |view: &mut ViewB, _: &ActionArg, ctx| { + ctx.propagate_action(); + actions_clone.borrow_mut().push(format!("{} d", view.id)); + }); + + let (window_id, view_1) = app.add_window(|_| ViewA { id: 1 }); + let view_2 = app.add_view(window_id, |_| ViewB { id: 2 }); + let view_3 = app.add_view(window_id, |_| ViewA { id: 3 }); + let view_4 = app.add_view(window_id, |_| ViewB { id: 4 }); + + app.dispatch_action( + window_id, + vec![view_1.id(), view_2.id(), view_3.id(), view_4.id()], + "action", + ActionArg { foo: "bar".into() }, + ); + + assert_eq!( + *actions.borrow(), + vec!["4 d", "4 c", "3 b", "3 a", "2 d", "2 c", "1 b"] + ); + + // Remove view_1, which doesn't propagate the action + actions.borrow_mut().clear(); + app.dispatch_action( + window_id, + vec![view_2.id(), view_3.id(), view_4.id()], + "action", + ActionArg { foo: "bar".into() }, + ); + + assert_eq!( + *actions.borrow(), + vec!["4 d", "4 c", "3 b", "3 a", "2 d", "2 c", "global b", "global a"] + ); + } + + #[test] + fn test_dispatch_keystroke() -> Result<()> { + use std::cell::Cell; + + #[derive(Clone)] + struct ActionArg { + key: String, + } + + struct View { + id: usize, + keymap_context: keymap::Context, + } + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + + fn keymap_context(&self, _: &AppContext) -> keymap::Context { + self.keymap_context.clone() + } + } + + impl View { + fn new(id: usize) -> Self { + View { + id, + keymap_context: keymap::Context::default(), + } + } + } + + let mut app = App::new().unwrap(); + + let mut view_1 = View::new(1); + let mut view_2 = View::new(2); + let mut view_3 = View::new(3); + view_1.keymap_context.set.insert("a".into()); + view_2.keymap_context.set.insert("b".into()); + view_3.keymap_context.set.insert("c".into()); + + let (window_id, view_1) = app.add_window(|_| view_1); + let view_2 = app.add_view(window_id, |_| view_2); + let view_3 = app.add_view(window_id, |_| view_3); + + // This keymap's only binding dispatches an action on view 2 because that view will have + // "a" and "b" in its context, but not "c". + let binding = keymap::Binding::new("a", "action", Some("a && b && !c")) + .with_arg(ActionArg { key: "a".into() }); + app.add_bindings(vec![binding]); + + let handled_action = Rc::new(Cell::new(false)); + let handled_action_clone = handled_action.clone(); + app.add_action("action", move |view: &mut View, arg: &ActionArg, _ctx| { + handled_action_clone.set(true); + assert_eq!(view.id, 2); + assert_eq!(arg.key, "a"); + }); + + app.dispatch_keystroke( + window_id, + vec![view_1.id(), view_2.id(), view_3.id()], + &Keystroke::parse("a")?, + )?; + + assert!(handled_action.get()); + Ok(()) + } + + #[test] + fn test_ui_and_window_updates() { + struct View { + count: usize, + } + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + App::run(|mut app| async move { + let (window_id, _) = app.add_window(|_| View { count: 3 }); + let view_1 = app.add_view(window_id, |_| View { count: 1 }); + let view_2 = app.add_view(window_id, |_| View { count: 2 }); + + // Ensure that registering for UI updates after mutating the app still gives us all the + // updates. + let ui_updates = Rc::new(RefCell::new(Vec::new())); + let ui_updates_ = ui_updates.clone(); + app.on_ui_update(move |update, _| ui_updates_.borrow_mut().push(update)); + + assert_eq!( + ui_updates.borrow_mut().drain(..).collect::>(), + vec![UiUpdate::OpenWindow { + window_id, + width: 1024.0, + height: 768.0, + }] + ); + + let window_invalidations = Rc::new(RefCell::new(Vec::new())); + let window_invalidations_ = window_invalidations.clone(); + app.on_window_invalidated(window_id, move |update, _| { + window_invalidations_.borrow_mut().push(update) + }); + + let view_2_id = view_2.id(); + view_1.update(&mut app, |view, ctx| { + view.count = 7; + ctx.notify(); + drop(view_2); + }); + + let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap(); + assert_eq!(invalidation.updated.len(), 1); + assert!(invalidation.updated.contains(&view_1.id())); + assert_eq!(invalidation.removed, vec![view_2_id]); + + let view_3 = view_1.update(&mut app, |_, ctx| ctx.add_view(|_| View { count: 8 })); + + let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap(); + assert_eq!(invalidation.updated.len(), 1); + assert!(invalidation.updated.contains(&view_3.id())); + assert!(invalidation.removed.is_empty()); + + view_3 + .update(&mut app, |_, ctx| { + ctx.spawn_local(async { 9 }, |me, output, ctx| { + me.count = output; + ctx.notify(); + }) + }) + .await; + + let invalidation = window_invalidations.borrow_mut().drain(..).next().unwrap(); + assert_eq!(invalidation.updated.len(), 1); + assert!(invalidation.updated.contains(&view_3.id())); + assert!(invalidation.removed.is_empty()); + }); + } + + #[test] + fn test_finish_pending_tasks() { + struct View; + + impl Entity for View { + type Event = (); + } + + impl super::View for View { + fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { + Empty::new().finish(bump) + } + + fn ui_name() -> &'static str { + "View" + } + } + + struct Model; + + impl Entity for Model { + type Event = (); + } + + App::run(|mut app| async move { + let model = app.add_model(|_| Model); + let (_, view) = app.add_window(|_| View); + + model.update(&mut app, |_, ctx| { + let _ = ctx.spawn(async {}, |_, _, _| {}); + let _ = ctx.spawn_local(async {}, |_, _, _| {}); + let _ = ctx.spawn_stream_local(futures::stream::iter(vec![1, 2, 3]), |_, _, _| {}); + }); + + view.update(&mut app, |_, ctx| { + let _ = ctx.spawn(async {}, |_, _, _| {}); + let _ = ctx.spawn_local(async {}, |_, _, _| {}); + let _ = ctx.spawn_stream_local(futures::stream::iter(vec![1, 2, 3]), |_, _, _| {}); + }); + + assert!(!app.0.borrow().task_callbacks.is_empty()); + app.finish_pending_tasks().await; + assert!(app.0.borrow().task_callbacks.is_empty()); + app.finish_pending_tasks().await; // Don't block if there are no tasks + }); + } +} diff --git a/gpui/src/elements/align.rs b/gpui/src/elements/align.rs new file mode 100644 index 0000000000..3f9189c967 --- /dev/null +++ b/gpui/src/elements/align.rs @@ -0,0 +1,68 @@ +use crate::{ + AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, + PaintContext, SizeConstraint, +}; +use pathfinder_geometry::vector::{vec2f, Vector2F}; + +pub struct Align { + child: Box, + alignment: Vector2F, + size: Option, +} + +impl Align { + pub fn new(child: Box) -> Self { + Self { + child, + alignment: Vector2F::zero(), + size: None, + } + } + + pub fn top_center(mut self) -> Self { + self.alignment = vec2f(0.0, -1.0); + self + } +} + +impl Element for Align { + fn layout( + &mut self, + mut constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + let mut size = constraint.max; + constraint.min = Vector2F::zero(); + let child_size = self.child.layout(constraint, ctx, app); + if size.x().is_infinite() { + size.set_x(child_size.x()); + } + if size.y().is_infinite() { + size.set_y(child_size.y()); + } + self.size = Some(size); + size + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + self.child.after_layout(ctx, app); + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + let self_center = self.size.unwrap() / 2.0; + let self_target = self_center + self_center * self.alignment; + let child_center = self.child.size().unwrap() / 2.0; + let child_target = child_center + child_center * self.alignment; + let origin = origin - (child_target - self_target); + self.child.paint(origin, ctx, app); + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + self.child.dispatch_event(event, ctx, app) + } + + fn size(&self) -> Option { + self.size + } +} diff --git a/gpui/src/elements/constrained_box.rs b/gpui/src/elements/constrained_box.rs new file mode 100644 index 0000000000..a4a06598d8 --- /dev/null +++ b/gpui/src/elements/constrained_box.rs @@ -0,0 +1,67 @@ +use crate::{ + AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, + PaintContext, SizeConstraint, +}; +use pathfinder_geometry::vector::Vector2F; + +pub struct ConstrainedBox { + child: Box, + constraint: SizeConstraint, +} + +impl ConstrainedBox { + pub fn new(child: Box) -> Self { + Self { + child, + constraint: SizeConstraint { + min: Vector2F::zero(), + max: Vector2F::splat(f32::INFINITY), + }, + } + } + + pub fn with_max_width(mut self, max_width: f32) -> Self { + self.constraint.max.set_x(max_width); + self + } + + pub fn with_max_height(mut self, max_height: f32) -> Self { + self.constraint.max.set_y(max_height); + self + } + + pub fn with_height(mut self, height: f32) -> Self { + self.constraint.min.set_y(height); + self.constraint.max.set_y(height); + self + } +} + +impl Element for ConstrainedBox { + fn layout( + &mut self, + mut constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + constraint.min = constraint.min.max(self.constraint.min); + constraint.max = constraint.max.min(self.constraint.max); + self.child.layout(constraint, ctx, app) + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + self.child.after_layout(ctx, app); + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + self.child.paint(origin, ctx, app); + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + self.child.dispatch_event(event, ctx, app) + } + + fn size(&self) -> Option { + self.child.size() + } +} diff --git a/gpui/src/elements/container.rs b/gpui/src/elements/container.rs new file mode 100644 index 0000000000..31cbdff1c8 --- /dev/null +++ b/gpui/src/elements/container.rs @@ -0,0 +1,358 @@ +use crate::{ + color::ColorU, + geometry::vector::{vec2f, Vector2F}, + AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, + PaintContext, SizeConstraint, +}; + +pub struct Container { + margin: Margin, + padding: Padding, + overdraw: Overdraw, + background_color: Option, + border: Border, + corner_radius: f32, + shadow: Option, + child: Box, + size: Option, + origin: Option, +} + +impl Container { + pub fn new(child: Box) -> Self { + Self { + margin: Margin::default(), + padding: Padding::default(), + overdraw: Overdraw::default(), + background_color: None, + border: Border::default(), + corner_radius: 0.0, + shadow: None, + child, + size: None, + origin: None, + } + } + + pub fn with_margin_top(mut self, margin: f32) -> Self { + self.margin.top = margin; + self + } + + pub fn with_uniform_padding(mut self, padding: f32) -> Self { + self.padding = Padding { + top: padding, + left: padding, + bottom: padding, + right: padding, + }; + self + } + + pub fn with_padding_right(mut self, padding: f32) -> Self { + self.padding.right = padding; + self + } + + pub fn with_background_color(mut self, color: impl Into) -> Self { + self.background_color = Some(color.into()); + self + } + + pub fn with_border(mut self, border: Border) -> Self { + self.border = border; + self + } + + pub fn with_overdraw_bottom(mut self, overdraw: f32) -> Self { + self.overdraw.bottom = overdraw; + self + } + + pub fn with_corner_radius(mut self, radius: f32) -> Self { + self.corner_radius = radius; + self + } + + pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: impl Into) -> Self { + self.shadow = Some(Shadow { + offset, + blur, + color: color.into(), + }); + self + } + + fn margin_size(&self) -> Vector2F { + vec2f( + self.margin.left + self.margin.right, + self.margin.top + self.margin.bottom, + ) + } + + fn padding_size(&self) -> Vector2F { + vec2f( + self.padding.left + self.padding.right, + self.padding.top + self.padding.bottom, + ) + } + + fn border_size(&self) -> Vector2F { + let mut x = 0.0; + if self.border.left { + x += self.border.width; + } + if self.border.right { + x += self.border.width; + } + + let mut y = 0.0; + if self.border.top { + y += self.border.width; + } + if self.border.bottom { + y += self.border.width; + } + + vec2f(x, y) + } +} + +impl Element for Container { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + let size_buffer = self.margin_size() + self.padding_size() + self.border_size(); + let child_constraint = SizeConstraint { + min: (constraint.min - size_buffer).max(Vector2F::zero()), + max: (constraint.max - size_buffer).max(Vector2F::zero()), + }; + let child_size = self.child.layout(child_constraint, ctx, app); + let size = child_size + size_buffer; + self.size = Some(size); + size + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + self.child.after_layout(ctx, app); + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + // self.origin = Some(origin); + + // let canvas = &mut ctx.canvas; + // let size = self.size.unwrap() - self.margin_size() + // + vec2f(self.overdraw.right, self.overdraw.bottom); + // let origin = origin + vec2f(self.margin.left, self.margin.top) + // - vec2f(self.overdraw.left, self.overdraw.top); + // let rect = RectF::new(origin, size); + + // let mut path = Path2D::new(); + // if self.corner_radius > 0.0 { + // path.move_to(rect.upper_right() - vec2f(self.corner_radius, 0.0)); + // path.arc_to( + // rect.upper_right(), + // rect.upper_right() + vec2f(0.0, self.corner_radius), + // self.corner_radius, + // ); + // path.line_to(rect.lower_right() - vec2f(0.0, self.corner_radius)); + // path.arc_to( + // rect.lower_right(), + // rect.lower_right() - vec2f(self.corner_radius, 0.0), + // self.corner_radius, + // ); + // path.line_to(rect.lower_left() + vec2f(self.corner_radius, 0.0)); + // path.arc_to( + // rect.lower_left(), + // rect.lower_left() - vec2f(0.0, self.corner_radius), + // self.corner_radius, + // ); + // path.line_to(origin + vec2f(0.0, self.corner_radius)); + // path.arc_to( + // origin, + // origin + vec2f(self.corner_radius, 0.0), + // self.corner_radius, + // ); + // path.close_path(); + // } else { + // path.rect(rect); + // } + + // canvas.save(); + // if let Some(shadow) = self.shadow.as_ref() { + // canvas.set_shadow_offset(shadow.offset); + // canvas.set_shadow_blur(shadow.blur); + // canvas.set_shadow_color(shadow.color); + // } + + // if let Some(background_color) = self.background_color { + // canvas.set_fill_style(FillStyle::Color(background_color)); + // canvas.fill_path(path.clone(), FillRule::Winding); + // } + + // canvas.set_line_width(self.border.width); + // canvas.set_stroke_style(FillStyle::Color(self.border.color)); + + // let border_rect = rect.contract(self.border.width / 2.0); + + // // For now, we ignore the corner radius unless we draw a border on all sides. + // // This could be improved. + // if self.border.all_sides() { + // let mut path = Path2D::new(); + // path.rect(border_rect); + // canvas.stroke_path(path); + // } else { + // canvas.set_line_cap(LineCap::Square); + + // if self.border.top { + // let mut path = Path2D::new(); + // path.move_to(border_rect.origin()); + // path.line_to(border_rect.upper_right()); + // canvas.stroke_path(path); + // } + + // if self.border.left { + // let mut path = Path2D::new(); + // path.move_to(border_rect.origin()); + // path.line_to(border_rect.lower_left()); + // canvas.stroke_path(path); + // } + + // if self.border.bottom { + // let mut path = Path2D::new(); + // path.move_to(border_rect.lower_left()); + // path.line_to(border_rect.lower_right()); + // canvas.stroke_path(path); + // } + + // if self.border.right { + // let mut path = Path2D::new(); + // path.move_to(border_rect.upper_right()); + // path.line_to(border_rect.lower_right()); + // canvas.stroke_path(path); + // } + // } + // canvas.restore(); + + // let mut child_origin = origin + vec2f(self.padding.left, self.padding.top); + // if self.border.left { + // child_origin.set_x(child_origin.x() + self.border.width); + // } + // if self.border.top { + // child_origin.set_y(child_origin.y() + self.border.width); + // } + // self.child.paint(child_origin, ctx, app); + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + self.child.dispatch_event(event, ctx, app) + } + + fn size(&self) -> Option { + self.size + } +} + +#[derive(Default)] +pub struct Margin { + top: f32, + left: f32, + bottom: f32, + right: f32, +} + +#[derive(Default)] +pub struct Padding { + top: f32, + left: f32, + bottom: f32, + right: f32, +} + +#[derive(Default)] +pub struct Overdraw { + top: f32, + left: f32, + bottom: f32, + right: f32, +} + +#[derive(Default)] +pub struct Border { + width: f32, + color: ColorU, + pub top: bool, + pub left: bool, + pub bottom: bool, + pub right: bool, +} + +impl Border { + pub fn new(width: f32, color: impl Into) -> Self { + Self { + width, + color: color.into(), + top: false, + left: false, + bottom: false, + right: false, + } + } + + pub fn all(width: f32, color: impl Into) -> Self { + Self { + width, + color: color.into(), + top: true, + left: true, + bottom: true, + right: true, + } + } + + pub fn top(width: f32, color: impl Into) -> Self { + let mut border = Self::new(width, color); + border.top = true; + border + } + + pub fn left(width: f32, color: impl Into) -> Self { + let mut border = Self::new(width, color); + border.left = true; + border + } + + pub fn bottom(width: f32, color: impl Into) -> Self { + let mut border = Self::new(width, color); + border.bottom = true; + border + } + + pub fn right(width: f32, color: impl Into) -> Self { + let mut border = Self::new(width, color); + border.right = true; + border + } + + pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self { + self.top = top; + self.left = left; + self.bottom = bottom; + self.right = right; + self + } + + fn all_sides(&self) -> bool { + self.top && self.left && self.bottom && self.right + } +} + +#[derive(Default)] +pub struct Shadow { + offset: Vector2F, + blur: f32, + color: ColorU, +} diff --git a/gpui/src/elements/empty.rs b/gpui/src/elements/empty.rs new file mode 100644 index 0000000000..493968167c --- /dev/null +++ b/gpui/src/elements/empty.rs @@ -0,0 +1,45 @@ +use crate::{ + AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, + PaintContext, SizeConstraint, +}; +use pathfinder_geometry::vector::Vector2F; + +pub struct Empty { + size: Option, + origin: Option, +} + +impl Empty { + pub fn new() -> Self { + Self { + size: None, + origin: None, + } + } +} + +impl Element for Empty { + fn layout( + &mut self, + constraint: SizeConstraint, + _: &mut LayoutContext, + _: &AppContext, + ) -> Vector2F { + self.size = Some(constraint.min); + constraint.max + } + + fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {} + + fn paint(&mut self, origin: Vector2F, _: &mut PaintContext, _: &AppContext) { + self.origin = Some(origin); + } + + fn dispatch_event(&self, _: &Event, _: &mut EventContext, _: &AppContext) -> bool { + false + } + + fn size(&self) -> Option { + self.size + } +} diff --git a/gpui/src/elements/event_handler.rs b/gpui/src/elements/event_handler.rs new file mode 100644 index 0000000000..eed03ca54b --- /dev/null +++ b/gpui/src/elements/event_handler.rs @@ -0,0 +1,69 @@ +use super::try_rect; +use crate::{ + geometry::vector::Vector2F, AfterLayoutContext, AppContext, Element, Event, EventContext, + LayoutContext, MutableAppContext, PaintContext, SizeConstraint, +}; +use std::cell::RefCell; + +pub struct EventHandler { + child: Box, + mouse_down: Option bool>>>, + origin: Option, +} + +impl EventHandler { + pub fn new(child: Box) -> Self { + Self { + child, + mouse_down: None, + origin: None, + } + } + + pub fn on_mouse_down(mut self, callback: F) -> Self + where + F: 'static + FnMut(&mut EventContext, &AppContext) -> bool, + { + self.mouse_down = Some(RefCell::new(Box::new(callback))); + self + } +} + +impl Element for EventHandler { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + self.child.layout(constraint, ctx, app) + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + self.child.after_layout(ctx, app); + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + self.origin = Some(origin); + self.child.paint(origin, ctx, app); + } + + fn size(&self) -> Option { + self.child.size() + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + match event { + Event::LeftMouseDown { position, .. } => { + if let Some(callback) = self.mouse_down.as_ref() { + let rect = try_rect(self.origin, self.size()).unwrap(); + if rect.contains_point(*position) { + return callback.borrow_mut()(ctx, app); + } + } + false + } + _ => false, + } + } +} diff --git a/gpui/src/elements/flex.rs b/gpui/src/elements/flex.rs new file mode 100644 index 0000000000..d2eab3483b --- /dev/null +++ b/gpui/src/elements/flex.rs @@ -0,0 +1,201 @@ +use crate::{ + AfterLayoutContext, AppContext, Axis, Element, Event, EventContext, LayoutContext, + MutableAppContext, PaintContext, SizeConstraint, Vector2FExt, +}; +use pathfinder_geometry::vector::{vec2f, Vector2F}; +use std::any::Any; + +pub struct Flex { + axis: Axis, + children: Vec>, + size: Option, + origin: Option, +} + +impl Flex { + pub fn new(axis: Axis) -> Self { + Self { + axis, + children: Default::default(), + size: None, + origin: None, + } + } + + pub fn row() -> Self { + Self::new(Axis::Horizontal) + } + + pub fn column() -> Self { + Self::new(Axis::Vertical) + } + + fn child_flex<'b>(child: &dyn Element) -> Option { + child + .parent_data() + .and_then(|d| d.downcast_ref::()) + .map(|data| data.flex) + } +} + +impl Extend> for Flex { + fn extend>>(&mut self, children: T) { + self.children.extend(children); + } +} + +impl Element for Flex { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + let mut total_flex = 0.0; + let mut fixed_space = 0.0; + + let cross_axis = self.axis.invert(); + let mut cross_axis_max: f32 = 0.0; + for child in &mut self.children { + if let Some(flex) = Self::child_flex(child.as_ref()) { + total_flex += flex; + } else { + let child_constraint = + SizeConstraint::strict_along(cross_axis, constraint.max_along(cross_axis)); + let size = child.layout(child_constraint, ctx, app); + fixed_space += size.along(self.axis); + cross_axis_max = cross_axis_max.max(size.along(cross_axis)); + } + } + + let mut size = if total_flex > 0.0 { + if constraint.max_along(self.axis).is_infinite() { + panic!("flex contains flexible children but has an infinite constraint along the flex axis"); + } + + let mut remaining_space = constraint.max_along(self.axis) - fixed_space; + let mut remaining_flex = total_flex; + for child in &mut self.children { + let space_per_flex = remaining_space / remaining_flex; + if let Some(flex) = Self::child_flex(child.as_ref()) { + let child_max = space_per_flex * flex; + let child_constraint = match self.axis { + Axis::Horizontal => SizeConstraint::new( + vec2f(0.0, constraint.max.y()), + vec2f(child_max, constraint.max.y()), + ), + Axis::Vertical => SizeConstraint::new( + vec2f(constraint.max.x(), 0.0), + vec2f(constraint.max.x(), child_max), + ), + }; + let child_size = child.layout(child_constraint, ctx, app); + remaining_space -= child_size.along(self.axis); + remaining_flex -= flex; + cross_axis_max = cross_axis_max.max(child_size.along(cross_axis)); + } + } + + match self.axis { + Axis::Horizontal => vec2f(constraint.max.x() - remaining_space, cross_axis_max), + Axis::Vertical => vec2f(cross_axis_max, constraint.max.y() - remaining_space), + } + } else { + match self.axis { + Axis::Horizontal => vec2f(fixed_space, cross_axis_max), + Axis::Vertical => vec2f(cross_axis_max, fixed_space), + } + }; + + if constraint.min.x().is_finite() { + size.set_x(size.x().max(constraint.min.x())); + } + if constraint.min.y().is_finite() { + size.set_y(size.y().max(constraint.min.y())); + } + + self.size = Some(size); + size + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + for child in &mut self.children { + child.after_layout(ctx, app); + } + } + + fn paint(&mut self, mut origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + self.origin = Some(origin); + + for child in &mut self.children { + child.paint(origin, ctx, app); + match self.axis { + Axis::Horizontal => origin += vec2f(child.size().unwrap().x(), 0.0), + Axis::Vertical => origin += vec2f(0.0, child.size().unwrap().y()), + } + } + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + let mut handled = false; + for child in &self.children { + if child.dispatch_event(event, ctx, app) { + handled = true; + } + } + handled + } + + fn size(&self) -> Option { + self.size + } +} + +struct FlexParentData { + flex: f32, +} + +pub struct Expanded { + parent_data: FlexParentData, + child: Box, +} + +impl Expanded { + pub fn new(flex: f32, child: Box) -> Self { + Expanded { + parent_data: FlexParentData { flex }, + child, + } + } +} + +impl Element for Expanded { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + self.child.layout(constraint, ctx, app) + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + self.child.after_layout(ctx, app); + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + self.child.paint(origin, ctx, app); + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + self.child.dispatch_event(event, ctx, app) + } + + fn size(&self) -> Option { + self.child.size() + } + + fn parent_data(&self) -> Option<&dyn Any> { + Some(&self.parent_data) + } +} diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs new file mode 100644 index 0000000000..4756d0cb5c --- /dev/null +++ b/gpui/src/elements/label.rs @@ -0,0 +1,154 @@ +use crate::{ + color::ColorU, + fonts::{FamilyId, Properties}, + geometry::vector::{vec2f, Vector2F}, + AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, + PaintContext, SizeConstraint, +}; +use std::{ops::Range, sync::Arc}; + +pub struct Label { + text: String, + family_id: FamilyId, + font_properties: Properties, + font_size: f32, + highlights: Option, + layout_line: Option>, + colors: Option, ColorU)>>, + size: Option, +} + +pub struct Highlights { + color: ColorU, + indices: Vec, + font_properties: Properties, +} + +impl Label { + pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self { + Self { + text, + family_id, + font_properties: Properties::new(), + font_size, + highlights: None, + layout_line: None, + colors: None, + size: None, + } + } + + pub fn with_highlights( + mut self, + color: ColorU, + font_properties: Properties, + indices: Vec, + ) -> Self { + self.highlights = Some(Highlights { + color, + font_properties, + indices, + }); + self + } +} + +impl Element for Label { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + _: &AppContext, + ) -> Vector2F { + let font_id = ctx + .font_cache + .select_font(self.family_id, &self.font_properties) + .unwrap(); + let text_len = self.text.chars().count(); + let mut styles; + let mut colors; + if let Some(highlights) = self.highlights.as_ref() { + styles = Vec::new(); + colors = Vec::new(); + let highlight_font_id = ctx + .font_cache + .select_font(self.family_id, &highlights.font_properties) + .unwrap_or(font_id); + let mut pending_highlight: Option> = None; + for ix in &highlights.indices { + if let Some(pending_highlight) = pending_highlight.as_mut() { + if *ix == pending_highlight.end { + pending_highlight.end += 1; + } else { + styles.push((pending_highlight.clone(), highlight_font_id)); + colors.push((pending_highlight.clone(), highlights.color)); + styles.push((pending_highlight.end..*ix, font_id)); + colors.push((pending_highlight.end..*ix, ColorU::black())); + *pending_highlight = *ix..*ix + 1; + } + } else { + styles.push((0..*ix, font_id)); + colors.push((0..*ix, ColorU::black())); + pending_highlight = Some(*ix..*ix + 1); + } + } + if let Some(pending_highlight) = pending_highlight.as_mut() { + styles.push((pending_highlight.clone(), highlight_font_id)); + colors.push((pending_highlight.clone(), highlights.color)); + if text_len > pending_highlight.end { + styles.push((pending_highlight.end..text_len, font_id)); + colors.push((pending_highlight.end..text_len, ColorU::black())); + } + } else { + styles.push((0..text_len, font_id)); + colors.push((0..text_len, ColorU::black())); + } + } else { + styles = vec![(0..text_len, font_id)]; + colors = vec![(0..text_len, ColorU::black())]; + } + + self.colors = Some(colors); + + let layout_line = ctx.text_layout_cache.layout_str( + self.text.as_str(), + self.font_size, + styles.as_slice(), + ctx.font_cache, + ); + + let size = vec2f( + layout_line + .width + .max(constraint.min.x()) + .min(constraint.max.x()), + ctx.font_cache.line_height(font_id, self.font_size).ceil(), + ); + + self.layout_line = Some(layout_line); + self.size = Some(size); + + size + } + + fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {} + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, _: &AppContext) { + // ctx.canvas.set_fill_style(FillStyle::Color(ColorU::black())); + // self.layout_line.as_ref().unwrap().paint( + // origin, + // RectF::new(origin, self.size.unwrap()), + // self.colors.as_ref().unwrap(), + // ctx.canvas, + // ctx.font_cache, + // ); + } + + fn size(&self) -> Option { + self.size + } + + fn dispatch_event(&self, _: &Event, _: &mut EventContext, _: &AppContext) -> bool { + false + } +} diff --git a/gpui/src/elements/line_box.rs b/gpui/src/elements/line_box.rs new file mode 100644 index 0000000000..22b6349975 --- /dev/null +++ b/gpui/src/elements/line_box.rs @@ -0,0 +1,84 @@ +use super::{AppContext, Element, MutableAppContext}; +use crate::{ + fonts::{FamilyId, FontId, Properties}, + geometry::vector::{vec2f, Vector2F}, + AfterLayoutContext, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, +}; + +pub struct LineBox { + child: Box, + family_id: FamilyId, + font_size: f32, + font_properties: Properties, + font_id: Option, + size: Option, +} + +impl LineBox { + pub fn new(family_id: FamilyId, font_size: f32, child: Box) -> Self { + Self { + child, + family_id, + font_size, + font_properties: Properties::default(), + font_id: None, + size: None, + } + } +} + +impl Element for LineBox { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + match ctx + .font_cache + .select_font(self.family_id, &self.font_properties) + { + Ok(font_id) => { + self.font_id = Some(font_id); + let line_height = ctx.font_cache.bounding_box(font_id, self.font_size).y(); + let child_max = vec2f( + constraint.max.x(), + ctx.font_cache.ascent(font_id, self.font_size) + - ctx.font_cache.descent(font_id, self.font_size), + ); + let child_size = self.child.layout( + SizeConstraint::new(constraint.min.min(child_max), child_max), + ctx, + app, + ); + let size = vec2f(child_size.x(), line_height); + self.size = Some(size); + size + } + Err(error) => { + log::error!("can't layout LineBox: {}", error); + self.size = Some(constraint.min); + constraint.min + } + } + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + self.child.after_layout(ctx, app); + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + if let Some(font_id) = self.font_id { + let descent = ctx.font_cache.descent(font_id, self.font_size); + self.child.paint(origin + vec2f(0.0, -descent), ctx, app); + } + } + + fn size(&self) -> Option { + self.size + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + self.child.dispatch_event(event, ctx, app) + } +} diff --git a/gpui/src/elements/mod.rs b/gpui/src/elements/mod.rs new file mode 100644 index 0000000000..09b171ec96 --- /dev/null +++ b/gpui/src/elements/mod.rs @@ -0,0 +1,80 @@ +mod align; +mod constrained_box; +mod container; +mod empty; +mod event_handler; +mod flex; +mod label; +mod line_box; +mod stack; +mod svg; +mod uniform_list; + +pub use align::*; +pub use constrained_box::*; +pub use container::*; +pub use empty::*; +pub use event_handler::*; +pub use flex::*; +pub use label::*; +pub use line_box::*; +pub use stack::*; +pub use svg::*; +pub use uniform_list::*; + +use crate::{ + AfterLayoutContext, AppContext, Event, EventContext, LayoutContext, MutableAppContext, + PaintContext, SizeConstraint, +}; +use pathfinder_geometry::{rect::RectF, vector::Vector2F}; +use std::any::Any; + +pub trait Element { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F; + + fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {} + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext); + + fn size(&self) -> Option; + + fn parent_data(&self) -> Option<&dyn Any> { + None + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool; + + fn boxed(self) -> Box { + Box::new(self) + } +} + +pub trait ParentElement<'a>: Extend> + Sized { + fn add_children(&mut self, children: impl IntoIterator>) { + self.extend(children); + } + + fn add_child(&mut self, child: Box) { + self.add_childen(Some(child)); + } + + fn with_children(mut self, children: impl IntoIterator>) -> Self { + self.add_children(children); + self + } + + fn with_child(self, child: Box) -> Self { + self.with_children(Some(child)) + } +} + +impl<'a, T> ParentElement<'a> for T where T: Extend> {} + +pub fn try_rect(origin: Option, size: Option) -> Option { + origin.and_then(|origin| size.map(|size| RectF::new(origin, size))) +} diff --git a/gpui/src/elements/stack.rs b/gpui/src/elements/stack.rs new file mode 100644 index 0000000000..20f5f1e890 --- /dev/null +++ b/gpui/src/elements/stack.rs @@ -0,0 +1,65 @@ +use crate::{ + geometry::vector::Vector2F, AfterLayoutContext, AppContext, Element, Event, EventContext, + LayoutContext, MutableAppContext, PaintContext, SizeConstraint, +}; + +pub struct Stack { + children: Vec>, + size: Option, +} + +impl Stack { + pub fn new() -> Self { + Stack { + children: Vec::new(), + size: None, + } + } +} + +impl Element for Stack { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + let mut size = constraint.min; + for child in &mut self.children { + size = size.max(child.layout(constraint, ctx, app)); + } + self.size = Some(size); + size + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + for child in &mut self.children { + child.after_layout(ctx, app); + } + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + for child in &mut self.children { + child.paint(origin, ctx, app); + } + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + for child in self.children.iter().rev() { + if child.dispatch_event(event, ctx, app) { + return true; + } + } + false + } + + fn size(&self) -> Option { + self.size + } +} + +impl Extend> for Stack { + fn extend>>(&mut self, children: T) { + self.children.extend(children) + } +} diff --git a/gpui/src/elements/svg.rs b/gpui/src/elements/svg.rs new file mode 100644 index 0000000000..8ea3903944 --- /dev/null +++ b/gpui/src/elements/svg.rs @@ -0,0 +1,81 @@ +use crate::{ + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, + PaintContext, SizeConstraint, +}; +use std::rc::Rc; + +pub struct Svg { + path: String, + // tree: Option>, + size: Option, +} + +impl Svg { + pub fn new(path: String) -> Self { + Self { + path, + // tree: None, + size: None, + } + } +} + +impl Element for Svg { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + _: &AppContext, + ) -> Vector2F { + // let size; + // match ctx.asset_cache.svg(&self.path) { + // Ok(tree) => { + // size = if constraint.max.x().is_infinite() && constraint.max.y().is_infinite() { + // let rect = usvg_rect_to_euclid_rect(&tree.svg_node().view_box.rect); + // rect.size() + // } else { + // let max_size = constraint.max; + // let svg_size = usvg_rect_to_euclid_rect(&tree.svg_node().view_box.rect).size(); + + // if max_size.x().is_infinite() + // || max_size.x() / max_size.y() > svg_size.x() / svg_size.y() + // { + // vec2f(svg_size.x() * max_size.y() / svg_size.y(), max_size.y()) + // } else { + // vec2f(max_size.x(), svg_size.y() * max_size.x() / svg_size.x()) + // } + // }; + // self.tree = Some(tree); + // } + // Err(error) => { + // log::error!("{}", error); + // size = constraint.min; + // } + // }; + + // self.size = Some(size); + // size + todo!() + } + + fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &mut MutableAppContext) {} + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, _: &AppContext) { + if let Some(tree) = self.tree.as_ref() { + ctx.canvas + .draw_svg(tree, RectF::new(origin, self.size.unwrap())); + } + } + + fn size(&self) -> Option { + self.size + } + + fn dispatch_event(&self, _: &Event, _: &mut EventContext, _: &AppContext) -> bool { + false + } +} diff --git a/gpui/src/elements/uniform_list.rs b/gpui/src/elements/uniform_list.rs new file mode 100644 index 0000000000..dc38c14109 --- /dev/null +++ b/gpui/src/elements/uniform_list.rs @@ -0,0 +1,226 @@ +use super::{ + try_rect, AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, + MutableAppContext, PaintContext, SizeConstraint, +}; +use crate::geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, +}; +use parking_lot::Mutex; +use std::{cmp, ops::Range, sync::Arc}; + +#[derive(Clone)] +pub struct UniformListState(Arc>); + +struct StateInner { + scroll_top: f32, + scroll_to: Option, +} + +impl UniformListState { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(StateInner { + scroll_top: 0.0, + scroll_to: None, + }))) + } + + pub fn scroll_to(&self, item_ix: usize) { + self.0.lock().scroll_to = Some(item_ix); + } +} + +pub struct UniformList +where + F: Fn(Range, &AppContext) -> G, + G: Iterator>, +{ + state: UniformListState, + item_count: usize, + build_items: F, + scroll_max: Option, + items: Vec>, + origin: Option, + size: Option, +} + +impl UniformList +where + F: Fn(Range, &AppContext) -> G, + G: Iterator>, +{ + pub fn new(state: UniformListState, item_count: usize, build_items: F) -> Self { + Self { + state, + item_count, + build_items, + scroll_max: None, + items: Default::default(), + origin: None, + size: None, + } + } + + fn scroll( + &self, + position: Vector2F, + delta: Vector2F, + precise: bool, + ctx: &mut EventContext, + _: &AppContext, + ) -> bool { + if !self.rect().unwrap().contains_point(position) { + return false; + } + + if !precise { + todo!("still need to handle non-precise scroll events from a mouse wheel"); + } + + let mut state = self.state.0.lock(); + state.scroll_top = (state.scroll_top - delta.y()) + .max(0.0) + .min(self.scroll_max.unwrap()); + ctx.dispatch_action("uniform_list:scroll", state.scroll_top); + + true + } + + fn autoscroll(&mut self, list_height: f32, item_height: f32) { + let mut state = self.state.0.lock(); + + let scroll_max = self.item_count as f32 * item_height - list_height; + if state.scroll_top > scroll_max { + state.scroll_top = scroll_max; + } + + if let Some(item_ix) = state.scroll_to.take() { + let item_top = item_ix as f32 * item_height; + let item_bottom = item_top + item_height; + + if item_top < state.scroll_top { + state.scroll_top = item_top; + } else if item_bottom > (state.scroll_top + list_height) { + state.scroll_top = item_bottom - list_height; + } + } + } + + fn scroll_top(&self) -> f32 { + self.state.0.lock().scroll_top + } + + fn rect(&self) -> Option { + try_rect(self.origin, self.size) + } +} + +impl Element for UniformList +where + F: Fn(Range, &AppContext) -> G, + G: Iterator>, +{ + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + if constraint.max.y().is_infinite() { + unimplemented!( + "UniformList does not support being rendered with an unconstrained height" + ); + } + let mut size = constraint.max; + let mut item_constraint = + SizeConstraint::new(vec2f(size.x(), 0.0), vec2f(size.x(), f32::INFINITY)); + + let first_item = (self.build_items)(0..1, app).next(); + if let Some(first_item) = first_item { + let mut item_size = first_item.layout(item_constraint, ctx, app); + item_size.set_x(size.x()); + item_constraint.min = item_size; + item_constraint.max = item_size; + + let scroll_height = self.item_count as f32 * item_size.y(); + if scroll_height < size.y() { + size.set_y(size.y().min(scroll_height).max(constraint.min.y())); + } + + self.autoscroll(size.y(), item_size.y()); + + let start = cmp::min( + (self.scroll_top() / item_size.y()) as usize, + self.item_count, + ); + let end = cmp::min( + self.item_count, + start + (size.y() / item_size.y()).ceil() as usize + 1, + ); + self.items.clear(); + self.items.extend((self.build_items)(start..end, app)); + + self.scroll_max = Some(item_size.y() * self.item_count as f32 - size.y()); + + for item in &mut self.items { + item.layout(item_constraint, ctx, app); + } + } + + self.size = Some(size); + size + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + for item in &mut self.items { + item.after_layout(ctx, app); + } + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + // self.origin = Some(origin); + + // if let Some(item) = self.items.first() { + // ctx.canvas.save(); + // let mut clip_path = Path2D::new(); + // clip_path.rect(RectF::new(origin, self.size.unwrap())); + // ctx.canvas.clip_path(clip_path, FillRule::Winding); + + // let item_height = item.size().unwrap().y(); + // let mut item_origin = origin - vec2f(0.0, self.state.0.lock().scroll_top % item_height); + // for item in &mut self.items { + // item.paint(item_origin, ctx, app); + // item_origin += vec2f(0.0, item_height); + // } + // ctx.canvas.restore(); + // } + } + + fn size(&self) -> Option { + self.size + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + let mut handled = false; + for item in &self.items { + if item.dispatch_event(event, ctx, app) { + handled = true; + } + } + + match event { + Event::ScrollWheel { + position, + delta, + precise, + } => { + if self.scroll(*position, *delta, *precise, ctx, app) { + handled = true; + } + } + _ => {} + } + + handled + } +} diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs new file mode 100644 index 0000000000..7f5ce45189 --- /dev/null +++ b/gpui/src/fonts.rs @@ -0,0 +1,298 @@ +use crate::geometry::vector::{vec2f, Vector2F}; +use anyhow::{anyhow, Result}; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; + +pub use font_kit::properties::{Properties, Weight}; +use font_kit::{ + font::Font, loaders::core_text::NativeFont, metrics::Metrics, source::SystemSource, +}; +use ordered_float::OrderedFloat; +use std::{collections::HashMap, sync::Arc}; + +pub type GlyphId = u32; + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct FamilyId(usize); + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct FontId(usize); + +pub struct FontCache(RwLock); + +pub struct FontCacheState { + source: SystemSource, + families: Vec, + fonts: Vec>, + font_names: Vec>, + font_selections: HashMap>, + metrics: HashMap, + native_fonts: HashMap<(FontId, OrderedFloat), NativeFont>, + fonts_by_name: HashMap, FontId>, + emoji_font_id: Option, +} + +unsafe impl Send for FontCache {} + +struct Family { + name: String, + font_ids: Vec, +} + +impl FontCache { + pub fn new() -> Self { + Self(RwLock::new(FontCacheState { + source: SystemSource::new(), + families: Vec::new(), + fonts: Vec::new(), + font_names: Vec::new(), + font_selections: HashMap::new(), + metrics: HashMap::new(), + native_fonts: HashMap::new(), + fonts_by_name: HashMap::new(), + emoji_font_id: None, + })) + } + + pub fn load_family(&self, names: &[&str]) -> Result { + for name in names { + let state = self.0.upgradable_read(); + + if let Some(ix) = state.families.iter().position(|f| f.name == *name) { + return Ok(FamilyId(ix)); + } + + let mut state = RwLockUpgradableReadGuard::upgrade(state); + + if let Ok(handle) = state.source.select_family_by_name(name) { + if handle.is_empty() { + continue; + } + + let family_id = FamilyId(state.families.len()); + let mut font_ids = Vec::new(); + for font in handle.fonts() { + let font = font.load()?; + if font.glyph_for_char('m').is_none() { + return Err(anyhow!("font must contain a glyph for the 'm' character")); + } + font_ids.push(push_font(&mut state, font)); + } + + state.families.push(Family { + name: String::from(*name), + font_ids, + }); + return Ok(family_id); + } + } + + Err(anyhow!( + "could not find a non-empty font family matching one of the given names" + )) + } + + pub fn default_font(&self, family_id: FamilyId) -> FontId { + self.select_font(family_id, &Properties::default()).unwrap() + } + + pub fn select_font(&self, family_id: FamilyId, properties: &Properties) -> Result { + let inner = self.0.upgradable_read(); + if let Some(font_id) = inner + .font_selections + .get(&family_id) + .and_then(|f| f.get(properties)) + { + Ok(*font_id) + } else { + let mut inner = RwLockUpgradableReadGuard::upgrade(inner); + let family = &inner.families[family_id.0]; + let candidates = family + .font_ids + .iter() + .map(|font_id| inner.fonts[font_id.0].properties()) + .collect::>(); + let idx = font_kit::matching::find_best_match(&candidates, properties)?; + let font_id = family.font_ids[idx]; + + inner + .font_selections + .entry(family_id) + .or_default() + .insert(properties.clone(), font_id); + Ok(font_id) + } + } + + pub fn font(&self, font_id: FontId) -> Arc { + self.0.read().fonts[font_id.0].clone() + } + + pub fn font_name(&self, font_id: FontId) -> Arc { + self.0.read().font_names[font_id.0].clone() + } + + pub fn metric(&self, font_id: FontId, f: F) -> T + where + F: FnOnce(&Metrics) -> T, + T: 'static, + { + let state = self.0.upgradable_read(); + if let Some(metrics) = state.metrics.get(&font_id) { + f(metrics) + } else { + let metrics = state.fonts[font_id.0].metrics(); + let metric = f(&metrics); + let mut state = RwLockUpgradableReadGuard::upgrade(state); + state.metrics.insert(font_id, metrics); + metric + } + } + + pub fn is_emoji(&self, font_id: FontId) -> bool { + self.0 + .read() + .emoji_font_id + .map_or(false, |emoji_font_id| emoji_font_id == font_id) + } + + pub fn bounding_box(&self, font_id: FontId, font_size: f32) -> Vector2F { + let bounding_box = self.metric(font_id, |m| m.bounding_box); + let width = self.scale_metric(bounding_box.width(), font_id, font_size); + let height = self.scale_metric(bounding_box.height(), font_id, font_size); + vec2f(width, height) + } + + pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 { + let bounding_box = self.metric(font_id, |m| m.bounding_box); + self.scale_metric(bounding_box.height(), font_id, font_size) + } + + pub fn cap_height(&self, font_id: FontId, font_size: f32) -> f32 { + self.scale_metric(self.metric(font_id, |m| m.cap_height), font_id, font_size) + } + + pub fn ascent(&self, font_id: FontId, font_size: f32) -> f32 { + self.scale_metric(self.metric(font_id, |m| m.ascent), font_id, font_size) + } + + pub fn descent(&self, font_id: FontId, font_size: f32) -> f32 { + self.scale_metric(self.metric(font_id, |m| m.descent), font_id, font_size) + } + + // pub fn render_emoji(&self, glyph_id: GlyphId, font_size: f32) -> Result { + // let key = (glyph_id, OrderedFloat(font_size)); + + // { + // if let Some(image) = self.0.read().emoji_images.get(&key) { + // return Ok(image.clone()); + // } + // } + + // let font_id = self.emoji_font_id()?; + // let bounding_box = self.bounding_box(font_id, font_size); + // let width = (4.0 * bounding_box.x()) as usize; + // let height = (4.0 * bounding_box.y()) as usize; + // let mut ctx = CGContext::create_bitmap_context( + // None, + // width, + // height, + // 8, + // width * 4, + // &CGColorSpace::create_device_rgb(), + // kCGImageAlphaPremultipliedLast | kCGBitmapByteOrderDefault, + // ); + // ctx.scale(4.0, 4.0); + + // let native_font = self.native_font(font_id, font_size); + // let glyph = glyph_id.0 as CGGlyph; + // let glyph_bounds = native_font.get_bounding_rects_for_glyphs(Default::default(), &[glyph]); + // let position = CGPoint::new(glyph_bounds.origin.x, -glyph_bounds.origin.y); + + // native_font.draw_glyphs(&[glyph], &[position], ctx.clone()); + + // ctx.flush(); + + // let image = Pattern::from_image(Image::new( + // vec2i(ctx.width() as i32, ctx.height() as i32), + // Arc::new(u8_slice_to_color_slice(&ctx.data()).into()), + // )); + // self.0.write().emoji_images.insert(key, image.clone()); + + // Ok(image) + // } + + fn emoji_font_id(&self) -> Result { + let state = self.0.upgradable_read(); + + if let Some(font_id) = state.emoji_font_id { + Ok(font_id) + } else { + let handle = state.source.select_family_by_name("Apple Color Emoji")?; + let font = handle + .fonts() + .first() + .ok_or(anyhow!("no fonts in Apple Color Emoji font family"))? + .load()?; + let mut state = RwLockUpgradableReadGuard::upgrade(state); + let font_id = push_font(&mut state, font); + state.emoji_font_id = Some(font_id); + Ok(font_id) + } + } + + pub fn scale_metric(&self, metric: f32, font_id: FontId, font_size: f32) -> f32 { + metric * font_size / self.metric(font_id, |m| m.units_per_em as f32) + } + + pub fn native_font(&self, font_id: FontId, size: f32) -> NativeFont { + let native_key = (font_id, OrderedFloat(size)); + + let state = self.0.upgradable_read(); + if let Some(native_font) = state.native_fonts.get(&native_key).cloned() { + native_font + } else { + let native_font = state.fonts[font_id.0] + .native_font() + .clone_with_font_size(size as f64); + RwLockUpgradableReadGuard::upgrade(state) + .native_fonts + .insert(native_key, native_font.clone()); + native_font + } + } + + pub fn font_id_for_native_font(&self, native_font: NativeFont) -> FontId { + let postscript_name = native_font.postscript_name(); + let state = self.0.upgradable_read(); + if let Some(font_id) = state.fonts_by_name.get(&postscript_name) { + *font_id + } else { + push_font(&mut RwLockUpgradableReadGuard::upgrade(state), unsafe { + Font::from_native_font(native_font.clone()) + }) + } + } +} + +fn push_font(state: &mut FontCacheState, font: Font) -> FontId { + let font_id = FontId(state.fonts.len()); + let name = Arc::new(font.postscript_name().unwrap()); + if *name == "AppleColorEmoji" { + state.emoji_font_id = Some(font_id); + } + state.fonts.push(Arc::new(font)); + state.font_names.push(name.clone()); + state.fonts_by_name.insert(name, font_id); + font_id +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_render_emoji() { + let ctx = FontCache::new(); + let _ = ctx.render_emoji(0, 16.0); + } +} diff --git a/gpui/src/lib.rs b/gpui/src/lib.rs index 67fbbf1ac7..e467d65e08 100644 --- a/gpui/src/lib.rs +++ b/gpui/src/lib.rs @@ -1,6 +1,17 @@ +mod app; +pub mod elements; pub mod executor; +mod fonts; pub mod keymap; pub mod platform; +mod presenter; +mod scene; +mod util; +pub use app::*; +pub use elements::Element; pub use pathfinder_color as color; pub use pathfinder_geometry as geometry; +pub use platform::Event; +pub use presenter::*; +use scene::Scene; diff --git a/gpui/src/platform/mod.rs b/gpui/src/platform/mod.rs index e22e6ff674..cf858c15e7 100644 --- a/gpui/src/platform/mod.rs +++ b/gpui/src/platform/mod.rs @@ -9,7 +9,7 @@ pub mod current { use crate::{executor, geometry::rect::RectF}; use anyhow::Result; use async_task::Runnable; -use event::Event; +pub use event::Event; use std::{path::PathBuf, rc::Rc, sync::Arc}; pub trait Runner { diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs new file mode 100644 index 0000000000..f873128b3f --- /dev/null +++ b/gpui/src/presenter.rs @@ -0,0 +1,370 @@ +use crate::{ + app::{AppContext, MutableAppContext, WindowInvalidation}, + elements::Element, + platform::Event, + Scene, +}; +use pathfinder_geometry::vector::{vec2f, Vector2F}; +use std::{any::Any, collections::HashMap, rc::Rc}; + +pub struct Presenter { + window_id: usize, + rendered_views: HashMap>, + parents: HashMap, + font_cache: Rc, + text_layout_cache: LayoutCache, + asset_cache: Rc, +} + +impl Presenter { + pub fn new( + window_id: usize, + font_cache: Rc, + asset_cache: Rc, + app: &MutableAppContext, + ) -> Self { + Self { + window_id, + rendered_views: app.render_views(window_id).unwrap(), + parents: HashMap::new(), + font_cache, + text_layout_cache: LayoutCache::new(), + asset_cache, + } + } + + fn invalidate(&mut self, invalidation: WindowInvalidation, app: &AppContext) { + for view_id in invalidation.updated { + self.rendered_views + .insert(view_id, app.render_view(self.window_id, view_id).unwrap()); + } + for view_id in invalidation.removed { + self.rendered_views.remove(&view_id); + self.parents.remove(&view_id); + } + } + + pub fn build_scene( + &mut self, + window_size: Vector2F, + scale_factor: f32, + app: &mut MutableAppContext, + ) -> Scene { + self.layout(window_size, app.ctx()); + self.after_layout(app); + let scene = self.paint(window_size, scale_factor, app.ctx()); + self.text_layout_cache.finish_frame(); + scene + } + + fn layout(&mut self, size: Vector2F, app: &AppContext) { + if let Some(root_view_id) = app.root_view_id(self.window_id) { + let mut layout_ctx = LayoutContext { + rendered_views: &mut self.rendered_views, + parents: &mut self.parents, + font_cache: &self.font_cache, + text_layout_cache: &self.text_layout_cache, + asset_cache: &self.asset_cache, + view_stack: Vec::new(), + }; + layout_ctx.layout(root_view_id, SizeConstraint::strict(size), app); + } + } + + fn after_layout(&mut self, app: &mut MutableAppContext) { + if let Some(root_view_id) = app.root_view_id(self.window_id) { + let mut ctx = AfterLayoutContext { + rendered_views: &mut self.rendered_views, + font_cache: &self.font_cache, + text_layout_cache: &self.text_layout_cache, + }; + ctx.after_layout(root_view_id, app); + } + } + + fn paint(&mut self, size: Vector2F, scale_factor: f32, app: &AppContext) -> Scene { + // let mut canvas = Canvas::new(size * scale_factor).get_context_2d(self.font_context.clone()); + // canvas.scale(scale_factor); + + // if let Some(root_view_id) = app.root_view_id(self.window_id) { + // let mut paint_ctx = PaintContext { + // canvas: &mut canvas, + // font_cache: &self.font_cache, + // text_layout_cache: &self.text_layout_cache, + // rendered_views: &mut self.rendered_views, + // }; + // paint_ctx.paint(root_view_id, Vector2F::zero(), app); + // } + + // canvas.into_canvas().into_scene() + todo!() + } + + pub fn responder_chain(&self, app: &AppContext) -> Option> { + app.focused_view_id(self.window_id).map(|mut view_id| { + let mut chain = vec![view_id]; + while let Some(parent_id) = self.parents.get(&view_id) { + view_id = *parent_id; + chain.push(view_id); + } + chain.reverse(); + chain + }) + } + + pub fn dispatch_event( + &self, + event: Event, + app: &AppContext, + ) -> Vec<(usize, &'static str, Box)> { + let mut event_ctx = EventContext { + rendered_views: &self.rendered_views, + actions: Vec::new(), + font_cache: &self.font_cache, + text_layout_cache: &self.text_layout_cache, + view_stack: Vec::new(), + }; + if let Some(root_view_id) = app.root_view_id(self.window_id) { + event_ctx.dispatch_event_on_view(root_view_id, &event, app); + } + event_ctx.actions + } +} + +pub struct LayoutContext<'a> { + rendered_views: &'a mut HashMap>, + parents: &'a mut HashMap, + pub font_cache: &'a FontCache, + pub text_layout_cache: &'a LayoutCache, + pub asset_cache: &'a AssetCache, + view_stack: Vec, +} + +impl<'a> LayoutContext<'a> { + fn layout(&mut self, view_id: usize, constraint: SizeConstraint, app: &AppContext) -> Vector2F { + if let Some(parent_id) = self.view_stack.last() { + self.parents.insert(view_id, *parent_id); + } + self.view_stack.push(view_id); + let mut rendered_view = self.rendered_views.remove(&view_id).unwrap(); + let size = rendered_view.layout(constraint, self, app); + self.rendered_views.insert(view_id, rendered_view); + self.view_stack.pop(); + size + } +} + +pub struct AfterLayoutContext<'a> { + rendered_views: &'a mut HashMap>, + pub font_cache: &'a FontCache, + pub text_layout_cache: &'a LayoutCache, +} + +impl<'a> AfterLayoutContext<'a> { + fn after_layout(&mut self, view_id: usize, app: &mut MutableAppContext) { + if let Some(mut view) = self.rendered_views.remove(&view_id) { + view.after_layout(self, app); + self.rendered_views.insert(view_id, view); + } + } +} + +pub struct PaintContext<'a> { + rendered_views: &'a mut HashMap>, + // pub canvas: &'a mut CanvasRenderingContext2D, + pub font_cache: &'a FontCache, + pub text_layout_cache: &'a LayoutCache, +} + +impl<'a> PaintContext<'a> { + fn paint(&mut self, view_id: usize, origin: Vector2F, app: &AppContext) { + if let Some(mut tree) = self.rendered_views.remove(&view_id) { + tree.paint(origin, self, app); + self.rendered_views.insert(view_id, tree); + } + } +} + +pub struct EventContext<'a> { + rendered_views: &'a HashMap>, + actions: Vec<(usize, &'static str, Box)>, + pub font_cache: &'a FontCache, + pub text_layout_cache: &'a LayoutCache, + view_stack: Vec, +} + +impl<'a> EventContext<'a> { + pub fn dispatch_event_on_view( + &mut self, + view_id: usize, + event: &Event, + app: &AppContext, + ) -> bool { + if let Some(element) = self.rendered_views.get(&view_id) { + self.view_stack.push(view_id); + let result = element.dispatch_event(event, self, app); + self.view_stack.pop(); + result + } else { + false + } + } + + pub fn dispatch_action(&mut self, name: &'static str, arg: A) { + self.actions + .push((*self.view_stack.last().unwrap(), name, Box::new(arg))); + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Axis { + Horizontal, + Vertical, +} + +impl Axis { + pub fn invert(self) -> Self { + match self { + Self::Horizontal => Self::Vertical, + Self::Vertical => Self::Horizontal, + } + } +} + +pub trait Vector2FExt { + fn along(self, axis: Axis) -> f32; +} + +impl Vector2FExt for Vector2F { + fn along(self, axis: Axis) -> f32 { + match axis { + Axis::Horizontal => self.x(), + Axis::Vertical => self.y(), + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct SizeConstraint { + pub min: Vector2F, + pub max: Vector2F, +} + +impl SizeConstraint { + pub fn new(min: Vector2F, max: Vector2F) -> Self { + Self { min, max } + } + + pub fn strict(size: Vector2F) -> Self { + Self { + min: size, + max: size, + } + } + + pub fn strict_along(axis: Axis, max: f32) -> Self { + match axis { + Axis::Horizontal => Self { + min: vec2f(max, 0.0), + max: vec2f(max, f32::INFINITY), + }, + Axis::Vertical => Self { + min: vec2f(0.0, max), + max: vec2f(f32::INFINITY, max), + }, + } + } + + pub fn max_along(&self, axis: Axis) -> f32 { + match axis { + Axis::Horizontal => self.max.x(), + Axis::Vertical => self.max.y(), + } + } +} + +pub struct ChildView { + view_id: usize, + size: Option, + origin: Option, +} + +impl ChildView { + pub fn new(view_id: usize) -> Self { + Self { + view_id, + size: None, + origin: None, + } + } +} + +impl Element for ChildView { + fn layout( + &mut self, + constraint: SizeConstraint, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + let size = ctx.layout(self.view_id, constraint, app); + self.size = Some(size); + size + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + ctx.after_layout(self.view_id, app); + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + self.origin = Some(origin); + ctx.paint(self.view_id, origin, app); + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + ctx.dispatch_event_on_view(self.view_id, event, app) + } + + fn size(&self) -> Option { + self.size + } +} + +#[cfg(test)] +mod tests { + // #[test] + // fn test_responder_chain() { + // let settings = settings_rx(None); + // let mut app = App::new().unwrap(); + // let workspace = app.add_model(|ctx| Workspace::new(Vec::new(), ctx)); + // let (window_id, workspace_view) = + // app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); + + // let invalidations = Rc::new(RefCell::new(Vec::new())); + // let invalidations_ = invalidations.clone(); + // app.on_window_invalidated(window_id, move |invalidation, _| { + // invalidations_.borrow_mut().push(invalidation) + // }); + + // let active_pane_id = workspace_view.update(&mut app, |view, ctx| { + // ctx.focus(view.active_pane()); + // view.active_pane().id() + // }); + + // app.update(|app| { + // let mut presenter = Presenter::new( + // window_id, + // Rc::new(FontCache::new()), + // Rc::new(AssetCache::new()), + // app, + // ); + // for invalidation in invalidations.borrow().iter().cloned() { + // presenter.update(vec2f(1024.0, 768.0), 2.0, Some(invalidation), app); + // } + + // assert_eq!( + // presenter.responder_chain(app.ctx()).unwrap(), + // vec![workspace_view.id(), active_pane_id] + // ); + // }); + // } +} diff --git a/gpui/src/scene.rs b/gpui/src/scene.rs new file mode 100644 index 0000000000..c894078ad2 --- /dev/null +++ b/gpui/src/scene.rs @@ -0,0 +1 @@ +pub struct Scene; diff --git a/gpui/src/util.rs b/gpui/src/util.rs new file mode 100644 index 0000000000..5a58e7b24d --- /dev/null +++ b/gpui/src/util.rs @@ -0,0 +1,77 @@ +use rand::prelude::*; +use std::cmp::Ordering; + +pub fn pre_inc(value: &mut usize) -> usize { + *value += 1; + *value +} + +pub fn post_inc(value: &mut usize) -> usize { + let prev = *value; + *value += 1; + prev +} + +pub fn find_insertion_index<'a, F, T, E>(slice: &'a [T], mut f: F) -> Result +where + F: FnMut(&'a T) -> Result, +{ + use Ordering::*; + + let s = slice; + let mut size = s.len(); + if size == 0 { + return Ok(0); + } + let mut base = 0usize; + while size > 1 { + let half = size / 2; + let mid = base + half; + // mid is always in [0, size), that means mid is >= 0 and < size. + // mid >= 0: by definition + // mid < size: mid = size / 2 + size / 4 + size / 8 ... + let cmp = f(unsafe { s.get_unchecked(mid) })?; + base = if cmp == Greater { base } else { mid }; + size -= half; + } + // base is always in [0, size) because base <= mid. + let cmp = f(unsafe { s.get_unchecked(base) })?; + if cmp == Equal { + Ok(base) + } else { + Ok(base + (cmp == Less) as usize) + } +} + +pub struct RandomCharIter(T); + +impl RandomCharIter { + pub fn new(rng: T) -> Self { + Self(rng) + } +} + +impl Iterator for RandomCharIter { + type Item = char; + + fn next(&mut self) -> Option { + if self.0.gen_bool(1.0 / 5.0) { + Some('\n') + } else { + Some(self.0.gen_range(b'a', b'z' + 1).into()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_find_insertion_index() { + assert_eq!( + find_insertion_index(&[0, 4, 8], |probe| Ok::(probe.cmp(&2))), + Ok(1) + ); + } +} diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 5da82a3da5..f9faa90a0f 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -4,9 +4,21 @@ edition = "2018" name = "zed" version = "0.1.0" +[lib] +name = "zed" +path = "src/lib.rs" + +[[bin]] +name = "zed" +path = "src/main.rs" + [dependencies] +anyhow = "1.0.38" +arrayvec = "0.5.2" dirs = "3.0" gpui = {path = "../gpui"} +lazy_static = "1.4.0" libc = "0.2" log = "0.4" +rand = "0.8.3" simplelog = "0.9" diff --git a/zed/src/editor/buffer/anchor.rs b/zed/src/editor/buffer/anchor.rs new file mode 100644 index 0000000000..b0e2f0269c --- /dev/null +++ b/zed/src/editor/buffer/anchor.rs @@ -0,0 +1,85 @@ +use super::Buffer; +use crate::time; +use anyhow::Result; +use std::cmp::Ordering; +use std::ops::Range; + +#[derive(Clone, Eq, PartialEq, Debug, Hash)] +pub enum Anchor { + Start, + End, + Middle { + insertion_id: time::Local, + offset: usize, + bias: AnchorBias, + }, +} + +#[derive(Clone, Eq, PartialEq, Debug, Hash)] +pub enum AnchorBias { + Left, + Right, +} + +impl PartialOrd for AnchorBias { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for AnchorBias { + fn cmp(&self, other: &Self) -> Ordering { + use AnchorBias::*; + + if self == other { + return Ordering::Equal; + } + + match (self, other) { + (Left, _) => Ordering::Less, + (Right, _) => Ordering::Greater, + } + } +} + +impl Anchor { + pub fn cmp(&self, other: &Anchor, buffer: &Buffer) -> Result { + if self == other { + return Ok(Ordering::Equal); + } + + Ok(match (self, other) { + (Anchor::Start, _) | (_, Anchor::End) => Ordering::Less, + (Anchor::End, _) | (_, Anchor::Start) => Ordering::Greater, + ( + Anchor::Middle { + offset: self_offset, + bias: self_bias, + .. + }, + Anchor::Middle { + offset: other_offset, + bias: other_bias, + .. + }, + ) => buffer + .fragment_id_for_anchor(self)? + .cmp(buffer.fragment_id_for_anchor(other)?) + .then_with(|| self_offset.cmp(other_offset)) + .then_with(|| self_bias.cmp(other_bias)), + }) + } +} + +pub trait AnchorRangeExt { + fn cmp(&self, b: &Range, buffer: &Buffer) -> Result; +} + +impl AnchorRangeExt for Range { + fn cmp(&self, other: &Range, buffer: &Buffer) -> Result { + Ok(match self.start.cmp(&other.start, buffer)? { + Ordering::Equal => other.end.cmp(&self.end, buffer)?, + ord @ _ => ord, + }) + } +} diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs new file mode 100644 index 0000000000..16ba5f5843 --- /dev/null +++ b/zed/src/editor/buffer/mod.rs @@ -0,0 +1,2548 @@ +mod anchor; +mod point; +mod text; + +pub use anchor::*; +pub use point::*; +pub use text::*; + +use crate::{ + app::{self as app, AppContext, ModelContext}, + operation_queue::{self, OperationQueue}, + sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree}, + time, + util::RandomCharIter, + worktree::FileHandle, + ReplicaId, +}; +use anyhow::{anyhow, Result}; +use lazy_static::lazy_static; +use rand::prelude::*; +use std::{ + cmp::{self, Ordering}, + collections::{HashMap, HashSet}, + iter::{self, Iterator}, + mem, + ops::{AddAssign, Range}, + path::PathBuf, + str, + sync::Arc, +}; + +pub type SelectionSetId = time::Lamport; +pub type SelectionsVersion = usize; + +pub struct Buffer { + file: Option, + fragments: SumTree, + insertion_splits: HashMap>, + pub version: time::Global, + last_edit: time::Local, + selections: HashMap>, + pub selections_last_update: SelectionsVersion, + deferred_ops: OperationQueue, + deferred_replicas: HashSet, + replica_id: ReplicaId, + local_clock: time::Local, + lamport_clock: time::Lamport, +} + +#[derive(Clone)] +pub struct History { + pub base_text: String, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Selection { + pub start: Anchor, + pub end: Anchor, + pub reversed: bool, +} + +#[derive(Clone)] +pub struct Chars<'a> { + fragments_cursor: Cursor<'a, Fragment, usize, usize>, + fragment_chars: str::Chars<'a>, +} + +struct Edits<'a, F: Fn(&FragmentSummary) -> bool> { + cursor: FilterCursor<'a, F, Fragment, usize>, + since: time::Global, + delta: isize, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Edit { + pub old_range: Range, + pub new_range: Range, +} + +impl Edit { + pub fn delta(&self) -> isize { + (self.new_range.end - self.new_range.start) as isize + - (self.old_range.end - self.old_range.start) as isize + } + + pub fn old_extent(&self) -> usize { + self.old_range.end - self.old_range.start + } +} + +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Insertion { + id: time::Local, + parent_id: time::Local, + offset_in_parent: usize, + text: Text, + lamport_timestamp: time::Lamport, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +struct Fragment { + id: FragmentId, + insertion: Insertion, + text: Text, + deletions: HashSet, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct FragmentSummary { + text_summary: TextSummary, + max_fragment_id: FragmentId, + max_version: time::Global, +} + +#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)] +struct FragmentExtent { + chars: usize, + lines: Point, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +struct InsertionSplit { + extent: usize, + fragment_id: FragmentId, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +struct InsertionSplitSummary { + extent: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Operation { + Edit { + start_id: time::Local, + start_offset: usize, + end_id: time::Local, + end_offset: usize, + version_in_range: time::Global, + new_text: Option, + local_timestamp: time::Local, + lamport_timestamp: time::Lamport, + }, + UpdateSelections { + set_id: SelectionSetId, + selections: Option>, + lamport_timestamp: time::Lamport, + }, +} + +impl Buffer { + pub fn new>(replica_id: ReplicaId, base_text: T) -> Self { + Self::build(replica_id, None, base_text.into()) + } + + pub fn from_history(replica_id: ReplicaId, file: FileHandle, history: History) -> Self { + Self::build(replica_id, Some(file), history.base_text) + } + + fn build(replica_id: ReplicaId, file: Option, base_text: String) -> Self { + let mut insertion_splits = HashMap::new(); + let mut fragments = SumTree::new(); + + let base_insertion = Insertion { + id: time::Local::default(), + parent_id: time::Local::default(), + offset_in_parent: 0, + text: base_text.into(), + lamport_timestamp: time::Lamport::default(), + }; + + insertion_splits.insert( + base_insertion.id, + SumTree::from_item(InsertionSplit { + fragment_id: FragmentId::min_value().clone(), + extent: 0, + }), + ); + fragments.push(Fragment { + id: FragmentId::min_value().clone(), + insertion: base_insertion.clone(), + text: base_insertion.text.slice(0..0), + deletions: HashSet::new(), + }); + + if base_insertion.text.len() > 0 { + let base_fragment_id = + FragmentId::between(&FragmentId::min_value(), &FragmentId::max_value()); + + insertion_splits + .get_mut(&base_insertion.id) + .unwrap() + .push(InsertionSplit { + fragment_id: base_fragment_id.clone(), + extent: base_insertion.text.len(), + }); + fragments.push(Fragment { + id: base_fragment_id, + text: base_insertion.text.clone(), + insertion: base_insertion, + deletions: HashSet::new(), + }); + } + + Self { + file, + fragments, + insertion_splits, + version: time::Global::new(), + last_edit: time::Local::default(), + selections: HashMap::default(), + selections_last_update: 0, + deferred_ops: OperationQueue::new(), + deferred_replicas: HashSet::new(), + replica_id, + local_clock: time::Local::new(replica_id), + lamport_clock: time::Lamport::new(replica_id), + } + } + + pub fn path(&self, app: &AppContext) -> Option { + self.file.as_ref().map(|file| file.path(app)) + } + + pub fn entry_id(&self) -> Option<(usize, usize)> { + self.file.as_ref().map(|file| file.entry_id()) + } + + pub fn is_modified(&self) -> bool { + self.version != time::Global::new() + } + + pub fn text_summary(&self) -> TextSummary { + self.fragments.extent::() + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + let mut summary = TextSummary::default(); + + let mut cursor = self.fragments.cursor::(); + cursor.seek(&range.start, SeekBias::Right); + + if let Some(fragment) = cursor.item() { + let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start(); + let summary_end = cmp::min(range.end - cursor.start(), fragment.len()); + summary += &fragment.text.slice(summary_start..summary_end).summary(); + cursor.next(); + } + + if range.end > *cursor.start() { + summary += &cursor.summary::(&range.end, SeekBias::Right); + + if let Some(fragment) = cursor.item() { + let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start(); + let summary_end = cmp::min(range.end - cursor.start(), fragment.len()); + summary += &fragment.text.slice(summary_start..summary_end).summary(); + } + } + + summary + } + + pub fn len(&self) -> usize { + self.fragments.extent::() + } + + pub fn line_len(&self, row: u32) -> Result { + let row_start_offset = Point::new(row, 0).to_offset(self)?; + let row_end_offset = if row >= self.max_point().row { + self.len() + } else { + Point::new(row + 1, 0).to_offset(self)? - 1 + }; + + Ok((row_end_offset - row_start_offset) as u32) + } + + pub fn rightmost_point(&self) -> Point { + self.fragments.summary().text_summary.rightmost_point + } + + pub fn rightmost_point_in_range(&self, range: Range) -> Point { + let mut summary = TextSummary::default(); + + let mut cursor = self.fragments.cursor::(); + cursor.seek(&range.start, SeekBias::Right); + + if let Some(fragment) = cursor.item() { + let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start(); + let summary_end = cmp::min(range.end - cursor.start(), fragment.len()); + summary += &fragment.text.slice(summary_start..summary_end).summary(); + cursor.next(); + } + + if range.end > *cursor.start() { + summary += &cursor.summary::(&range.end, SeekBias::Right); + + if let Some(fragment) = cursor.item() { + let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start(); + let summary_end = cmp::min(range.end - cursor.start(), fragment.len()); + summary += &fragment.text.slice(summary_start..summary_end).summary(); + } + } + + summary.rightmost_point + } + + pub fn max_point(&self) -> Point { + self.fragments.extent() + } + + pub fn line(&self, row: u32) -> Result { + Ok(self + .chars_at(Point::new(row, 0))? + .take_while(|c| *c != '\n') + .collect()) + } + + pub fn text(&self) -> String { + self.chars().collect() + } + + pub fn text_for_range(&self, range: Range) -> Result { + let start = range.start.to_offset(self)?; + let end = range.end.to_offset(self)?; + Ok(self.chars_at(start)?.take(end - start).collect()) + } + + pub fn chars(&self) -> Chars { + self.chars_at(0).unwrap() + } + + pub fn chars_at(&self, position: T) -> Result { + let offset = position.to_offset(self)?; + + let mut fragments_cursor = self.fragments.cursor::(); + fragments_cursor.seek(&offset, SeekBias::Right); + + let fragment_chars = fragments_cursor.item().map_or("".chars(), |fragment| { + let offset_in_fragment = offset - fragments_cursor.start(); + fragment.text[offset_in_fragment..].chars() + }); + + Ok(Chars { + fragments_cursor, + fragment_chars, + }) + } + + pub fn selections_changed_since(&self, since: SelectionsVersion) -> bool { + self.selections_last_update != since + } + + pub fn edits_since<'a>(&'a self, since: time::Global) -> impl 'a + Iterator { + let since_2 = since.clone(); + let cursor = self + .fragments + .filter(move |summary| summary.max_version.changed_since(&since_2)); + + Edits { + cursor, + since, + delta: 0, + } + } + + pub fn deferred_ops_len(&self) -> usize { + self.deferred_ops.len() + } + + pub fn edit( + &mut self, + old_ranges: I, + new_text: T, + ctx: Option<&mut ModelContext>, + ) -> Result> + where + I: IntoIterator>, + S: ToOffset, + T: Into, + { + let new_text = new_text.into(); + let new_text = if new_text.len() > 0 { + Some(new_text) + } else { + None + }; + + let old_version = self.version.clone(); + let old_ranges = old_ranges + .into_iter() + .map(|range| Ok(range.start.to_offset(self)?..range.end.to_offset(self)?)) + .collect::>>>()?; + + let ops = self.splice_fragments( + old_ranges + .into_iter() + .filter(|old_range| new_text.is_some() || old_range.end > old_range.start), + new_text.clone(), + ); + + if let Some(op) = ops.last() { + if let Some(ctx) = ctx { + ctx.notify(); + let changes = self.edits_since(old_version).collect::>(); + if !changes.is_empty() { + ctx.emit(Event::Edited(changes)) + } + } + + if let Operation::Edit { + local_timestamp, .. + } = op + { + self.last_edit = *local_timestamp; + self.version.observe(*local_timestamp); + } else { + unreachable!() + } + } + + Ok(ops) + } + + pub fn simulate_typing(&mut self, rng: &mut T) { + let end = rng.gen_range(0, self.len() + 1); + let start = rng.gen_range(0, end + 1); + let mut range = start..end; + + let new_text_len = rng.gen_range(0, 100); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + for char in new_text.chars() { + self.edit(Some(range.clone()), char.to_string().as_str(), None) + .unwrap(); + range = range.end + 1..range.end + 1; + } + } + + pub fn randomly_edit( + &mut self, + rng: &mut T, + old_range_count: usize, + ctx: Option<&mut ModelContext>, + ) -> (Vec>, String, Vec) + where + T: Rng, + { + let mut old_ranges: Vec> = Vec::new(); + for _ in 0..old_range_count { + let last_end = old_ranges.last().map_or(0, |last_range| last_range.end + 1); + if last_end > self.len() { + break; + } + let end = rng.gen_range(last_end, self.len() + 1); + let start = rng.gen_range(last_end, end + 1); + old_ranges.push(start..end); + } + let new_text_len = rng.gen_range(0, 10); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + let operations = self + .edit(old_ranges.iter().cloned(), new_text.as_str(), ctx) + .unwrap(); + + (old_ranges, new_text, operations) + } + + pub fn add_selection_set(&mut self, ranges: I) -> Result<(SelectionSetId, Operation)> + where + I: IntoIterator>, + { + let selections = self.selections_from_ranges(ranges)?; + let lamport_timestamp = self.lamport_clock.tick(); + self.selections + .insert(lamport_timestamp, selections.clone()); + self.selections_last_update += 1; + + Ok(( + lamport_timestamp, + Operation::UpdateSelections { + set_id: lamport_timestamp, + selections: Some(selections), + lamport_timestamp, + }, + )) + } + + pub fn replace_selection_set( + &mut self, + set_id: SelectionSetId, + ranges: I, + ) -> Result + where + I: IntoIterator>, + { + self.selections + .remove(&set_id) + .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?; + + let mut selections = self.selections_from_ranges(ranges)?; + self.merge_selections(&mut selections); + self.selections.insert(set_id, selections.clone()); + + let lamport_timestamp = self.lamport_clock.tick(); + self.selections_last_update += 1; + + Ok(Operation::UpdateSelections { + set_id, + selections: Some(selections), + lamport_timestamp, + }) + } + + pub fn remove_selection_set(&mut self, set_id: SelectionSetId) -> Result { + self.selections + .remove(&set_id) + .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?; + let lamport_timestamp = self.lamport_clock.tick(); + self.selections_last_update += 1; + Ok(Operation::UpdateSelections { + set_id, + selections: None, + lamport_timestamp, + }) + } + + pub fn selection_ranges<'a>( + &'a self, + set_id: SelectionSetId, + ) -> Result> + 'a> { + let selections = self + .selections + .get(&set_id) + .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?; + Ok(selections.iter().map(move |selection| { + let start = selection.start.to_point(self).unwrap(); + let end = selection.end.to_point(self).unwrap(); + if selection.reversed { + end..start + } else { + start..end + } + })) + } + + pub fn all_selections(&self) -> impl Iterator)> { + self.selections.iter() + } + + pub fn all_selection_ranges<'a>( + &'a self, + ) -> impl 'a + Iterator>)> { + self.selections + .keys() + .map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap().collect())) + } + + fn merge_selections(&mut self, selections: &mut Vec) { + let mut new_selections = Vec::with_capacity(selections.len()); + { + let mut old_selections = selections.drain(..); + if let Some(mut prev_selection) = old_selections.next() { + for selection in old_selections { + if prev_selection.end.cmp(&selection.start, self).unwrap() >= Ordering::Equal { + if selection.end.cmp(&prev_selection.end, self).unwrap() > Ordering::Equal { + prev_selection.end = selection.end; + } + } else { + new_selections.push(mem::replace(&mut prev_selection, selection)); + } + } + new_selections.push(prev_selection); + } + } + *selections = new_selections; + } + + fn selections_from_ranges(&self, ranges: I) -> Result> + where + I: IntoIterator>, + { + let mut ranges = ranges.into_iter().collect::>(); + ranges.sort_unstable_by_key(|range| range.start); + + let mut selections = Vec::with_capacity(ranges.len()); + for range in ranges { + if range.start > range.end { + selections.push(Selection { + start: self.anchor_before(range.end)?, + end: self.anchor_before(range.start)?, + reversed: true, + }); + } else { + selections.push(Selection { + start: self.anchor_after(range.start)?, + end: self.anchor_before(range.end)?, + reversed: false, + }); + } + } + Ok(selections) + } + + pub fn apply_ops>( + &mut self, + ops: I, + ctx: Option<&mut ModelContext>, + ) -> Result<()> { + let old_version = self.version.clone(); + + let mut deferred_ops = Vec::new(); + for op in ops { + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + self.flush_deferred_ops()?; + + if let Some(ctx) = ctx { + ctx.notify(); + let changes = self.edits_since(old_version).collect::>(); + if !changes.is_empty() { + ctx.emit(Event::Edited(changes)); + } + } + + Ok(()) + } + + fn apply_op(&mut self, op: Operation) -> Result<()> { + match op { + Operation::Edit { + start_id, + start_offset, + end_id, + end_offset, + new_text, + version_in_range, + local_timestamp, + lamport_timestamp, + } => { + if !self.version.observed(local_timestamp) { + self.apply_edit( + start_id, + start_offset, + end_id, + end_offset, + new_text.as_ref().cloned(), + &version_in_range, + local_timestamp, + lamport_timestamp, + )?; + self.version.observe(local_timestamp); + } + } + Operation::UpdateSelections { + set_id, + selections, + lamport_timestamp, + } => { + if let Some(selections) = selections { + self.selections.insert(set_id, selections); + } else { + self.selections.remove(&set_id); + } + self.lamport_clock.observe(lamport_timestamp); + self.selections_last_update += 1; + } + } + Ok(()) + } + + fn apply_edit( + &mut self, + start_id: time::Local, + start_offset: usize, + end_id: time::Local, + end_offset: usize, + new_text: Option, + version_in_range: &time::Global, + local_timestamp: time::Local, + lamport_timestamp: time::Lamport, + ) -> Result<()> { + let mut new_text = new_text.as_ref().cloned(); + let start_fragment_id = self.resolve_fragment_id(start_id, start_offset)?; + let end_fragment_id = self.resolve_fragment_id(end_id, end_offset)?; + + let old_fragments = self.fragments.clone(); + let last_id = old_fragments.extent::().0.unwrap(); + let last_id_ref = FragmentIdRef::new(&last_id); + + let mut cursor = old_fragments.cursor::(); + let mut new_fragments = + cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left); + + if start_offset == cursor.item().unwrap().end_offset() { + new_fragments.push(cursor.item().unwrap().clone()); + cursor.next(); + } + + while let Some(fragment) = cursor.item() { + if new_text.is_none() && fragment.id > end_fragment_id { + break; + } + + let mut fragment = fragment.clone(); + + if fragment.id == start_fragment_id || fragment.id == end_fragment_id { + let split_start = if start_fragment_id == fragment.id { + start_offset + } else { + fragment.start_offset() + }; + let split_end = if end_fragment_id == fragment.id { + end_offset + } else { + fragment.end_offset() + }; + let (before_range, within_range, after_range) = self.split_fragment( + cursor.prev_item().as_ref().unwrap(), + &fragment, + split_start..split_end, + ); + let insertion = if let Some(new_text) = new_text.take() { + Some(self.build_fragment_to_insert( + before_range.as_ref().or(cursor.prev_item()).unwrap(), + within_range.as_ref().or(after_range.as_ref()), + new_text, + local_timestamp, + lamport_timestamp, + )) + } else { + None + }; + if let Some(fragment) = before_range { + new_fragments.push(fragment); + } + if let Some(fragment) = insertion { + new_fragments.push(fragment); + } + if let Some(mut fragment) = within_range { + if version_in_range.observed(fragment.insertion.id) { + fragment.deletions.insert(local_timestamp); + } + new_fragments.push(fragment); + } + if let Some(fragment) = after_range { + new_fragments.push(fragment); + } + } else { + if new_text.is_some() && lamport_timestamp > fragment.insertion.lamport_timestamp { + new_fragments.push(self.build_fragment_to_insert( + cursor.prev_item().as_ref().unwrap(), + Some(&fragment), + new_text.take().unwrap(), + local_timestamp, + lamport_timestamp, + )); + } + + if fragment.id < end_fragment_id && version_in_range.observed(fragment.insertion.id) + { + fragment.deletions.insert(local_timestamp); + } + new_fragments.push(fragment); + } + + cursor.next(); + } + + if let Some(new_text) = new_text { + new_fragments.push(self.build_fragment_to_insert( + cursor.prev_item().as_ref().unwrap(), + None, + new_text, + local_timestamp, + lamport_timestamp, + )); + } + + new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right)); + self.fragments = new_fragments; + self.local_clock.observe(local_timestamp); + self.lamport_clock.observe(lamport_timestamp); + Ok(()) + } + + fn flush_deferred_ops(&mut self) -> Result<()> { + self.deferred_replicas.clear(); + let mut deferred_ops = Vec::new(); + for op in self.deferred_ops.drain().cursor().cloned() { + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + Ok(()) + } + + fn can_apply_op(&self, op: &Operation) -> bool { + if self.deferred_replicas.contains(&op.replica_id()) { + false + } else { + match op { + Operation::Edit { + start_id, + end_id, + version_in_range, + .. + } => { + self.version.observed(*start_id) + && self.version.observed(*end_id) + && *version_in_range <= self.version + } + Operation::UpdateSelections { selections, .. } => { + if let Some(selections) = selections { + selections.iter().all(|selection| { + let contains_start = match selection.start { + Anchor::Middle { insertion_id, .. } => { + self.version.observed(insertion_id) + } + _ => true, + }; + let contains_end = match selection.end { + Anchor::Middle { insertion_id, .. } => { + self.version.observed(insertion_id) + } + _ => true, + }; + contains_start && contains_end + }) + } else { + true + } + } + } + } + } + + fn resolve_fragment_id(&self, edit_id: time::Local, offset: usize) -> Result { + let split_tree = self + .insertion_splits + .get(&edit_id) + .ok_or_else(|| anyhow!("invalid operation"))?; + let mut cursor = split_tree.cursor::(); + cursor.seek(&offset, SeekBias::Left); + Ok(cursor + .item() + .ok_or_else(|| anyhow!("invalid operation"))? + .fragment_id + .clone()) + } + + fn splice_fragments(&mut self, mut old_ranges: I, new_text: Option) -> Vec + where + I: Iterator>, + { + let mut cur_range = old_ranges.next(); + if cur_range.is_none() { + return Vec::new(); + } + + let mut ops = Vec::with_capacity(old_ranges.size_hint().0); + + let old_fragments = self.fragments.clone(); + let mut cursor = old_fragments.cursor::(); + let mut new_fragments = SumTree::new(); + new_fragments.push_tree(cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right)); + + let mut start_id = None; + let mut start_offset = None; + let mut end_id = None; + let mut end_offset = None; + let mut version_in_range = time::Global::new(); + + let mut local_timestamp = self.local_clock.tick(); + let mut lamport_timestamp = self.lamport_clock.tick(); + + while cur_range.is_some() && cursor.item().is_some() { + let mut fragment = cursor.item().unwrap().clone(); + let mut fragment_start = *cursor.start(); + let mut fragment_end = fragment_start + fragment.visible_len(); + + let old_split_tree = self + .insertion_splits + .remove(&fragment.insertion.id) + .unwrap(); + let mut splits_cursor = old_split_tree.cursor::(); + let mut new_split_tree = splits_cursor.slice(&fragment.start_offset(), SeekBias::Right); + + // Find all splices that start or end within the current fragment. Then, split the + // fragment and reassemble it in both trees accounting for the deleted and the newly + // inserted text. + while cur_range.as_ref().map_or(false, |r| r.start < fragment_end) { + let range = cur_range.clone().unwrap(); + if range.start > fragment_start { + let mut prefix = fragment.clone(); + prefix.set_end_offset(prefix.start_offset() + (range.start - fragment_start)); + prefix.id = + FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id); + fragment.set_start_offset(prefix.end_offset()); + new_fragments.push(prefix.clone()); + new_split_tree.push(InsertionSplit { + extent: prefix.end_offset() - prefix.start_offset(), + fragment_id: prefix.id, + }); + fragment_start = range.start; + } + + if range.end == fragment_start { + end_id = Some(new_fragments.last().unwrap().insertion.id); + end_offset = Some(new_fragments.last().unwrap().end_offset()); + } else if range.end == fragment_end { + end_id = Some(fragment.insertion.id); + end_offset = Some(fragment.end_offset()); + } + + if range.start == fragment_start { + start_id = Some(new_fragments.last().unwrap().insertion.id); + start_offset = Some(new_fragments.last().unwrap().end_offset()); + + if let Some(new_text) = new_text.clone() { + let new_fragment = self.build_fragment_to_insert( + &new_fragments.last().unwrap(), + Some(&fragment), + new_text, + local_timestamp, + lamport_timestamp, + ); + new_fragments.push(new_fragment); + } + } + + if range.end < fragment_end { + if range.end > fragment_start { + let mut prefix = fragment.clone(); + prefix.set_end_offset(prefix.start_offset() + (range.end - fragment_start)); + prefix.id = + FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id); + if fragment.is_visible() { + prefix.deletions.insert(local_timestamp); + } + fragment.set_start_offset(prefix.end_offset()); + new_fragments.push(prefix.clone()); + new_split_tree.push(InsertionSplit { + extent: prefix.end_offset() - prefix.start_offset(), + fragment_id: prefix.id, + }); + fragment_start = range.end; + end_id = Some(fragment.insertion.id); + end_offset = Some(fragment.start_offset()); + version_in_range.observe(fragment.insertion.id); + } + } else { + version_in_range.observe(fragment.insertion.id); + if fragment.is_visible() { + fragment.deletions.insert(local_timestamp); + } + } + + // If the splice ends inside this fragment, we can advance to the next splice and + // check if it also intersects the current fragment. Otherwise we break out of the + // loop and find the first fragment that the splice does not contain fully. + if range.end <= fragment_end { + ops.push(Operation::Edit { + start_id: start_id.unwrap(), + start_offset: start_offset.unwrap(), + end_id: end_id.unwrap(), + end_offset: end_offset.unwrap(), + version_in_range, + new_text: new_text.clone(), + local_timestamp, + lamport_timestamp, + }); + + start_id = None; + start_offset = None; + end_id = None; + end_offset = None; + version_in_range = time::Global::new(); + cur_range = old_ranges.next(); + if cur_range.is_some() { + local_timestamp = self.local_clock.tick(); + lamport_timestamp = self.lamport_clock.tick(); + } + } else { + break; + } + } + new_split_tree.push(InsertionSplit { + extent: fragment.end_offset() - fragment.start_offset(), + fragment_id: fragment.id.clone(), + }); + splits_cursor.next(); + new_split_tree + .push_tree(splits_cursor.slice(&old_split_tree.extent::(), SeekBias::Right)); + self.insertion_splits + .insert(fragment.insertion.id, new_split_tree); + new_fragments.push(fragment); + + // Scan forward until we find a fragment that is not fully contained by the current splice. + cursor.next(); + if let Some(range) = cur_range.clone() { + while let Some(fragment) = cursor.item() { + fragment_start = *cursor.start(); + fragment_end = fragment_start + fragment.visible_len(); + if range.start < fragment_start && range.end >= fragment_end { + let mut new_fragment = fragment.clone(); + if new_fragment.is_visible() { + new_fragment.deletions.insert(local_timestamp); + } + version_in_range.observe(new_fragment.insertion.id); + new_fragments.push(new_fragment); + cursor.next(); + + if range.end == fragment_end { + end_id = Some(fragment.insertion.id); + end_offset = Some(fragment.end_offset()); + ops.push(Operation::Edit { + start_id: start_id.unwrap(), + start_offset: start_offset.unwrap(), + end_id: end_id.unwrap(), + end_offset: end_offset.unwrap(), + version_in_range, + new_text: new_text.clone(), + local_timestamp, + lamport_timestamp, + }); + + start_id = None; + start_offset = None; + end_id = None; + end_offset = None; + version_in_range = time::Global::new(); + + cur_range = old_ranges.next(); + if cur_range.is_some() { + local_timestamp = self.local_clock.tick(); + lamport_timestamp = self.lamport_clock.tick(); + } + break; + } + } else { + break; + } + } + + // If the splice we are currently evaluating starts after the end of the fragment + // that the cursor is parked at, we should seek to the next splice's start range + // and push all the fragments in between into the new tree. + if cur_range.as_ref().map_or(false, |r| r.start > fragment_end) { + new_fragments.push_tree( + cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right), + ); + } + } + } + + // Handle range that is at the end of the buffer if it exists. There should never be + // multiple because ranges must be disjoint. + if cur_range.is_some() { + debug_assert_eq!(old_ranges.next(), None); + let last_fragment = new_fragments.last().unwrap(); + ops.push(Operation::Edit { + start_id: last_fragment.insertion.id, + start_offset: last_fragment.end_offset(), + end_id: last_fragment.insertion.id, + end_offset: last_fragment.end_offset(), + version_in_range: time::Global::new(), + new_text: new_text.clone(), + local_timestamp, + lamport_timestamp, + }); + + if let Some(new_text) = new_text { + let new_fragment = self.build_fragment_to_insert( + &last_fragment, + None, + new_text, + local_timestamp, + lamport_timestamp, + ); + new_fragments.push(new_fragment); + } + } else { + new_fragments + .push_tree(cursor.slice(&old_fragments.extent::(), SeekBias::Right)); + } + + self.fragments = new_fragments; + ops + } + + fn split_fragment( + &mut self, + prev_fragment: &Fragment, + fragment: &Fragment, + range: Range, + ) -> (Option, Option, Option) { + debug_assert!(range.start >= fragment.start_offset()); + debug_assert!(range.start <= fragment.end_offset()); + debug_assert!(range.end <= fragment.end_offset()); + debug_assert!(range.end >= fragment.start_offset()); + + if range.end == fragment.start_offset() { + (None, None, Some(fragment.clone())) + } else if range.start == fragment.end_offset() { + (Some(fragment.clone()), None, None) + } else if range.start == fragment.start_offset() && range.end == fragment.end_offset() { + (None, Some(fragment.clone()), None) + } else { + let mut prefix = fragment.clone(); + + let after_range = if range.end < fragment.end_offset() { + let mut suffix = prefix.clone(); + suffix.set_start_offset(range.end); + prefix.set_end_offset(range.end); + prefix.id = FragmentId::between(&prev_fragment.id, &suffix.id); + Some(suffix) + } else { + None + }; + + let within_range = if range.start != range.end { + let mut suffix = prefix.clone(); + suffix.set_start_offset(range.start); + prefix.set_end_offset(range.start); + prefix.id = FragmentId::between(&prev_fragment.id, &suffix.id); + Some(suffix) + } else { + None + }; + + let before_range = if range.start > fragment.start_offset() { + Some(prefix) + } else { + None + }; + + let old_split_tree = self + .insertion_splits + .remove(&fragment.insertion.id) + .unwrap(); + let mut cursor = old_split_tree.cursor::(); + let mut new_split_tree = cursor.slice(&fragment.start_offset(), SeekBias::Right); + + if let Some(ref fragment) = before_range { + new_split_tree.push(InsertionSplit { + extent: range.start - fragment.start_offset(), + fragment_id: fragment.id.clone(), + }); + } + + if let Some(ref fragment) = within_range { + new_split_tree.push(InsertionSplit { + extent: range.end - range.start, + fragment_id: fragment.id.clone(), + }); + } + + if let Some(ref fragment) = after_range { + new_split_tree.push(InsertionSplit { + extent: fragment.end_offset() - range.end, + fragment_id: fragment.id.clone(), + }); + } + + cursor.next(); + new_split_tree + .push_tree(cursor.slice(&old_split_tree.extent::(), SeekBias::Right)); + + self.insertion_splits + .insert(fragment.insertion.id, new_split_tree); + + (before_range, within_range, after_range) + } + } + + fn build_fragment_to_insert( + &mut self, + prev_fragment: &Fragment, + next_fragment: Option<&Fragment>, + text: Text, + local_timestamp: time::Local, + lamport_timestamp: time::Lamport, + ) -> Fragment { + let new_fragment_id = FragmentId::between( + &prev_fragment.id, + next_fragment + .map(|f| &f.id) + .unwrap_or(&FragmentId::max_value()), + ); + + let mut split_tree = SumTree::new(); + split_tree.push(InsertionSplit { + extent: text.len(), + fragment_id: new_fragment_id.clone(), + }); + self.insertion_splits.insert(local_timestamp, split_tree); + + Fragment::new( + new_fragment_id, + Insertion { + id: local_timestamp, + parent_id: prev_fragment.insertion.id, + offset_in_parent: prev_fragment.end_offset(), + text, + lamport_timestamp, + }, + ) + } + + pub fn anchor_before(&self, position: T) -> Result { + self.anchor_at(position, AnchorBias::Left) + } + + pub fn anchor_after(&self, position: T) -> Result { + self.anchor_at(position, AnchorBias::Right) + } + + pub fn anchor_at(&self, position: T, bias: AnchorBias) -> Result { + let offset = position.to_offset(self)?; + let max_offset = self.len(); + if offset > max_offset { + return Err(anyhow!("offset is out of range")); + } + + let seek_bias; + match bias { + AnchorBias::Left => { + if offset == 0 { + return Ok(Anchor::Start); + } else { + seek_bias = SeekBias::Left; + } + } + AnchorBias::Right => { + if offset == max_offset { + return Ok(Anchor::End); + } else { + seek_bias = SeekBias::Right; + } + } + }; + + let mut cursor = self.fragments.cursor::(); + cursor.seek(&offset, seek_bias); + let fragment = cursor.item().unwrap(); + let offset_in_fragment = offset - cursor.start(); + let offset_in_insertion = fragment.start_offset() + offset_in_fragment; + let anchor = Anchor::Middle { + insertion_id: fragment.insertion.id, + offset: offset_in_insertion, + bias, + }; + Ok(anchor) + } + + fn fragment_id_for_anchor(&self, anchor: &Anchor) -> Result<&FragmentId> { + match anchor { + Anchor::Start => Ok(FragmentId::max_value()), + Anchor::End => Ok(FragmentId::min_value()), + Anchor::Middle { + insertion_id, + offset, + bias, + .. + } => { + let seek_bias = match bias { + AnchorBias::Left => SeekBias::Left, + AnchorBias::Right => SeekBias::Right, + }; + + let splits = self + .insertion_splits + .get(&insertion_id) + .ok_or_else(|| anyhow!("split does not exist for insertion id"))?; + let mut splits_cursor = splits.cursor::(); + splits_cursor.seek(offset, seek_bias); + splits_cursor + .item() + .ok_or_else(|| anyhow!("split offset is out of range")) + .map(|split| &split.fragment_id) + } + } + } + + fn summary_for_anchor(&self, anchor: &Anchor) -> Result { + match anchor { + Anchor::Start => Ok(TextSummary::default()), + Anchor::End => Ok(self.fragments.summary().text_summary), + Anchor::Middle { + insertion_id, + offset, + bias, + } => { + let seek_bias = match bias { + AnchorBias::Left => SeekBias::Left, + AnchorBias::Right => SeekBias::Right, + }; + + let splits = self + .insertion_splits + .get(&insertion_id) + .ok_or_else(|| anyhow!("split does not exist for insertion id"))?; + let mut splits_cursor = splits.cursor::(); + splits_cursor.seek(offset, seek_bias); + let split = splits_cursor + .item() + .ok_or_else(|| anyhow!("split offset is out of range"))?; + + let mut fragments_cursor = self.fragments.cursor::(); + fragments_cursor.seek(&FragmentIdRef::new(&split.fragment_id), SeekBias::Left); + let fragment = fragments_cursor + .item() + .ok_or_else(|| anyhow!("fragment id does not exist"))?; + + let mut summary = fragments_cursor.start().clone(); + if fragment.is_visible() { + summary += fragment + .text + .slice(..offset - fragment.start_offset()) + .summary(); + } + Ok(summary) + } + } + } + + #[allow(dead_code)] + pub fn point_for_offset(&self, offset: usize) -> Result { + let mut fragments_cursor = self.fragments.cursor::(); + fragments_cursor.seek(&offset, SeekBias::Left); + fragments_cursor + .item() + .ok_or_else(|| anyhow!("offset is out of range")) + .map(|fragment| { + let overshoot = fragment + .point_for_offset(offset - &fragments_cursor.start().chars) + .unwrap(); + fragments_cursor.start().lines + &overshoot + }) + } +} + +impl Clone for Buffer { + fn clone(&self) -> Self { + Self { + file: self.file.clone(), + fragments: self.fragments.clone(), + insertion_splits: self.insertion_splits.clone(), + version: self.version.clone(), + last_edit: self.last_edit.clone(), + selections: self.selections.clone(), + selections_last_update: self.selections_last_update.clone(), + deferred_ops: self.deferred_ops.clone(), + deferred_replicas: self.deferred_replicas.clone(), + replica_id: self.replica_id, + local_clock: self.local_clock.clone(), + lamport_clock: self.lamport_clock.clone(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Event { + Edited(Vec), +} + +impl app::Entity for Buffer { + type Event = Event; +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Point { + fn add_summary(&mut self, summary: &FragmentSummary) { + *self += &summary.text_summary.lines; + } +} + +impl<'a> Iterator for Chars<'a> { + type Item = char; + + fn next(&mut self) -> Option { + if let Some(char) = self.fragment_chars.next() { + Some(char) + } else { + loop { + self.fragments_cursor.next(); + if let Some(fragment) = self.fragments_cursor.item() { + if fragment.is_visible() { + self.fragment_chars = fragment.text.as_str().chars(); + return self.fragment_chars.next(); + } + } else { + return None; + } + } + } + } +} + +impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { + type Item = Edit; + + fn next(&mut self) -> Option { + let mut change: Option = None; + + while let Some(fragment) = self.cursor.item() { + let new_offset = *self.cursor.start(); + let old_offset = (new_offset as isize - self.delta) as usize; + + if !fragment.was_visible(&self.since) && fragment.is_visible() { + if let Some(ref mut change) = change { + if change.new_range.end == new_offset { + change.new_range.end += fragment.len(); + self.delta += fragment.len() as isize; + } else { + break; + } + } else { + change = Some(Edit { + old_range: old_offset..old_offset, + new_range: new_offset..new_offset + fragment.len(), + }); + self.delta += fragment.len() as isize; + } + } else if fragment.was_visible(&self.since) && !fragment.is_visible() { + if let Some(ref mut change) = change { + if change.new_range.end == new_offset { + change.old_range.end += fragment.len(); + self.delta -= fragment.len() as isize; + } else { + break; + } + } else { + change = Some(Edit { + old_range: old_offset..old_offset + fragment.len(), + new_range: new_offset..new_offset, + }); + self.delta -= fragment.len() as isize; + } + } + + self.cursor.next(); + } + + change + } +} + +// 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 +// } + +impl Selection { + pub fn head(&self) -> &Anchor { + if self.reversed { + &self.start + } else { + &self.end + } + } + + pub fn set_head(&mut self, buffer: &Buffer, cursor: Anchor) { + if cursor.cmp(self.tail(), buffer).unwrap() < Ordering::Equal { + if !self.reversed { + mem::swap(&mut self.start, &mut self.end); + self.reversed = true; + } + self.start = cursor; + } else { + if self.reversed { + mem::swap(&mut self.start, &mut self.end); + self.reversed = false; + } + self.end = cursor; + } + } + + pub fn tail(&self) -> &Anchor { + if self.reversed { + &self.end + } else { + &self.start + } + } + + pub fn is_empty(&self, buffer: &Buffer) -> bool { + self.start.to_offset(buffer).unwrap() == self.end.to_offset(buffer).unwrap() + } + + pub fn anchor_range(&self) -> Range { + self.start.clone()..self.end.clone() + } +} + +#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] +struct FragmentId(Arc<[u16]>); + +lazy_static! { + static ref FRAGMENT_ID_EMPTY: FragmentId = FragmentId(Arc::from([])); + static ref FRAGMENT_ID_MIN_VALUE: FragmentId = FragmentId(Arc::from([0 as u16])); + static ref FRAGMENT_ID_MAX_VALUE: FragmentId = FragmentId(Arc::from([u16::max_value()])); +} + +impl Default for FragmentId { + fn default() -> Self { + FRAGMENT_ID_EMPTY.clone() + } +} + +impl FragmentId { + fn min_value() -> &'static Self { + &FRAGMENT_ID_MIN_VALUE + } + + fn max_value() -> &'static Self { + &FRAGMENT_ID_MAX_VALUE + } + + fn between(left: &Self, right: &Self) -> Self { + Self::between_with_max(left, right, u16::max_value()) + } + + fn between_with_max(left: &Self, right: &Self, max_value: u16) -> Self { + let mut new_entries = Vec::new(); + + let left_entries = left.0.iter().cloned().chain(iter::repeat(0)); + let right_entries = right.0.iter().cloned().chain(iter::repeat(max_value)); + for (l, r) in left_entries.zip(right_entries) { + let interval = r - l; + if interval > 1 { + new_entries.push(l + cmp::max(1, cmp::min(8, interval / 2))); + break; + } else { + new_entries.push(l); + } + } + + FragmentId(Arc::from(new_entries)) + } +} + +#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Debug, Default)] +struct FragmentIdRef<'a>(Option<&'a FragmentId>); + +impl<'a> FragmentIdRef<'a> { + fn new(id: &'a FragmentId) -> Self { + Self(Some(id)) + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentIdRef<'a> { + fn add_summary(&mut self, summary: &'a FragmentSummary) { + self.0 = Some(&summary.max_fragment_id) + } +} + +impl Fragment { + fn new(id: FragmentId, insertion: Insertion) -> Self { + Self { + id, + text: insertion.text.clone(), + insertion, + deletions: HashSet::new(), + } + } + + fn start_offset(&self) -> usize { + self.text.range().start + } + + fn set_start_offset(&mut self, offset: usize) { + self.text = self.insertion.text.slice(offset..self.end_offset()); + } + + fn end_offset(&self) -> usize { + self.text.range().end + } + + fn set_end_offset(&mut self, offset: usize) { + self.text = self.insertion.text.slice(self.start_offset()..offset); + } + + fn as_str(&self) -> &str { + self.text.as_str() + } + + fn visible_len(&self) -> usize { + if self.is_visible() { + self.len() + } else { + 0 + } + } + + fn len(&self) -> usize { + self.text.len() + } + + fn is_visible(&self) -> bool { + self.deletions.is_empty() + } + + fn was_visible(&self, version: &time::Global) -> bool { + version.observed(self.insertion.id) && self.deletions.iter().all(|d| !version.observed(*d)) + } + + fn point_for_offset(&self, offset: usize) -> Result { + Ok(self.text.point_for_offset(offset)) + } + + fn offset_for_point(&self, point: Point) -> Result { + Ok(self.text.offset_for_point(point)) + } +} + +impl sum_tree::Item for Fragment { + type Summary = FragmentSummary; + + fn summary(&self) -> Self::Summary { + let mut max_version = time::Global::new(); + max_version.observe(self.insertion.id); + for deletion in &self.deletions { + max_version.observe(*deletion); + } + + if self.is_visible() { + FragmentSummary { + text_summary: self.text.summary(), + max_fragment_id: self.id.clone(), + max_version, + } + } else { + FragmentSummary { + text_summary: TextSummary::default(), + max_fragment_id: self.id.clone(), + max_version, + } + } + } +} + +impl<'a> AddAssign<&'a FragmentSummary> for FragmentSummary { + fn add_assign(&mut self, other: &Self) { + self.text_summary += &other.text_summary; + debug_assert!(self.max_fragment_id <= other.max_fragment_id); + self.max_fragment_id = other.max_fragment_id.clone(); + self.max_version.observe_all(&other.max_version); + } +} + +impl Default for FragmentSummary { + fn default() -> Self { + FragmentSummary { + text_summary: TextSummary::default(), + max_fragment_id: FragmentId::min_value().clone(), + max_version: time::Global::new(), + } + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for TextSummary { + fn add_summary(&mut self, summary: &FragmentSummary) { + *self += &summary.text_summary; + } +} + +impl<'a> AddAssign<&'a FragmentExtent> for FragmentExtent { + fn add_assign(&mut self, other: &Self) { + self.chars += other.chars; + self.lines += &other.lines; + } +} + +impl Default for FragmentExtent { + fn default() -> Self { + FragmentExtent { + lines: Point::zero(), + chars: 0, + } + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentExtent { + fn add_summary(&mut self, summary: &FragmentSummary) { + self.chars += summary.text_summary.chars; + self.lines += &summary.text_summary.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for usize { + fn add_summary(&mut self, summary: &FragmentSummary) { + *self += summary.text_summary.chars; + } +} + +impl sum_tree::Item for InsertionSplit { + type Summary = InsertionSplitSummary; + + fn summary(&self) -> Self::Summary { + InsertionSplitSummary { + extent: self.extent, + } + } +} + +impl<'a> AddAssign<&'a InsertionSplitSummary> for InsertionSplitSummary { + fn add_assign(&mut self, other: &Self) { + self.extent += other.extent; + } +} + +impl Default for InsertionSplitSummary { + fn default() -> Self { + InsertionSplitSummary { extent: 0 } + } +} + +impl<'a> sum_tree::Dimension<'a, InsertionSplitSummary> for usize { + fn add_summary(&mut self, summary: &InsertionSplitSummary) { + *self += &summary.extent; + } +} + +impl Operation { + fn replica_id(&self) -> ReplicaId { + self.lamport_timestamp().replica_id + } + + fn lamport_timestamp(&self) -> time::Lamport { + match self { + Operation::Edit { + lamport_timestamp, .. + } => *lamport_timestamp, + Operation::UpdateSelections { + lamport_timestamp, .. + } => *lamport_timestamp, + } + } + + pub fn is_edit(&self) -> bool { + match self { + Operation::Edit { .. } => true, + _ => false, + } + } +} + +impl operation_queue::Operation for Operation { + fn timestamp(&self) -> time::Lamport { + self.lamport_timestamp() + } +} + +pub trait ToOffset { + fn to_offset(&self, buffer: &Buffer) -> Result; +} + +impl ToOffset for Point { + fn to_offset(&self, buffer: &Buffer) -> Result { + let mut fragments_cursor = buffer.fragments.cursor::(); + fragments_cursor.seek(self, SeekBias::Left); + fragments_cursor + .item() + .ok_or_else(|| anyhow!("point is out of range")) + .map(|fragment| { + let overshoot = fragment + .offset_for_point(*self - fragments_cursor.start().lines) + .unwrap(); + fragments_cursor.start().chars + overshoot + }) + } +} + +impl ToOffset for usize { + fn to_offset(&self, _: &Buffer) -> Result { + Ok(*self) + } +} + +impl ToOffset for Anchor { + fn to_offset(&self, buffer: &Buffer) -> Result { + Ok(buffer.summary_for_anchor(self)?.chars) + } +} + +pub trait ToPoint { + fn to_point(&self, buffer: &Buffer) -> Result; +} + +impl ToPoint for Anchor { + fn to_point(&self, buffer: &Buffer) -> Result { + Ok(buffer.summary_for_anchor(self)?.lines) + } +} + +impl ToPoint for usize { + fn to_point(&self, buffer: &Buffer) -> Result { + let mut fragments_cursor = buffer.fragments.cursor::(); + fragments_cursor.seek(&self, SeekBias::Left); + fragments_cursor + .item() + .ok_or_else(|| anyhow!("offset is out of range")) + .map(|fragment| { + let overshoot = fragment + .point_for_offset(*self - &fragments_cursor.start().chars) + .unwrap(); + fragments_cursor.start().lines + overshoot + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::BTreeMap; + + #[test] + fn test_edit() -> Result<()> { + let mut buffer = Buffer::new(0, "abc"); + assert_eq!(buffer.text(), "abc"); + buffer.edit(vec![3..3], "def", None)?; + assert_eq!(buffer.text(), "abcdef"); + buffer.edit(vec![0..0], "ghi", None)?; + assert_eq!(buffer.text(), "ghiabcdef"); + buffer.edit(vec![5..5], "jkl", None)?; + assert_eq!(buffer.text(), "ghiabjklcdef"); + buffer.edit(vec![6..7], "", None)?; + assert_eq!(buffer.text(), "ghiabjlcdef"); + buffer.edit(vec![4..9], "mno", None)?; + assert_eq!(buffer.text(), "ghiamnoef"); + + Ok(()) + } + + #[test] + fn test_edit_events() { + use crate::app::App; + use std::{cell::RefCell, rc::Rc}; + + let mut app = App::new().unwrap(); + + let buffer_1_events = Rc::new(RefCell::new(Vec::new())); + let buffer_2_events = Rc::new(RefCell::new(Vec::new())); + + let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef")); + let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef")); + let ops = buffer1.update(&mut app, |buffer, ctx| { + let buffer_1_events = buffer_1_events.clone(); + ctx.subscribe(&buffer1, move |_, event, _| { + buffer_1_events.borrow_mut().push(event.clone()) + }); + let buffer_2_events = buffer_2_events.clone(); + ctx.subscribe(&buffer2, move |_, event, _| { + buffer_2_events.borrow_mut().push(event.clone()) + }); + + buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap() + }); + buffer2.update(&mut app, |buffer, ctx| { + buffer.apply_ops(ops, Some(ctx)).unwrap(); + }); + + let buffer_1_events = buffer_1_events.borrow(); + assert_eq!( + *buffer_1_events, + vec![Event::Edited(vec![Edit { + old_range: 2..4, + new_range: 2..5 + }])] + ); + + let buffer_2_events = buffer_2_events.borrow(); + assert_eq!( + *buffer_2_events, + vec![Event::Edited(vec![Edit { + old_range: 2..4, + new_range: 2..5 + }])] + ); + } + + #[test] + fn test_random_edits() { + for seed in 0..100 { + println!("{:?}", seed); + let mut rng = &mut StdRng::seed_from_u64(seed); + + let reference_string_len = rng.gen_range(0, 3); + let mut reference_string = RandomCharIter::new(&mut rng) + .take(reference_string_len) + .collect::(); + let mut buffer = Buffer::new(0, reference_string.as_str()); + let mut buffer_versions = Vec::new(); + + for _i in 0..10 { + let (old_ranges, new_text, _) = buffer.randomly_mutate(rng, None); + for old_range in old_ranges.iter().rev() { + reference_string = [ + &reference_string[0..old_range.start], + new_text.as_str(), + &reference_string[old_range.end..], + ] + .concat(); + } + assert_eq!(buffer.text(), reference_string); + + { + let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len()); + + for (len, rows) in &line_lengths { + for row in rows { + assert_eq!(buffer.line_len(*row).unwrap(), *len); + } + } + + let (longest_column, longest_rows) = line_lengths.iter().next_back().unwrap(); + let rightmost_point = buffer.rightmost_point(); + assert_eq!(rightmost_point.column, *longest_column); + assert!(longest_rows.contains(&rightmost_point.row)); + } + + for _ in 0..5 { + let end = rng.gen_range(0, buffer.len() + 1); + let start = rng.gen_range(0, end + 1); + + let line_lengths = line_lengths_in_range(&buffer, start..end); + let (longest_column, longest_rows) = line_lengths.iter().next_back().unwrap(); + let range_sum = buffer.text_summary_for_range(start..end); + assert_eq!(range_sum.rightmost_point.column, *longest_column); + assert!(longest_rows.contains(&range_sum.rightmost_point.row)); + let range_text = &buffer.text()[start..end]; + assert_eq!(range_sum.chars, range_text.chars().count()); + assert_eq!(range_sum.bytes, range_text.len()); + } + + if rng.gen_bool(0.3) { + buffer_versions.push(buffer.clone()); + } + } + + for mut old_buffer in buffer_versions { + let mut delta = 0_isize; + for Edit { + old_range, + new_range, + } in buffer.edits_since(old_buffer.version.clone()) + { + 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; + + old_buffer + .edit( + Some(old_start..old_start + old_len), + buffer.text_for_range(new_range).unwrap(), + None, + ) + .unwrap(); + + delta += new_len as isize - old_len as isize; + } + assert_eq!(old_buffer.text(), buffer.text()); + } + } + } + + #[test] + fn test_line_len() -> Result<()> { + let mut buffer = Buffer::new(0, ""); + buffer.edit(vec![0..0], "abcd\nefg\nhij", None)?; + buffer.edit(vec![12..12], "kl\nmno", None)?; + buffer.edit(vec![18..18], "\npqrs\n", None)?; + buffer.edit(vec![18..21], "\nPQ", None)?; + + assert_eq!(buffer.line_len(0)?, 4); + assert_eq!(buffer.line_len(1)?, 3); + assert_eq!(buffer.line_len(2)?, 5); + assert_eq!(buffer.line_len(3)?, 3); + assert_eq!(buffer.line_len(4)?, 4); + assert_eq!(buffer.line_len(5)?, 0); + assert!(buffer.line_len(6).is_err()); + + Ok(()) + } + + #[test] + fn test_rightmost_point() -> Result<()> { + let mut buffer = Buffer::new(0, ""); + assert_eq!(buffer.rightmost_point().row, 0); + buffer.edit(vec![0..0], "abcd\nefg\nhij", None)?; + assert_eq!(buffer.rightmost_point().row, 0); + buffer.edit(vec![12..12], "kl\nmno", None)?; + assert_eq!(buffer.rightmost_point().row, 2); + buffer.edit(vec![18..18], "\npqrs", None)?; + assert_eq!(buffer.rightmost_point().row, 2); + buffer.edit(vec![10..12], "", None)?; + assert_eq!(buffer.rightmost_point().row, 0); + buffer.edit(vec![24..24], "tuv", None)?; + assert_eq!(buffer.rightmost_point().row, 4); + + println!("{:?}", buffer.text()); + + Ok(()) + } + + #[test] + fn test_text_summary_for_range() { + let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz"); + let text = Text::from(buffer.text()); + + assert_eq!( + buffer.text_summary_for_range(1..3), + text.slice(1..3).summary() + ); + assert_eq!( + buffer.text_summary_for_range(1..12), + text.slice(1..12).summary() + ); + assert_eq!( + buffer.text_summary_for_range(0..20), + text.slice(0..20).summary() + ); + assert_eq!( + buffer.text_summary_for_range(0..22), + text.slice(0..22).summary() + ); + assert_eq!( + buffer.text_summary_for_range(7..22), + text.slice(7..22).summary() + ); + } + + #[test] + fn test_chars_at() -> Result<()> { + let mut buffer = Buffer::new(0, ""); + buffer.edit(vec![0..0], "abcd\nefgh\nij", None)?; + buffer.edit(vec![12..12], "kl\nmno", None)?; + buffer.edit(vec![18..18], "\npqrs", None)?; + buffer.edit(vec![18..21], "\nPQ", None)?; + + let chars = buffer.chars_at(Point::new(0, 0))?; + assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(1, 0))?; + assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(2, 0))?; + assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(3, 0))?; + assert_eq!(chars.collect::(), "mno\nPQrs"); + + let chars = buffer.chars_at(Point::new(4, 0))?; + assert_eq!(chars.collect::(), "PQrs"); + + // Regression test: + let mut buffer = Buffer::new(0, ""); + buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", None)?; + buffer.edit(vec![60..60], "\n", None)?; + + let chars = buffer.chars_at(Point::new(6, 0))?; + assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); + + Ok(()) + } + + // #[test] + // fn test_point_for_offset() -> Result<()> { + // let text = Text::from("abc\ndefgh\nijklm\nopq"); + // assert_eq!(text.point_for_offset(0)?, Point { row: 0, column: 0 }); + // assert_eq!(text.point_for_offset(1)?, Point { row: 0, column: 1 }); + // assert_eq!(text.point_for_offset(2)?, Point { row: 0, column: 2 }); + // assert_eq!(text.point_for_offset(3)?, Point { row: 0, column: 3 }); + // assert_eq!(text.point_for_offset(4)?, Point { row: 1, column: 0 }); + // assert_eq!(text.point_for_offset(5)?, Point { row: 1, column: 1 }); + // assert_eq!(text.point_for_offset(9)?, Point { row: 1, column: 5 }); + // assert_eq!(text.point_for_offset(10)?, Point { row: 2, column: 0 }); + // assert_eq!(text.point_for_offset(14)?, Point { row: 2, column: 4 }); + // assert_eq!(text.point_for_offset(15)?, Point { row: 2, column: 5 }); + // assert_eq!(text.point_for_offset(16)?, Point { row: 3, column: 0 }); + // assert_eq!(text.point_for_offset(17)?, Point { row: 3, column: 1 }); + // assert_eq!(text.point_for_offset(19)?, Point { row: 3, column: 3 }); + // assert!(text.point_for_offset(20).is_err()); + // + // let text = Text::from("abc"); + // assert_eq!(text.point_for_offset(0)?, Point { row: 0, column: 0 }); + // assert_eq!(text.point_for_offset(1)?, Point { row: 0, column: 1 }); + // assert_eq!(text.point_for_offset(2)?, Point { row: 0, column: 2 }); + // assert_eq!(text.point_for_offset(3)?, Point { row: 0, column: 3 }); + // assert!(text.point_for_offset(4).is_err()); + // Ok(()) + // } + + // #[test] + // fn test_offset_for_point() -> Result<()> { + // let text = Text::from("abc\ndefgh"); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 0 })?, 0); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 1 })?, 1); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 2 })?, 2); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 3 })?, 3); + // assert!(text.offset_for_point(Point { row: 0, column: 4 }).is_err()); + // assert_eq!(text.offset_for_point(Point { row: 1, column: 0 })?, 4); + // assert_eq!(text.offset_for_point(Point { row: 1, column: 1 })?, 5); + // assert_eq!(text.offset_for_point(Point { row: 1, column: 5 })?, 9); + // assert!(text.offset_for_point(Point { row: 1, column: 6 }).is_err()); + // + // let text = Text::from("abc"); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 0 })?, 0); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 1 })?, 1); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 2 })?, 2); + // assert_eq!(text.offset_for_point(Point { row: 0, column: 3 })?, 3); + // assert!(text.offset_for_point(Point { row: 0, column: 4 }).is_err()); + // Ok(()) + // } + + // #[test] + // fn test_longest_row_in_range() -> Result<()> { + // for seed in 0..100 { + // println!("{:?}", seed); + // let mut rng = &mut StdRng::seed_from_u64(seed); + // let string_len = rng.gen_range(1, 10); + // let string = RandomCharIter(&mut rng) + // .take(string_len) + // .collect::(); + // let text = Text::from(string.as_ref()); + // + // for _i in 0..10 { + // let end = rng.gen_range(1, string.len() + 1); + // let start = rng.gen_range(0, end); + // + // let mut cur_row = string[0..start].chars().filter(|c| *c == '\n').count() as u32; + // let mut cur_row_len = 0; + // let mut expected_longest_row = cur_row; + // let mut expected_longest_row_len = cur_row_len; + // for ch in string[start..end].chars() { + // if ch == '\n' { + // if cur_row_len > expected_longest_row_len { + // expected_longest_row = cur_row; + // expected_longest_row_len = cur_row_len; + // } + // cur_row += 1; + // cur_row_len = 0; + // } else { + // cur_row_len += 1; + // } + // } + // if cur_row_len > expected_longest_row_len { + // expected_longest_row = cur_row; + // expected_longest_row_len = cur_row_len; + // } + // + // assert_eq!( + // text.longest_row_in_range(start..end)?, + // (expected_longest_row, expected_longest_row_len) + // ); + // } + // } + // Ok(()) + // } + + #[test] + fn test_fragment_ids() { + for seed in 0..10 { + let rng = &mut StdRng::seed_from_u64(seed); + + let mut ids = vec![FragmentId(Arc::from([0])), FragmentId(Arc::from([4]))]; + for _i in 0..100 { + let index = rng.gen_range(1, ids.len()); + + let left = ids[index - 1].clone(); + let right = ids[index].clone(); + ids.insert(index, FragmentId::between_with_max(&left, &right, 4)); + + let mut sorted_ids = ids.clone(); + sorted_ids.sort(); + assert_eq!(ids, sorted_ids); + } + } + } + + #[test] + fn test_anchors() -> Result<()> { + let mut buffer = Buffer::new(0, ""); + buffer.edit(vec![0..0], "abc", None)?; + let left_anchor = buffer.anchor_before(2).unwrap(); + let right_anchor = buffer.anchor_after(2).unwrap(); + + buffer.edit(vec![1..1], "def\n", None)?; + assert_eq!(buffer.text(), "adef\nbc"); + assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 6); + assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 6); + assert_eq!( + left_anchor.to_point(&buffer).unwrap(), + Point { row: 1, column: 1 } + ); + assert_eq!( + right_anchor.to_point(&buffer).unwrap(), + Point { row: 1, column: 1 } + ); + + buffer.edit(vec![2..3], "", None)?; + assert_eq!(buffer.text(), "adf\nbc"); + assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5); + assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 5); + assert_eq!( + left_anchor.to_point(&buffer).unwrap(), + Point { row: 1, column: 1 } + ); + assert_eq!( + right_anchor.to_point(&buffer).unwrap(), + Point { row: 1, column: 1 } + ); + + buffer.edit(vec![5..5], "ghi\n", None)?; + assert_eq!(buffer.text(), "adf\nbghi\nc"); + assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5); + assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 9); + assert_eq!( + left_anchor.to_point(&buffer).unwrap(), + Point { row: 1, column: 1 } + ); + assert_eq!( + right_anchor.to_point(&buffer).unwrap(), + Point { row: 2, column: 0 } + ); + + buffer.edit(vec![7..9], "", None)?; + assert_eq!(buffer.text(), "adf\nbghc"); + assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5); + assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 7); + assert_eq!( + left_anchor.to_point(&buffer).unwrap(), + Point { row: 1, column: 1 }, + ); + assert_eq!( + right_anchor.to_point(&buffer).unwrap(), + Point { row: 1, column: 3 } + ); + + // Ensure anchoring to a point is equivalent to anchoring to an offset. + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 0 })?, + buffer.anchor_before(0)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 1 })?, + buffer.anchor_before(1)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 2 })?, + buffer.anchor_before(2)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 3 })?, + buffer.anchor_before(3)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 0 })?, + buffer.anchor_before(4)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 1 })?, + buffer.anchor_before(5)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 2 })?, + buffer.anchor_before(6)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 3 })?, + buffer.anchor_before(7)? + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 4 })?, + buffer.anchor_before(8)? + ); + + // Comparison between anchors. + let anchor_at_offset_0 = buffer.anchor_before(0).unwrap(); + let anchor_at_offset_1 = buffer.anchor_before(1).unwrap(); + let anchor_at_offset_2 = buffer.anchor_before(2).unwrap(); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer)?, + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer)?, + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer)?, + Ordering::Equal + ); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer)?, + Ordering::Less + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer)?, + Ordering::Less + ); + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer)?, + Ordering::Less + ); + + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer)?, + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer)?, + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer)?, + Ordering::Greater + ); + Ok(()) + } + + #[test] + fn test_anchors_at_start_and_end() -> Result<()> { + let mut buffer = Buffer::new(0, ""); + let before_start_anchor = buffer.anchor_before(0).unwrap(); + let after_end_anchor = buffer.anchor_after(0).unwrap(); + + buffer.edit(vec![0..0], "abc", None)?; + assert_eq!(buffer.text(), "abc"); + assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0); + assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 3); + + let after_start_anchor = buffer.anchor_after(0).unwrap(); + let before_end_anchor = buffer.anchor_before(3).unwrap(); + + buffer.edit(vec![3..3], "def", None)?; + buffer.edit(vec![0..0], "ghi", None)?; + assert_eq!(buffer.text(), "ghiabcdef"); + assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0); + assert_eq!(after_start_anchor.to_offset(&buffer).unwrap(), 3); + assert_eq!(before_end_anchor.to_offset(&buffer).unwrap(), 6); + assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9); + + Ok(()) + } + + #[test] + fn test_is_modified() -> Result<()> { + let mut buffer = Buffer::new(0, "abc"); + assert!(!buffer.is_modified()); + buffer.edit(vec![1..2], "", None)?; + assert!(buffer.is_modified()); + + Ok(()) + } + + #[test] + fn test_random_concurrent_edits() { + use crate::tests::Network; + + const PEERS: usize = 3; + + for seed in 0..50 { + println!("{:?}", seed); + let mut rng = &mut StdRng::seed_from_u64(seed); + + let base_text_len = rng.gen_range(0, 10); + let base_text = RandomCharIter::new(&mut rng) + .take(base_text_len) + .collect::(); + let mut replica_ids = Vec::new(); + let mut buffers = Vec::new(); + let mut network = Network::new(); + for i in 0..PEERS { + let buffer = Buffer::new(i as ReplicaId, base_text.as_str()); + buffers.push(buffer); + replica_ids.push(i as u16); + network.add_peer(i as u16); + } + + let mut mutation_count = 10; + loop { + let replica_index = rng.gen_range(0, PEERS); + let replica_id = replica_ids[replica_index]; + let buffer = &mut buffers[replica_index]; + if mutation_count > 0 && rng.gen() { + let (_, _, ops) = buffer.randomly_mutate(&mut rng, None); + network.broadcast(replica_id, ops, &mut rng); + mutation_count -= 1; + } else if network.has_unreceived(replica_id) { + buffer + .apply_ops(network.receive(replica_id, &mut rng), None) + .unwrap(); + } + + if mutation_count == 0 && network.is_idle() { + break; + } + } + + for buffer in &buffers[1..] { + assert_eq!(buffer.text(), buffers[0].text()); + assert_eq!( + buffer.all_selections().collect::>(), + buffers[0].all_selections().collect::>() + ); + assert_eq!( + buffer.all_selection_ranges().collect::>(), + buffers[0].all_selection_ranges().collect::>() + ); + } + } + } + + impl Buffer { + pub fn randomly_mutate( + &mut self, + rng: &mut T, + ctx: Option<&mut ModelContext>, + ) -> (Vec>, String, Vec) + where + T: Rng, + { + // Randomly edit + let (old_ranges, new_text, mut operations) = self.randomly_edit(rng, 5, ctx); + + // Randomly add, remove or mutate selection sets. + let replica_selection_sets = &self + .all_selections() + .map(|(set_id, _)| *set_id) + .filter(|set_id| self.replica_id == set_id.replica_id) + .collect::>(); + let set_id = replica_selection_sets.choose(rng); + if set_id.is_some() && rng.gen_bool(1.0 / 6.0) { + let op = self.remove_selection_set(*set_id.unwrap()).unwrap(); + operations.push(op); + } else { + let mut ranges = Vec::new(); + for _ in 0..5 { + let start = rng.gen_range(0, self.len() + 1); + let start_point = self.point_for_offset(start).unwrap(); + let end = rng.gen_range(0, self.len() + 1); + let end_point = self.point_for_offset(end).unwrap(); + ranges.push(start_point..end_point); + } + + let op = if set_id.is_none() || rng.gen_bool(1.0 / 5.0) { + self.add_selection_set(ranges).unwrap().1 + } else { + self.replace_selection_set(*set_id.unwrap(), ranges) + .unwrap() + }; + operations.push(op); + } + + (old_ranges, new_text, operations) + } + } + + fn line_lengths_in_range(buffer: &Buffer, range: Range) -> BTreeMap> { + let mut lengths = BTreeMap::new(); + for (row, line) in buffer.text()[range].lines().enumerate() { + lengths + .entry(line.len() as u32) + .or_insert(HashSet::new()) + .insert(row as u32); + } + if lengths.is_empty() { + let mut rows = HashSet::new(); + rows.insert(0); + lengths.insert(0, rows); + } + lengths + } +} diff --git a/zed/src/editor/buffer/point.rs b/zed/src/editor/buffer/point.rs new file mode 100644 index 0000000000..d4ecc69e0c --- /dev/null +++ b/zed/src/editor/buffer/point.rs @@ -0,0 +1,100 @@ +use std::{ + cmp::Ordering, + ops::{Add, AddAssign, Sub}, +}; + +#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)] +pub struct Point { + pub row: u32, + pub column: u32, +} + +impl Point { + pub fn new(row: u32, column: u32) -> Self { + Point { row, column } + } + + pub fn zero() -> Self { + Point::new(0, 0) + } + + pub fn is_zero(&self) -> bool { + self.row == 0 && self.column == 0 + } +} + +impl<'a> Add<&'a Self> for Point { + type Output = Point; + + fn add(self, other: &'a Self) -> Self::Output { + if other.row == 0 { + Point::new(self.row, self.column + other.column) + } else { + Point::new(self.row + other.row, other.column) + } + } +} + +impl Add for Point { + type Output = Point; + + fn add(self, other: Self) -> Self::Output { + self + &other + } +} + +impl<'a> Sub<&'a Self> for Point { + type Output = Point; + + fn sub(self, other: &'a Self) -> Self::Output { + debug_assert!(*other <= self); + + if self.row == other.row { + Point::new(0, self.column - other.column) + } else { + Point::new(self.row - other.row, self.column) + } + } +} + +impl Sub for Point { + type Output = Point; + + fn sub(self, other: Self) -> Self::Output { + self - &other + } +} + +impl<'a> AddAssign<&'a Self> for Point { + fn add_assign(&mut self, other: &'a Self) { + if other.row == 0 { + self.column += other.column; + } else { + self.row += other.row; + self.column = other.column; + } + } +} + +impl PartialOrd for Point { + fn partial_cmp(&self, other: &Point) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Point { + #[cfg(target_pointer_width = "64")] + fn cmp(&self, other: &Point) -> Ordering { + let a = (self.row as usize) << 32 | self.column as usize; + let b = (other.row as usize) << 32 | other.column as usize; + a.cmp(&b) + } + + #[cfg(target_pointer_width = "32")] + fn cmp(&self, other: &Point) -> Ordering { + match self.row.cmp(&other.row) { + Ordering::Equal => self.column.cmp(&other.column), + comparison @ _ => comparison, + } + } +} diff --git a/zed/src/editor/buffer/text.rs b/zed/src/editor/buffer/text.rs new file mode 100644 index 0000000000..342fa38904 --- /dev/null +++ b/zed/src/editor/buffer/text.rs @@ -0,0 +1,445 @@ +use super::Point; +use crate::sum_tree::{self, SeekBias, SumTree}; +use arrayvec::ArrayVec; +use std::{ + cmp, + fmt::{self, Debug}, + ops::{Bound, Index, Range, RangeBounds}, + sync::Arc, +}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum Run { + Newline, + Chars { len: usize, char_size: u8 }, +} + +#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +struct ByteOffset(usize); + +impl sum_tree::Item for Run { + type Summary = TextSummary; + + fn summary(&self) -> Self::Summary { + match *self { + Run::Newline => TextSummary { + chars: 1, + bytes: 1, + lines: Point::new(1, 0), + first_line_len: 0, + rightmost_point: Point::new(0, 0), + }, + Run::Chars { len, char_size } => TextSummary { + chars: len, + bytes: len * char_size as usize, + lines: Point::new(0, len as u32), + first_line_len: len as u32, + rightmost_point: Point::new(0, len as u32), + }, + } + } +} + +impl Run { + fn char_size(&self) -> u8 { + match self { + Run::Newline => 1, + Run::Chars { char_size, .. } => *char_size, + } + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct TextSummary { + pub chars: usize, + pub bytes: usize, + pub lines: Point, + pub first_line_len: u32, + pub rightmost_point: Point, +} + +impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { + fn add_assign(&mut self, other: &'a Self) { + let joined_line_len = self.lines.column + other.first_line_len; + if joined_line_len > self.rightmost_point.column { + self.rightmost_point = Point::new(self.lines.row, joined_line_len); + } + if other.rightmost_point.column > self.rightmost_point.column { + self.rightmost_point = self.lines + &other.rightmost_point; + } + + if self.lines.row == 0 { + self.first_line_len += other.first_line_len; + } + + self.chars += other.chars; + self.bytes += other.bytes; + self.lines += &other.lines; + } +} + +impl std::ops::AddAssign for TextSummary { + fn add_assign(&mut self, other: Self) { + *self += &other; + } +} + +impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary { + fn add_summary(&mut self, summary: &TextSummary) { + *self += summary; + } +} + +impl<'a> sum_tree::Dimension<'a, TextSummary> for Point { + fn add_summary(&mut self, summary: &TextSummary) { + *self += &summary.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, TextSummary> for ByteOffset { + fn add_summary(&mut self, summary: &TextSummary) { + self.0 += summary.bytes + } +} + +impl<'a> sum_tree::Dimension<'a, TextSummary> for usize { + fn add_summary(&mut self, summary: &TextSummary) { + *self += summary.chars; + } +} + +#[derive(Clone)] +pub struct Text { + text: Arc, + runs: SumTree, + range: Range, +} + +impl From for Text { + fn from(text: String) -> Self { + let mut runs = Vec::new(); + + let mut chars_len = 0; + let mut run_char_size = 0; + let mut run_chars = 0; + + let mut chars = text.chars(); + loop { + let ch = chars.next(); + let ch_size = ch.map_or(0, |ch| ch.len_utf8()); + if run_chars != 0 && (ch.is_none() || ch == Some('\n') || run_char_size != ch_size) { + runs.push(Run::Chars { + len: run_chars, + char_size: run_char_size as u8, + }); + run_chars = 0; + } + run_char_size = ch_size; + + match ch { + Some('\n') => runs.push(Run::Newline), + Some(_) => run_chars += 1, + None => break, + } + chars_len += 1; + } + + let mut tree = SumTree::new(); + tree.extend(runs); + Text { + text: text.into(), + runs: tree, + range: 0..chars_len, + } + } +} + +impl<'a> From<&'a str> for Text { + fn from(text: &'a str) -> Self { + Self::from(String::from(text)) + } +} + +impl Debug for Text { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Text").field(&self.text).finish() + } +} + +impl PartialEq for Text { + fn eq(&self, other: &Self) -> bool { + self.text == other.text + } +} + +impl Eq for Text {} + +impl> Index for Text { + type Output = str; + + fn index(&self, range: T) -> &Self::Output { + let start = match range.start_bound() { + Bound::Included(start) => cmp::min(self.range.start + start, self.range.end), + Bound::Excluded(_) => unimplemented!(), + Bound::Unbounded => self.range.start, + }; + let end = match range.end_bound() { + Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end), + Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end), + Bound::Unbounded => self.range.end, + }; + + let byte_start = self.abs_byte_offset_for_offset(start); + let byte_end = self.abs_byte_offset_for_offset(end); + &self.text[byte_start..byte_end] + } +} + +impl Text { + pub fn range(&self) -> Range { + self.range.clone() + } + + pub fn as_str(&self) -> &str { + &self[..] + } + + pub fn slice>(&self, range: T) -> Text { + let start = match range.start_bound() { + Bound::Included(start) => cmp::min(self.range.start + start, self.range.end), + Bound::Excluded(_) => unimplemented!(), + Bound::Unbounded => self.range.start, + }; + let end = match range.end_bound() { + Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end), + Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end), + Bound::Unbounded => self.range.end, + }; + + Text { + text: self.text.clone(), + runs: self.runs.clone(), + range: start..end, + } + } + + pub fn line_len(&self, row: u32) -> u32 { + let mut cursor = self.runs.cursor::(); + cursor.seek(&self.range.start, SeekBias::Right); + let absolute_row = cursor.start().row + row; + + let mut cursor = self.runs.cursor::(); + cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right); + let prefix_len = self.range.start.saturating_sub(*cursor.start()); + let line_len = cursor.summary::(&Point::new(absolute_row + 1, 0), SeekBias::Left); + let suffix_len = cursor.start().saturating_sub(self.range.end); + + line_len + .saturating_sub(prefix_len) + .saturating_sub(suffix_len) as u32 + } + + pub fn len(&self) -> usize { + self.range.end - self.range.start + } + + pub fn lines(&self) -> Point { + self.abs_point_for_offset(self.range.end) - &self.abs_point_for_offset(self.range.start) + } + + pub fn rightmost_point(&self) -> Point { + let lines = self.lines(); + + let mut candidates = ArrayVec::<[Point; 3]>::new(); + candidates.push(lines); + if lines.row > 0 { + candidates.push(Point::new(0, self.line_len(0))); + if lines.row > 1 { + let mut cursor = self.runs.cursor::(); + cursor.seek(&self.range.start, SeekBias::Right); + let absolute_start_row = cursor.start().row; + + let mut cursor = self.runs.cursor::(); + cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right); + let summary = cursor.summary::( + &Point::new(absolute_start_row + lines.row, 0), + SeekBias::Left, + ); + + candidates.push(Point::new(1, 0) + &summary.rightmost_point); + } + } + + candidates.into_iter().max_by_key(|p| p.column).unwrap() + } + + pub fn point_for_offset(&self, offset: usize) -> Point { + self.abs_point_for_offset(self.range.start + offset) + - &self.abs_point_for_offset(self.range.start) + } + + pub fn offset_for_point(&self, point: Point) -> usize { + let mut cursor = self.runs.cursor::(); + let abs_point = self.abs_point_for_offset(self.range.start) + &point; + cursor.seek(&abs_point, SeekBias::Right); + let overshoot = abs_point - &cursor.start().lines; + let abs_offset = cursor.start().chars + overshoot.column as usize; + abs_offset - self.range.start + } + + pub fn summary(&self) -> TextSummary { + TextSummary { + chars: self.range.end - self.range.start, + bytes: self.abs_byte_offset_for_offset(self.range.end) + - self.abs_byte_offset_for_offset(self.range.start), + lines: self.abs_point_for_offset(self.range.end) + - &self.abs_point_for_offset(self.range.start), + first_line_len: self.line_len(0), + rightmost_point: self.rightmost_point(), + } + } + + fn abs_point_for_offset(&self, offset: usize) -> Point { + let mut cursor = self.runs.cursor::(); + cursor.seek(&offset, SeekBias::Right); + let overshoot = (offset - cursor.start().chars) as u32; + cursor.start().lines + &Point::new(0, overshoot) + } + + fn abs_byte_offset_for_offset(&self, offset: usize) -> usize { + let mut cursor = self.runs.cursor::(); + cursor.seek(&offset, SeekBias::Right); + let overshoot = offset - cursor.start().chars; + cursor.start().bytes + overshoot * cursor.item().map_or(0, |run| run.char_size()) as usize + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use std::iter::FromIterator; + + #[test] + fn test_basic() { + let text = Text::from(String::from("ab\ncd€\nfghij\nkl¢m")); + assert_eq!(text.len(), 17); + assert_eq!(text.as_str(), "ab\ncd€\nfghij\nkl¢m"); + assert_eq!(text.lines(), Point::new(3, 4)); + assert_eq!(text.line_len(0), 2); + assert_eq!(text.line_len(1), 3); + assert_eq!(text.line_len(2), 5); + assert_eq!(text.line_len(3), 4); + assert_eq!(text.rightmost_point(), Point::new(2, 5)); + + let b_to_g = text.slice(1..9); + assert_eq!(b_to_g.as_str(), "b\ncd€\nfg"); + assert_eq!(b_to_g.len(), 8); + assert_eq!(b_to_g.lines(), Point::new(2, 2)); + assert_eq!(b_to_g.line_len(0), 1); + assert_eq!(b_to_g.line_len(1), 3); + assert_eq!(b_to_g.line_len(2), 2); + assert_eq!(b_to_g.line_len(3), 0); + assert_eq!(b_to_g.rightmost_point(), Point::new(1, 3)); + + let d_to_i = text.slice(4..11); + assert_eq!(d_to_i.as_str(), "d€\nfghi"); + assert_eq!(&d_to_i[1..5], "€\nfg"); + assert_eq!(d_to_i.len(), 7); + assert_eq!(d_to_i.lines(), Point::new(1, 4)); + assert_eq!(d_to_i.line_len(0), 2); + assert_eq!(d_to_i.line_len(1), 4); + assert_eq!(d_to_i.line_len(2), 0); + assert_eq!(d_to_i.rightmost_point(), Point::new(1, 4)); + + let d_to_j = text.slice(4..=11); + assert_eq!(d_to_j.as_str(), "d€\nfghij"); + assert_eq!(&d_to_j[1..], "€\nfghij"); + assert_eq!(d_to_j.len(), 8); + } + + #[test] + fn test_random() { + use rand::prelude::*; + + for seed in 0..100 { + println!("buffer::text seed: {}", seed); + let rng = &mut StdRng::seed_from_u64(seed); + + let len = rng.gen_range(0, 50); + let mut string = String::new(); + for _ in 0..len { + if rng.gen_ratio(1, 5) { + string.push('\n'); + } else { + string.push(rng.gen()); + } + } + let text = Text::from(string.clone()); + + for _ in 0..10 { + let start = rng.gen_range(0, text.len() + 1); + let end = rng.gen_range(start, text.len() + 2); + + let string_slice = string + .chars() + .skip(start) + .take(end - start) + .collect::(); + let expected_line_endpoints = string_slice + .split('\n') + .enumerate() + .map(|(row, line)| Point::new(row as u32, line.chars().count() as u32)) + .collect::>(); + let text_slice = text.slice(start..end); + + assert_eq!(text_slice.lines(), lines(&string_slice)); + + let mut rightmost_points: HashSet = HashSet::new(); + for endpoint in &expected_line_endpoints { + if let Some(rightmost_point) = rightmost_points.iter().next().cloned() { + if endpoint.column > rightmost_point.column { + rightmost_points.clear(); + } + if endpoint.column >= rightmost_point.column { + rightmost_points.insert(*endpoint); + } + } else { + rightmost_points.insert(*endpoint); + } + + assert_eq!(text_slice.line_len(endpoint.row as u32), endpoint.column); + } + + assert!(rightmost_points.contains(&text_slice.rightmost_point())); + + for _ in 0..10 { + let offset = rng.gen_range(0, string_slice.chars().count() + 1); + let point = lines(&string_slice.chars().take(offset).collect::()); + assert_eq!(text_slice.point_for_offset(offset), point); + assert_eq!(text_slice.offset_for_point(point), offset); + if offset < string_slice.chars().count() { + assert_eq!( + &text_slice[offset..offset + 1], + String::from_iter(string_slice.chars().nth(offset)).as_str() + ); + } + } + } + } + } + + pub fn lines(s: &str) -> Point { + let mut row = 0; + let mut column = 0; + for ch in s.chars() { + if ch == '\n' { + row += 1; + column = 0; + } else { + column += 1; + } + } + Point::new(row, column) + } +} diff --git a/zed/src/editor/buffer_element.rs b/zed/src/editor/buffer_element.rs new file mode 100644 index 0000000000..92364442bc --- /dev/null +++ b/zed/src/editor/buffer_element.rs @@ -0,0 +1,765 @@ +use super::{BufferView, DisplayPoint, SelectAction}; +use crate::{ + app::{AppContext, MutableAppContext, ViewHandle}, + fonts::FontCache, + text_layout::{self, LayoutCache}, + ui::{ + AfterLayoutContext, Bump, Element, Event, EventContext, LayoutContext, PaintContext, + SizeConstraint, + }, +}; +use pathfinder_canvas::{ + ArcDirection, CanvasRenderingContext2D, ColorF, FillRule, FillStyle, Path2D, +}; +use pathfinder_color::ColorU; +use pathfinder_geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, +}; +use smallvec::SmallVec; +use std::{ + cmp::{self, Ordering}, + sync::Arc, +}; + +pub struct BufferElement { + view: ViewHandle, + layout: Option, + paint: Option, +} + +impl BufferElement { + pub fn new(view: ViewHandle) -> Self { + Self { + view, + layout: None, + paint: None, + } + } + + fn mouse_down( + &self, + position: Vector2F, + cmd: bool, + ctx: &mut EventContext, + app: &AppContext, + ) -> bool { + let layout = self.layout.as_ref().unwrap(); + let paint = self.paint.as_ref().unwrap(); + if paint.text_rect.contains_point(position) { + let view = self.view.as_ref(app); + let position = paint.point_for_position(view, layout, position, ctx.font_cache, app); + ctx.dispatch_action("buffer:select", SelectAction::Begin { position, add: cmd }); + true + } else { + false + } + } + + fn mouse_up(&self, _position: Vector2F, ctx: &mut EventContext, app: &AppContext) -> bool { + if self.view.as_ref(app).is_selecting() { + ctx.dispatch_action("buffer:select", SelectAction::End); + true + } else { + false + } + } + + fn mouse_dragged(&self, position: Vector2F, ctx: &mut EventContext, app: &AppContext) -> bool { + let view = self.view.as_ref(app); + let layout = self.layout.as_ref().unwrap(); + let paint = self.paint.as_ref().unwrap(); + + if view.is_selecting() { + let rect = self.paint.as_ref().unwrap().text_rect; + let mut scroll_delta = Vector2F::zero(); + + let vertical_margin = view.line_height(ctx.font_cache).min(rect.height() / 3.0); + let top = rect.origin_y() + vertical_margin; + let bottom = rect.lower_left().y() - vertical_margin; + if position.y() < top { + scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y())) + } + if position.y() > bottom { + scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom)) + } + + let horizontal_margin = view.line_height(ctx.font_cache).min(rect.width() / 3.0); + let left = rect.origin_x() + horizontal_margin; + let right = rect.upper_right().x() - horizontal_margin; + if position.x() < left { + scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta( + left - position.x(), + )) + } + if position.x() > right { + scroll_delta.set_x(scale_horizontal_mouse_autoscroll_delta( + position.x() - right, + )) + } + + ctx.dispatch_action( + "buffer:select", + SelectAction::Update { + position: paint.point_for_position(view, layout, position, ctx.font_cache, app), + scroll_position: (view.scroll_position() + scroll_delta).clamp( + Vector2F::zero(), + self.layout.as_ref().unwrap().scroll_max( + view, + ctx.font_cache, + ctx.text_layout_cache, + app, + ), + ), + }, + ); + true + } else { + false + } + } + + fn key_down(&self, chars: &str, ctx: &mut EventContext, app: &AppContext) -> bool { + if self.view.is_focused(app) { + if chars.is_empty() { + false + } else { + if chars.chars().any(|c| c.is_control()) { + false + } else { + ctx.dispatch_action("buffer:insert", chars.to_string()); + true + } + } + } else { + false + } + } + + fn scroll( + &self, + position: Vector2F, + delta: Vector2F, + precise: bool, + ctx: &mut EventContext, + app: &AppContext, + ) -> bool { + let paint = self.paint.as_ref().unwrap(); + + if !paint.rect.contains_point(position) { + return false; + } + + if !precise { + todo!("still need to handle non-precise scroll events from a mouse wheel"); + } + + let view = self.view.as_ref(app); + let font_cache = &ctx.font_cache; + let layout_cache = &ctx.text_layout_cache; + let max_glyph_width = view.em_width(font_cache); + let line_height = view.line_height(font_cache); + + let x = (view.scroll_position().x() * max_glyph_width - delta.x()) / max_glyph_width; + let y = (view.scroll_position().y() * line_height - delta.y()) / line_height; + let scroll_position = vec2f(x, y).clamp( + Vector2F::zero(), + self.layout + .as_ref() + .unwrap() + .scroll_max(view, font_cache, layout_cache, app), + ); + + ctx.dispatch_action("buffer:scroll", scroll_position); + + true + } + + fn paint_gutter(&mut self, rect: RectF, ctx: &mut PaintContext, app: &AppContext) { + if let Some(layout) = self.layout.as_ref() { + let view = self.view.as_ref(app); + let canvas = &mut ctx.canvas; + let font_cache = &ctx.font_cache; + let line_height = view.line_height(font_cache); + let scroll_top = view.scroll_position().y() * line_height; + + canvas.save(); + canvas.translate(rect.origin()); + canvas.set_fill_style(FillStyle::Color(ColorU::white())); + + let rect = RectF::new(Vector2F::zero(), rect.size()); + let mut rect_path = Path2D::new(); + rect_path.rect(rect); + canvas.clip_path(rect_path, FillRule::EvenOdd); + canvas.fill_rect(rect); + + for (ix, line) in layout.line_number_layouts.iter().enumerate() { + let line_origin = vec2f( + rect.width() - line.width - layout.gutter_padding, + ix as f32 * line_height - (scroll_top % line_height), + ); + line.paint( + line_origin, + rect, + &[(0..line.len, ColorU::black())], + canvas, + font_cache, + ); + } + + canvas.restore(); + } + } + + fn paint_text(&mut self, rect: RectF, ctx: &mut PaintContext, app: &AppContext) { + if let Some(layout) = self.layout.as_ref() { + let canvas = &mut ctx.canvas; + let font_cache = &ctx.font_cache; + + canvas.save(); + canvas.translate(rect.origin()); + canvas.set_fill_style(FillStyle::Color(ColorU::white())); + let rect = RectF::new(Vector2F::zero(), rect.size()); + let mut rect_path = Path2D::new(); + rect_path.rect(rect); + canvas.clip_path(rect_path, FillRule::EvenOdd); + canvas.fill_rect(rect); + + let view = self.view.as_ref(app); + let line_height = view.line_height(font_cache); + let descent = view.font_descent(font_cache); + let start_row = view.scroll_position().y() as u32; + let scroll_top = view.scroll_position().y() * line_height; + let end_row = ((scroll_top + rect.height()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen + let max_glyph_width = view.em_width(font_cache); + let scroll_left = view.scroll_position().x() * max_glyph_width; + + // Draw selections + canvas.save(); + let corner_radius = 2.5; + let mut cursors = SmallVec::<[Cursor; 32]>::new(); + + for selection in view.selections_in_range( + DisplayPoint::new(start_row, 0)..DisplayPoint::new(end_row, 0), + app, + ) { + if selection.start != selection.end { + let range_start = cmp::min(selection.start, selection.end); + let range_end = cmp::max(selection.start, selection.end); + let row_range = if range_end.column() == 0 { + cmp::max(range_start.row(), start_row)..cmp::min(range_end.row(), end_row) + } else { + cmp::max(range_start.row(), start_row) + ..cmp::min(range_end.row() + 1, end_row) + }; + + let selection = Selection { + line_height, + start_y: row_range.start as f32 * line_height - scroll_top, + lines: row_range + .into_iter() + .map(|row| { + let line_layout = &layout.line_layouts[(row - start_row) as usize]; + SelectionLine { + start_x: if row == range_start.row() { + line_layout.x_for_index(range_start.column() as usize) + - scroll_left + - descent + } else { + -scroll_left + }, + end_x: if row == range_end.row() { + line_layout.x_for_index(range_end.column() as usize) + - scroll_left + - descent + } else { + line_layout.width + corner_radius * 2.0 + - scroll_left + - descent + }, + } + }) + .collect(), + }; + + selection.paint(canvas); + } + + if view.cursors_visible() { + let cursor_position = selection.end; + if (start_row..end_row).contains(&cursor_position.row()) { + let cursor_row_layout = + &layout.line_layouts[(selection.end.row() - start_row) as usize]; + cursors.push(Cursor { + x: cursor_row_layout.x_for_index(selection.end.column() as usize) + - scroll_left + - descent, + y: selection.end.row() as f32 * line_height - scroll_top, + line_height, + }); + } + } + } + canvas.restore(); + + // Draw glyphs + + canvas.set_fill_style(FillStyle::Color(ColorU::black())); + + for (ix, line) in layout.line_layouts.iter().enumerate() { + let row = start_row + ix as u32; + let line_origin = vec2f( + -scroll_left - descent, + row as f32 * line_height - scroll_top, + ); + + line.paint( + line_origin, + rect, + &[(0..line.len, ColorU::black())], + canvas, + font_cache, + ); + } + + for cursor in cursors { + cursor.paint(canvas); + } + + canvas.restore() + } + } +} + +impl<'a> Element<'a> for BufferElement { + fn layout( + &mut self, + constraint: SizeConstraint, + _: &'a Bump, + ctx: &mut LayoutContext, + app: &AppContext, + ) -> Vector2F { + let mut size = constraint.max; + if size.y().is_infinite() { + let view = self.view.as_ref(app); + size.set_y((view.max_point(app).row() + 1) as f32 * view.line_height(ctx.font_cache)); + } + if size.x().is_infinite() { + unimplemented!("we don't yet handle an infinite width constraint on buffer elements"); + } + + let view = self.view.as_ref(app); + let font_cache = &ctx.font_cache; + let layout_cache = &ctx.text_layout_cache; + let line_height = view.line_height(font_cache); + + let gutter_padding; + let gutter_width; + if view.is_gutter_visible() { + gutter_padding = view.em_width(ctx.font_cache); + match view.max_line_number_width(ctx.font_cache, ctx.text_layout_cache, app) { + Err(error) => { + log::error!("error computing max line number width: {}", error); + return size; + } + Ok(width) => gutter_width = width + gutter_padding * 2.0, + } + } else { + gutter_padding = 0.0; + gutter_width = 0.0 + }; + + let gutter_size = vec2f(gutter_width, size.y()); + let text_size = size - vec2f(gutter_width, 0.0); + + let autoscroll_horizontally = view.autoscroll_vertically(size.y(), line_height, app); + + let line_number_layouts = if view.is_gutter_visible() { + match view.layout_line_numbers(size.y(), ctx.font_cache, ctx.text_layout_cache, app) { + Err(error) => { + log::error!("error laying out line numbers: {}", error); + return size; + } + Ok(layouts) => layouts, + } + } else { + Vec::new() + }; + + let start_row = view.scroll_position().y() as u32; + let scroll_top = view.scroll_position().y() * line_height; + let end_row = ((scroll_top + size.y()) / line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen + + let mut max_visible_line_width = 0.0; + let line_layouts = + match view.layout_lines(start_row..end_row, font_cache, layout_cache, app) { + Err(error) => { + log::error!("error laying out lines: {}", error); + return size; + } + Ok(layouts) => { + for line in &layouts { + if line.width > max_visible_line_width { + max_visible_line_width = line.width; + } + } + + layouts + } + }; + + self.layout = Some(LayoutState { + size, + gutter_size, + gutter_padding, + text_size, + line_layouts, + line_number_layouts, + max_visible_line_width, + autoscroll_horizontally, + }); + + size + } + + fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &mut MutableAppContext) { + let layout = self.layout.as_ref().unwrap(); + + let view = self.view.as_ref(app); + view.clamp_scroll_left( + layout + .scroll_max(view, ctx.font_cache, ctx.text_layout_cache, app.ctx()) + .x(), + ); + + if layout.autoscroll_horizontally { + view.autoscroll_horizontally( + view.scroll_position().y() as u32, + layout.text_size.x(), + layout.scroll_width(view, ctx.font_cache, ctx.text_layout_cache, app.ctx()), + view.em_width(ctx.font_cache), + &layout.line_layouts, + app.ctx(), + ); + } + } + + fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) { + let rect; + let gutter_rect; + let text_rect; + { + let layout = self.layout.as_ref().unwrap(); + rect = RectF::new(origin, layout.size); + gutter_rect = RectF::new(origin, layout.gutter_size); + text_rect = RectF::new( + origin + vec2f(layout.gutter_size.x(), 0.0), + layout.text_size, + ); + } + + if self.view.as_ref(app).is_gutter_visible() { + self.paint_gutter(gutter_rect, ctx, app); + } + self.paint_text(text_rect, ctx, app); + + self.paint = Some(PaintState { rect, text_rect }); + } + + fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool { + match event { + Event::LeftMouseDown { position, cmd } => self.mouse_down(*position, *cmd, ctx, app), + Event::LeftMouseUp { position } => self.mouse_up(*position, ctx, app), + Event::LeftMouseDragged { position } => self.mouse_dragged(*position, ctx, app), + Event::ScrollWheel { + position, + delta, + precise, + } => self.scroll(*position, *delta, *precise, ctx, app), + Event::KeyDown { chars, .. } => self.key_down(chars, ctx, app), + } + } + + fn size(&self) -> Option { + self.layout.as_ref().map(|layout| layout.size) + } +} + +struct LayoutState { + size: Vector2F, + gutter_size: Vector2F, + gutter_padding: f32, + text_size: Vector2F, + line_layouts: Vec>, + line_number_layouts: Vec>, + max_visible_line_width: f32, + autoscroll_horizontally: bool, +} + +impl LayoutState { + fn scroll_width( + &self, + view: &BufferView, + font_cache: &FontCache, + layout_cache: &LayoutCache, + app: &AppContext, + ) -> f32 { + let row = view.rightmost_point(app).row(); + let longest_line_width = view + .layout_line(row, font_cache, layout_cache, app) + .unwrap() + .width; + longest_line_width.max(self.max_visible_line_width) + view.em_width(font_cache) + } + + fn scroll_max( + &self, + view: &BufferView, + font_cache: &FontCache, + layout_cache: &LayoutCache, + app: &AppContext, + ) -> Vector2F { + vec2f( + ((self.scroll_width(view, font_cache, layout_cache, app) - self.text_size.x()) + / view.em_width(font_cache)) + .max(0.0), + view.max_point(app).row().saturating_sub(1) as f32, + ) + } +} + +struct PaintState { + rect: RectF, + text_rect: RectF, +} + +impl PaintState { + fn point_for_position( + &self, + view: &BufferView, + layout: &LayoutState, + position: Vector2F, + font_cache: &FontCache, + app: &AppContext, + ) -> DisplayPoint { + let scroll_position = view.scroll_position(); + let position = position - self.text_rect.origin(); + let y = position.y().max(0.0).min(layout.size.y()); + let row = ((y / view.line_height(font_cache)) + scroll_position.y()) as u32; + let row = cmp::min(row, view.max_point(app).row()); + let line = &layout.line_layouts[(row - scroll_position.y() as u32) as usize]; + let x = position.x() + (scroll_position.x() * view.em_width(font_cache)); + + let column = if x >= 0.0 { + line.index_for_x(x) + .map(|ix| ix as u32) + .unwrap_or(view.line_len(row, app).unwrap()) + } else { + 0 + }; + + DisplayPoint::new(row, column) + } +} + +struct Cursor { + x: f32, + y: f32, + line_height: f32, +} + +impl Cursor { + fn paint(&self, canvas: &mut CanvasRenderingContext2D) { + canvas.set_fill_style(FillStyle::Color(ColorU::black())); + canvas.fill_rect(RectF::new( + vec2f(self.x, self.y), + vec2f(2.0, self.line_height), + )); + } +} + +#[derive(Debug)] +struct Selection { + start_y: f32, + line_height: f32, + lines: Vec, +} + +#[derive(Debug)] +struct SelectionLine { + start_x: f32, + end_x: f32, +} + +impl Selection { + fn paint(&self, canvas: &mut CanvasRenderingContext2D) { + if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x { + self.paint_lines(self.start_y, &self.lines[0..1], canvas); + self.paint_lines(self.start_y + self.line_height, &self.lines[1..], canvas); + } else { + self.paint_lines(self.start_y, &self.lines, canvas); + } + } + + fn paint_lines( + &self, + start_y: f32, + lines: &[SelectionLine], + canvas: &mut CanvasRenderingContext2D, + ) { + use Direction::*; + + if lines.is_empty() { + return; + } + + let mut path = Path2D::new(); + let corner_radius = 0.08 * self.line_height; + + let first_line = lines.first().unwrap(); + let last_line = lines.last().unwrap(); + + let corner = vec2f(first_line.end_x, start_y); + path.move_to(corner - vec2f(corner_radius, 0.0)); + rounded_corner(&mut path, corner, corner_radius, Right, Down); + + let mut iter = lines.iter().enumerate().peekable(); + while let Some((ix, line)) = iter.next() { + let corner = vec2f(line.end_x, start_y + (ix + 1) as f32 * self.line_height); + + if let Some((_, next_line)) = iter.peek() { + let next_corner = vec2f(next_line.end_x, corner.y()); + + match next_corner.x().partial_cmp(&corner.x()).unwrap() { + Ordering::Equal => { + path.line_to(corner); + } + Ordering::Less => { + path.line_to(corner - vec2f(0.0, corner_radius)); + rounded_corner(&mut path, corner, corner_radius, Down, Left); + path.line_to(next_corner + vec2f(corner_radius, 0.0)); + rounded_corner(&mut path, next_corner, corner_radius, Left, Down); + } + Ordering::Greater => { + path.line_to(corner - vec2f(0.0, corner_radius)); + rounded_corner(&mut path, corner, corner_radius, Down, Right); + path.line_to(next_corner - vec2f(corner_radius, 0.0)); + rounded_corner(&mut path, next_corner, corner_radius, Right, Down); + } + } + } else { + path.line_to(corner - vec2f(0.0, corner_radius)); + rounded_corner(&mut path, corner, corner_radius, Down, Left); + + let corner = vec2f(line.start_x, corner.y()); + path.line_to(corner + vec2f(corner_radius, 0.0)); + rounded_corner(&mut path, corner, corner_radius, Left, Up); + } + } + + if first_line.start_x > last_line.start_x { + let corner = vec2f(last_line.start_x, start_y + self.line_height); + path.line_to(corner + vec2f(0.0, corner_radius)); + rounded_corner(&mut path, corner, corner_radius, Up, Right); + let corner = vec2f(first_line.start_x, corner.y()); + path.line_to(corner - vec2f(corner_radius, 0.0)); + rounded_corner(&mut path, corner, corner_radius, Right, Up); + } + + let corner = vec2f(first_line.start_x, start_y); + path.line_to(corner + vec2f(0.0, corner_radius)); + rounded_corner(&mut path, corner, corner_radius, Up, Right); + path.close_path(); + + canvas.set_fill_style(FillStyle::Color( + ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8(), + )); + canvas.fill_path(path, FillRule::Winding); + } +} + +enum Direction { + Up, + Down, + Left, + Right, +} + +fn rounded_corner( + path: &mut Path2D, + corner: Vector2F, + radius: f32, + incoming: Direction, + outgoing: Direction, +) { + use std::f32::consts::PI; + use Direction::*; + + match (incoming, outgoing) { + (Down, Right) => path.arc( + corner + vec2f(radius, -radius), + radius, + 1.0 * PI, + 0.5 * PI, + ArcDirection::CCW, + ), + (Down, Left) => path.arc( + corner + vec2f(-radius, -radius), + radius, + 0.0, + 0.5 * PI, + ArcDirection::CW, + ), + (Up, Right) => path.arc( + corner + vec2f(radius, radius), + radius, + 1.0 * PI, + 1.5 * PI, + ArcDirection::CW, + ), + (Up, Left) => path.arc( + corner + vec2f(-radius, radius), + radius, + 0.0, + 1.5 * PI, + ArcDirection::CCW, + ), + (Right, Up) => path.arc( + corner + vec2f(-radius, -radius), + radius, + 0.5 * PI, + 0.0, + ArcDirection::CCW, + ), + (Right, Down) => path.arc( + corner + vec2f(-radius, radius), + radius, + 1.5 * PI, + 2.0 * PI, + ArcDirection::CW, + ), + (Left, Up) => path.arc( + corner + vec2f(radius, -radius), + radius, + 0.5 * PI, + PI, + ArcDirection::CW, + ), + (Left, Down) => path.arc( + corner + vec2f(radius, radius), + radius, + 1.5 * PI, + PI, + ArcDirection::CCW, + ), + _ => panic!("invalid incoming and outgoing directions for a corner"), + } +} + +fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 { + delta.powf(1.5) / 100.0 +} + +fn scale_horizontal_mouse_autoscroll_delta(delta: f32) -> f32 { + delta.powf(1.2) / 300.0 +} diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs new file mode 100644 index 0000000000..428e801e3c --- /dev/null +++ b/zed/src/editor/buffer_view.rs @@ -0,0 +1,1521 @@ +use super::{ + buffer, movement, Anchor, Bias, Buffer, BufferElement, DisplayMap, DisplayPoint, Point, + ToOffset, ToPoint, +}; +use crate::{ + app::{self as app, App, AppContext, ModelHandle, ViewContext, WeakViewHandle}, + fonts::FontCache, + keymap::Binding, + settings::Settings, + text_layout, + ui::elements::*, + watch, workspace, +}; +use anyhow::Result; +use easy_parallel::Parallel; +use font_kit::properties::Properties as FontProperties; +use parking_lot::Mutex; +use pathfinder_geometry::vector::Vector2F; +use smallvec::SmallVec; +use smol::Timer; +use std::{ + cmp::{self, Ordering}, + fmt::Write, + mem, + ops::Range, + sync::Arc, + time::Duration, +}; +use text_layout::LayoutCache; + +const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); + +pub fn init(app: &mut App) { + app.add_bindings(vec![ + Binding::new("backspace", "buffer:backspace", Some("BufferView")), + Binding::new("enter", "buffer:newline", Some("BufferView")), + Binding::new("up", "buffer:move_up", Some("BufferView")), + Binding::new("down", "buffer:move_down", Some("BufferView")), + Binding::new("left", "buffer:move_left", Some("BufferView")), + Binding::new("right", "buffer:move_right", Some("BufferView")), + Binding::new("shift-up", "buffer:select_up", Some("BufferView")), + Binding::new("shift-down", "buffer:select_down", Some("BufferView")), + Binding::new("shift-left", "buffer:select_left", Some("BufferView")), + Binding::new("shift-right", "buffer:select_right", 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")), + Binding::new("alt-cmd-]", "buffer:unfold", Some("BufferView")), + Binding::new( + "alt-cmd-f", + "buffer:fold_selected_ranges", + Some("BufferView"), + ), + ]); + + app.add_action("buffer:scroll", BufferView::scroll); + app.add_action("buffer:select", BufferView::select); + app.add_action("buffer:insert", BufferView::insert); + app.add_action("buffer:newline", BufferView::newline); + app.add_action("buffer:backspace", BufferView::backspace); + app.add_action("buffer:move_up", BufferView::move_up); + app.add_action("buffer:move_down", BufferView::move_down); + app.add_action("buffer:move_left", BufferView::move_left); + app.add_action("buffer:move_right", BufferView::move_right); + app.add_action("buffer:select_up", BufferView::select_up); + app.add_action("buffer:select_down", BufferView::select_down); + app.add_action("buffer:select_left", BufferView::select_left); + app.add_action("buffer:select_right", BufferView::select_right); + 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); + app.add_action("buffer:unfold", BufferView::unfold); + app.add_action( + "buffer:fold_selected_ranges", + BufferView::fold_selected_ranges, + ); +} + +pub enum SelectAction { + Begin { + position: DisplayPoint, + add: bool, + }, + Update { + position: DisplayPoint, + scroll_position: Vector2F, + }, + End, +} + +impl workspace::Item for Buffer { + type View = BufferView; + + fn build_view( + buffer: ModelHandle, + settings: watch::Receiver, + ctx: &mut ViewContext, + ) -> Self::View { + BufferView::for_buffer(buffer, settings, ctx) + } +} + +pub struct BufferView { + handle: WeakViewHandle, + buffer: ModelHandle, + display_map: ModelHandle, + selections: Vec, + pending_selection: Option, + scroll_position: Mutex, + autoscroll_requested: Mutex, + settings: watch::Receiver, + focused: bool, + cursors_visible: bool, + blink_epoch: usize, + blinking_paused: bool, + single_line: bool, +} + +impl BufferView { + pub fn single_line(settings: watch::Receiver, ctx: &mut ViewContext) -> Self { + let buffer = ctx.add_model(|_| Buffer::new(0, String::new())); + let mut view = Self::for_buffer(buffer, settings, ctx); + view.single_line = true; + view + } + + pub fn for_buffer( + buffer: ModelHandle, + settings: watch::Receiver, + ctx: &mut ViewContext, + ) -> Self { + settings.notify_view_on_change(ctx); + + ctx.observe(&buffer, Self::on_buffer_changed); + ctx.subscribe_to_model(&buffer, Self::on_buffer_event); + let display_map = ctx.add_model(|ctx| { + DisplayMap::new( + buffer.clone(), + smol::block_on(settings.read()).tab_size, + ctx, + ) + }); + ctx.observe(&display_map, Self::on_display_map_changed); + + let buffer_ref = buffer.as_ref(ctx); + Self { + handle: ctx.handle(), + buffer, + display_map, + selections: vec![Selection { + start: buffer_ref.anchor_before(0).unwrap(), + end: buffer_ref.anchor_before(0).unwrap(), + reversed: false, + goal_column: None, + }], + pending_selection: None, + scroll_position: Mutex::new(Vector2F::zero()), + autoscroll_requested: Mutex::new(false), + settings, + focused: false, + cursors_visible: false, + blink_epoch: 0, + blinking_paused: false, + single_line: false, + } + } + + pub fn buffer(&self) -> &ModelHandle { + &self.buffer + } + + pub fn is_gutter_visible(&self) -> bool { + !self.single_line + } + + fn scroll(&mut self, scroll_position: &Vector2F, ctx: &mut ViewContext) { + *self.scroll_position.lock() = *scroll_position; + ctx.notify(); + } + + pub fn scroll_position(&self) -> Vector2F { + *self.scroll_position.lock() + } + + pub fn clamp_scroll_left(&self, max: f32) { + let mut scroll_position = self.scroll_position.lock(); + let scroll_left = scroll_position.x(); + scroll_position.set_x(scroll_left.min(max)); + } + + pub fn autoscroll_vertically( + &self, + viewport_height: f32, + line_height: f32, + app: &AppContext, + ) -> bool { + let mut scroll_position = self.scroll_position.lock(); + let scroll_top = scroll_position.y(); + scroll_position.set_y(scroll_top.min(self.max_point(app).row().saturating_sub(1) as f32)); + + let mut autoscroll_requested = self.autoscroll_requested.lock(); + if *autoscroll_requested { + *autoscroll_requested = false; + } else { + return false; + } + + let map = self.display_map.as_ref(app); + let visible_lines = viewport_height / line_height; + let first_cursor_top = self + .selections + .first() + .unwrap() + .head() + .to_display_point(map, app) + .unwrap() + .row() as f32; + let last_cursor_bottom = self + .selections + .last() + .unwrap() + .head() + .to_display_point(map, app) + .unwrap() + .row() as f32 + + 1.0; + + let margin = ((visible_lines - (last_cursor_bottom - first_cursor_top)) / 2.0) + .floor() + .min(3.0); + if margin < 0.0 { + return false; + } + + let target_top = (first_cursor_top - margin).max(0.0); + let target_bottom = last_cursor_bottom + margin; + let start_row = scroll_position.y(); + let end_row = start_row + visible_lines; + + if target_top < start_row { + scroll_position.set_y(target_top); + } else if target_bottom >= end_row { + scroll_position.set_y(target_bottom - visible_lines); + } + + true + } + + pub fn autoscroll_horizontally( + &self, + start_row: u32, + viewport_width: f32, + scroll_width: f32, + max_glyph_width: f32, + layouts: &[Arc], + app: &AppContext, + ) { + let map = self.display_map.as_ref(app); + + let mut target_left = std::f32::INFINITY; + let mut target_right = 0.0_f32; + for selection in &self.selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let start_column = head.column().saturating_sub(3); + let end_column = cmp::min(map.line_len(head.row(), app).unwrap(), head.column() + 3); + target_left = target_left + .min(layouts[(head.row() - start_row) as usize].x_for_index(start_column as usize)); + target_right = target_right.max( + layouts[(head.row() - start_row) as usize].x_for_index(end_column as usize) + + max_glyph_width, + ); + } + target_right = target_right.min(scroll_width); + + if target_right - target_left > viewport_width { + return; + } + + let mut scroll_position = self.scroll_position.lock(); + let scroll_left = scroll_position.x() * max_glyph_width; + let scroll_right = scroll_left + viewport_width; + + if target_left < scroll_left { + scroll_position.set_x(target_left / max_glyph_width); + } else if target_right > scroll_right { + scroll_position.set_x((target_right - viewport_width) / max_glyph_width); + } + } + + fn select(&mut self, arg: &SelectAction, ctx: &mut ViewContext) { + match arg { + SelectAction::Begin { position, add } => self.begin_selection(*position, *add, ctx), + SelectAction::Update { + position, + scroll_position, + } => self.update_selection(*position, *scroll_position, ctx), + SelectAction::End => self.end_selection(ctx), + } + } + + fn begin_selection(&mut self, position: DisplayPoint, add: bool, ctx: &mut ViewContext) { + if !self.focused { + ctx.focus_self(); + ctx.emit(Event::Activate); + } + + let display_map = self.display_map.as_ref(ctx); + let cursor = display_map + .anchor_before(position, Bias::Left, ctx.app()) + .unwrap(); + let selection = Selection { + start: cursor.clone(), + end: cursor, + reversed: false, + goal_column: None, + }; + + if !add { + self.selections.clear(); + } + self.pending_selection = Some(selection); + + ctx.notify(); + } + + fn update_selection( + &mut self, + position: DisplayPoint, + scroll_position: Vector2F, + ctx: &mut ViewContext, + ) { + let buffer = self.buffer.as_ref(ctx); + let map = self.display_map.as_ref(ctx); + let cursor = map.anchor_before(position, Bias::Left, ctx.app()).unwrap(); + if let Some(selection) = self.pending_selection.as_mut() { + selection.set_head(buffer, cursor); + } else { + log::error!("update_selection dispatched with no pending selection"); + return; + } + + *self.scroll_position.lock() = scroll_position; + + ctx.notify(); + } + + fn end_selection(&mut self, ctx: &mut ViewContext) { + if let Some(selection) = self.pending_selection.take() { + let ix = self.selection_insertion_index(&selection.start, ctx.app()); + self.selections.insert(ix, selection); + self.merge_selections(ctx); + ctx.notify(); + } else { + log::error!("end_selection dispatched with no pending selection"); + } + } + + pub fn is_selecting(&self) -> bool { + self.pending_selection.is_some() + } + + #[cfg(test)] + fn select_ranges(&mut self, ranges: T, ctx: &mut ViewContext) -> Result<()> + where + T: IntoIterator>, + { + let buffer = self.buffer.as_ref(ctx); + let map = self.display_map.as_ref(ctx); + let mut selections = Vec::new(); + for range in ranges { + selections.push(Selection { + start: map.anchor_after(range.start, Bias::Left, ctx.app())?, + end: map.anchor_before(range.end, Bias::Left, ctx.app())?, + reversed: false, + goal_column: None, + }); + } + selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); + self.selections = selections; + self.merge_selections(ctx); + ctx.notify(); + Ok(()) + } + + fn insert(&mut self, text: &String, ctx: &mut ViewContext) { + let buffer = self.buffer.as_ref(ctx); + let mut offset_ranges = SmallVec::<[Range; 32]>::new(); + for selection in &self.selections { + let start = selection.start.to_offset(buffer).unwrap(); + let end = selection.end.to_offset(buffer).unwrap(); + offset_ranges.push(start..end); + } + + self.buffer.update(ctx, |buffer, ctx| { + if let Err(error) = buffer.edit(offset_ranges.iter().cloned(), text.as_str(), Some(ctx)) + { + log::error!("error inserting text: {}", error); + }; + }); + + let buffer = self.buffer.as_ref(ctx); + let char_count = text.chars().count() as isize; + let mut delta = 0_isize; + self.selections = offset_ranges + .into_iter() + .map(|range| { + let start = range.start as isize; + let end = range.end as isize; + let anchor = buffer + .anchor_before((start + delta + char_count) as usize) + .unwrap(); + let deleted_count = end - start; + delta += char_count - deleted_count; + Selection { + start: anchor.clone(), + end: anchor, + reversed: false, + goal_column: None, + } + }) + .collect(); + + self.pause_cursor_blinking(ctx); + *self.autoscroll_requested.lock() = true; + } + + fn newline(&mut self, _: &(), ctx: &mut ViewContext) { + if self.single_line { + ctx.propagate_action(); + } else { + self.insert(&"\n".into(), ctx); + } + } + + pub fn backspace(&mut self, _: &(), ctx: &mut ViewContext) { + self.select_left(&(), ctx); + self.insert(&String::new(), ctx); + } + + pub fn move_left(&mut self, _: &(), ctx: &mut ViewContext) { + { + let app = ctx.app(); + let map = self.display_map.as_ref(ctx); + for selection in &mut self.selections { + let start = selection.start.to_display_point(map, app).unwrap(); + let end = selection.end.to_display_point(map, app).unwrap(); + + if start != end { + selection.end = selection.start.clone(); + } else { + let cursor = map + .anchor_before(movement::left(map, start, app).unwrap(), Bias::Left, app) + .unwrap(); + selection.start = cursor.clone(); + selection.end = cursor; + } + selection.reversed = false; + selection.goal_column = None; + } + } + self.changed_selections(ctx); + } + + pub fn select_left(&mut self, _: &(), ctx: &mut ViewContext) { + { + let buffer = self.buffer.as_ref(ctx); + let map = self.display_map.as_ref(ctx); + for selection in &mut self.selections { + let head = selection.head().to_display_point(map, ctx.app()).unwrap(); + let cursor = map + .anchor_before( + movement::left(map, head, ctx.app()).unwrap(), + Bias::Left, + ctx.app(), + ) + .unwrap(); + selection.set_head(&buffer, cursor); + selection.goal_column = None; + } + } + self.changed_selections(ctx); + } + + pub fn move_right(&mut self, _: &(), ctx: &mut ViewContext) { + { + let app = ctx.app(); + let map = self.display_map.as_ref(app); + for selection in &mut self.selections { + let start = selection.start.to_display_point(map, app).unwrap(); + let end = selection.end.to_display_point(map, app).unwrap(); + + if start != end { + selection.start = selection.end.clone(); + } else { + let cursor = map + .anchor_before(movement::right(map, end, app).unwrap(), Bias::Right, app) + .unwrap(); + selection.start = cursor.clone(); + selection.end = cursor; + } + selection.reversed = false; + selection.goal_column = None; + } + } + self.changed_selections(ctx); + } + + pub fn select_right(&mut self, _: &(), ctx: &mut ViewContext) { + { + let buffer = self.buffer.as_ref(ctx); + let app = ctx.app(); + let map = self.display_map.as_ref(app); + for selection in &mut self.selections { + let head = selection.head().to_display_point(map, ctx.app()).unwrap(); + let cursor = map + .anchor_before(movement::right(map, head, app).unwrap(), Bias::Right, app) + .unwrap(); + selection.set_head(&buffer, cursor); + selection.goal_column = None; + } + } + self.changed_selections(ctx); + } + + pub fn move_up(&mut self, _: &(), ctx: &mut ViewContext) { + if self.single_line { + ctx.propagate_action(); + } else { + let app = ctx.app(); + let map = self.display_map.as_ref(app); + for selection in &mut self.selections { + let start = selection.start.to_display_point(map, app).unwrap(); + let end = selection.end.to_display_point(map, app).unwrap(); + if start != end { + selection.goal_column = None; + } + + let (start, goal_column) = + movement::up(map, start, selection.goal_column, app).unwrap(); + let cursor = map.anchor_before(start, Bias::Left, app).unwrap(); + selection.start = cursor.clone(); + selection.end = cursor; + selection.goal_column = goal_column; + selection.reversed = false; + } + self.changed_selections(ctx); + } + } + + pub fn select_up(&mut self, _: &(), ctx: &mut ViewContext) { + if self.single_line { + ctx.propagate_action(); + } else { + let app = ctx.app(); + let buffer = self.buffer.as_ref(app); + let map = self.display_map.as_ref(app); + for selection in &mut self.selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let (head, goal_column) = + movement::up(map, head, selection.goal_column, app).unwrap(); + selection.set_head(&buffer, map.anchor_before(head, Bias::Left, app).unwrap()); + selection.goal_column = goal_column; + } + self.changed_selections(ctx); + } + } + + pub fn move_down(&mut self, _: &(), ctx: &mut ViewContext) { + if self.single_line { + ctx.propagate_action(); + } else { + let app = ctx.app(); + let map = self.display_map.as_ref(app); + for selection in &mut self.selections { + let start = selection.start.to_display_point(map, app).unwrap(); + let end = selection.end.to_display_point(map, app).unwrap(); + if start != end { + selection.goal_column = None; + } + + let (start, goal_column) = + movement::down(map, end, selection.goal_column, app).unwrap(); + let cursor = map.anchor_before(start, Bias::Right, app).unwrap(); + selection.start = cursor.clone(); + selection.end = cursor; + selection.goal_column = goal_column; + selection.reversed = false; + } + self.changed_selections(ctx); + } + } + + pub fn select_down(&mut self, _: &(), ctx: &mut ViewContext) { + if self.single_line { + ctx.propagate_action(); + } else { + let app = ctx.app(); + let buffer = self.buffer.as_ref(ctx); + let map = self.display_map.as_ref(ctx); + for selection in &mut self.selections { + let head = selection.head().to_display_point(map, app).unwrap(); + let (head, goal_column) = + movement::down(map, head, selection.goal_column, app).unwrap(); + selection.set_head(&buffer, map.anchor_before(head, Bias::Right, app).unwrap()); + selection.goal_column = goal_column; + } + self.changed_selections(ctx); + } + } + + pub fn changed_selections(&mut self, ctx: &mut ViewContext) { + self.merge_selections(ctx); + self.pause_cursor_blinking(ctx); + *self.autoscroll_requested.lock() = true; + ctx.notify(); + } + + fn merge_selections(&mut self, ctx: &A) { + let buffer = self.buffer.as_ref(ctx); + let mut i = 1; + while i < self.selections.len() { + if self.selections[i - 1] + .end + .cmp(&self.selections[i].start, buffer) + .unwrap() + >= Ordering::Equal + { + let removed = self.selections.remove(i); + if removed + .start + .cmp(&self.selections[i - 1].start, buffer) + .unwrap() + < Ordering::Equal + { + self.selections[i - 1].start = removed.start; + } + if removed + .end + .cmp(&self.selections[i - 1].end, buffer) + .unwrap() + > Ordering::Equal + { + self.selections[i - 1].end = removed.end; + } + } else { + i += 1; + } + } + } + + pub fn first_selection(&self, app: &AppContext) -> Range { + self.selections + .first() + .unwrap() + .display_range(self.display_map.as_ref(app), app) + } + + pub fn last_selection(&self, app: &AppContext) -> Range { + self.selections + .last() + .unwrap() + .display_range(self.display_map.as_ref(app), app) + } + + pub fn selections_in_range<'a>( + &'a self, + range: Range, + app: &'a AppContext, + ) -> impl 'a + Iterator> { + let map = self.display_map.as_ref(app); + + let start = map.anchor_before(range.start, Bias::Left, app).unwrap(); + let start_index = self.selection_insertion_index(&start, app); + let pending_selection = self.pending_selection.as_ref().and_then(|s| { + let selection_range = s.display_range(map, app); + if selection_range.start <= range.end || selection_range.end <= range.end { + Some(selection_range) + } else { + None + } + }); + self.selections[start_index..] + .iter() + .map(move |s| s.display_range(map, app)) + .take_while(move |r| r.start <= range.end || r.end <= range.end) + .chain(pending_selection) + } + + fn selection_insertion_index(&self, start: &Anchor, app: &AppContext) -> usize { + let buffer = self.buffer.as_ref(app); + + match self + .selections + .binary_search_by(|probe| probe.start.cmp(&start, buffer).unwrap()) + { + Ok(index) => index, + Err(index) => { + if index > 0 + && self.selections[index - 1].end.cmp(&start, buffer).unwrap() + == Ordering::Greater + { + index - 1 + } else { + index + } + } + } + } + + pub fn page_up(&mut self, _: &(), _: &mut ViewContext) { + log::info!("BufferView::page_up"); + } + + pub fn page_down(&mut self, _: &(), _: &mut ViewContext) { + log::info!("BufferView::page_down"); + } + + pub fn fold(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt; + + let mut fold_ranges = Vec::new(); + + let app = ctx.app(); + let map = self.display_map.as_ref(app); + for selection in &self.selections { + let (start, end) = selection.display_range(map, app).sorted(); + let buffer_start_row = start.to_buffer_point(map, Bias::Left, app).unwrap().row; + + for row in (0..=end.row()).rev() { + if self.is_line_foldable(row, app) && !map.is_line_folded(row) { + let fold_range = self.foldable_range_for_line(row, app).unwrap(); + if fold_range.end.row >= buffer_start_row { + fold_ranges.push(fold_range); + if row <= start.row() { + break; + } + } + } + } + } + + if !fold_ranges.is_empty() { + self.display_map.update(ctx, |map, ctx| { + map.fold(fold_ranges, ctx).unwrap(); + }); + *self.autoscroll_requested.lock() = true; + } + } + + pub fn unfold(&mut self, _: &(), ctx: &mut ViewContext) { + use super::RangeExt; + + let app = ctx.app(); + let map = self.display_map.as_ref(app); + let buffer = self.buffer.as_ref(app); + let ranges = self + .selections + .iter() + .map(|s| { + let (start, end) = s.display_range(map, app).sorted(); + let mut start = start.to_buffer_point(map, Bias::Left, app).unwrap(); + let mut end = end.to_buffer_point(map, Bias::Left, app).unwrap(); + start.column = 0; + end.column = buffer.line_len(end.row).unwrap(); + start..end + }) + .collect::>(); + + self.display_map.update(ctx, |map, ctx| { + map.unfold(ranges, ctx).unwrap(); + }); + *self.autoscroll_requested.lock() = true; + } + + fn is_line_foldable(&self, display_row: u32, app: &AppContext) -> bool { + let max_point = self.max_point(app); + if display_row >= max_point.row() { + false + } else { + let (start_indent, is_blank) = self.line_indent(display_row, app).unwrap(); + if is_blank { + false + } else { + for display_row in display_row + 1..=max_point.row() { + let (indent, is_blank) = self.line_indent(display_row, app).unwrap(); + if !is_blank { + return indent > start_indent; + } + } + false + } + } + } + + fn line_indent(&self, display_row: u32, app: &AppContext) -> Result<(usize, bool)> { + let mut indent = 0; + let mut is_blank = true; + for c in self + .display_map + .as_ref(app) + .chars_at(DisplayPoint::new(display_row, 0), app)? + { + if c == ' ' { + indent += 1; + } else { + is_blank = c == '\n'; + break; + } + } + Ok((indent, is_blank)) + } + + fn foldable_range_for_line(&self, start_row: u32, app: &AppContext) -> Result> { + let map = self.display_map.as_ref(app); + let max_point = self.max_point(app); + + let (start_indent, _) = self.line_indent(start_row, app)?; + let start = DisplayPoint::new(start_row, self.line_len(start_row, app)?); + let mut end = None; + for row in start_row + 1..=max_point.row() { + let (indent, is_blank) = self.line_indent(row, app)?; + if !is_blank && indent <= start_indent { + end = Some(DisplayPoint::new(row - 1, self.line_len(row - 1, app)?)); + break; + } + } + + let end = end.unwrap_or(max_point); + return Ok(start.to_buffer_point(map, Bias::Left, app)? + ..end.to_buffer_point(map, Bias::Left, app)?); + } + + pub fn fold_selected_ranges(&mut self, _: &(), ctx: &mut ViewContext) { + self.display_map.update(ctx, |map, ctx| { + let buffer = self.buffer.as_ref(ctx); + let ranges = self + .selections + .iter() + .map(|s| s.range(buffer)) + .collect::>(); + map.fold(ranges, ctx).unwrap(); + }); + } + + pub fn line(&self, display_row: u32, app: &AppContext) -> Result { + self.display_map.as_ref(app).line(display_row, app) + } + + pub fn line_len(&self, display_row: u32, app: &AppContext) -> Result { + self.display_map.as_ref(app).line_len(display_row, app) + } + + pub fn rightmost_point(&self, app: &AppContext) -> DisplayPoint { + self.display_map.as_ref(app).rightmost_point() + } + + pub fn max_point(&self, app: &AppContext) -> DisplayPoint { + self.display_map.as_ref(app).max_point(app) + } + + pub fn text(&self, app: &AppContext) -> String { + self.display_map.as_ref(app).text(app) + } + + pub fn font_size(&self) -> f32 { + smol::block_on(self.settings.read()).buffer_font_size + } + + pub fn font_ascent(&self, font_cache: &FontCache) -> f32 { + let settings = smol::block_on(self.settings.read()); + let font_id = font_cache.default_font(settings.buffer_font_family); + let ascent = font_cache.metric(font_id, |m| m.ascent); + font_cache.scale_metric(ascent, font_id, settings.buffer_font_size) + } + + pub fn font_descent(&self, font_cache: &FontCache) -> f32 { + let settings = smol::block_on(self.settings.read()); + let font_id = font_cache.default_font(settings.buffer_font_family); + let ascent = font_cache.metric(font_id, |m| m.descent); + font_cache.scale_metric(ascent, font_id, settings.buffer_font_size) + } + + pub fn line_height(&self, font_cache: &FontCache) -> f32 { + let settings = smol::block_on(self.settings.read()); + let font_id = font_cache.default_font(settings.buffer_font_family); + font_cache + .bounding_box(font_id, settings.buffer_font_size) + .y() + } + + pub fn em_width(&self, font_cache: &FontCache) -> f32 { + let settings = smol::block_on(self.settings.read()); + let font_id = font_cache.default_font(settings.buffer_font_family); + let font = font_cache.font(font_id); + let glyph_id = font.glyph_for_char('m').unwrap(); + let bounds = font.typographic_bounds(glyph_id).unwrap(); + font_cache.scale_metric(bounds.width(), font_id, settings.buffer_font_size) + } + + pub fn max_line_number_width( + &self, + font_cache: &FontCache, + layout_cache: &LayoutCache, + app: &AppContext, + ) -> Result { + let settings = smol::block_on(self.settings.read()); + 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.as_ref(app).max_point().row + 1) as f32) + .log10() + .floor() as usize + + 1; + + Ok(layout_cache + .layout_str( + "1".repeat(digit_count).as_str(), + font_size, + &[(0..digit_count, font_id)], + font_cache, + ) + .width) + } + + pub fn layout_line_numbers( + &self, + viewport_height: f32, + font_cache: &FontCache, + layout_cache: &LayoutCache, + app: &AppContext, + ) -> Result>> { + let display_map = self.display_map.as_ref(app); + + let settings = smol::block_on(self.settings.read()); + let font_size = settings.buffer_font_size; + let font_id = + font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?; + + let start_row = self.scroll_position().y() as usize; + let end_row = cmp::min( + self.max_point(app).row() as usize, + start_row + (viewport_height / self.line_height(font_cache)).ceil() as usize, + ); + let line_count = end_row - start_row + 1; + let cpus = num_cpus::get(); + let chunk_size = (line_count + cpus - 1) / cpus; + + let mut layouts = Vec::new(); + layouts.resize_with(line_count, Default::default); + + Parallel::>::new() + .each( + layouts.chunks_mut(chunk_size).enumerate(), + |(i, layouts)| { + let mut line_number = String::new(); + let start = start_row + i * chunk_size; + let line_numbers = display_map.buffer_rows(start as u32)?.take(layouts.len()); + for (j, buffer_row) in line_numbers.enumerate() { + line_number.clear(); + write!(&mut line_number, "{}", buffer_row + 1).unwrap(); + layouts[j] = layout_cache.layout_str( + &line_number, + font_size, + &[(0..line_number.len(), font_id)], + font_cache, + ); + } + Ok(()) + }, + ) + .run() + .into_iter() + .collect::>()?; + + Ok(layouts) + } + + pub fn layout_lines( + &self, + mut rows: Range, + font_cache: &FontCache, + layout_cache: &LayoutCache, + app: &AppContext, + ) -> Result>> { + let display_map = self.display_map.as_ref(app); + + rows.end = cmp::min(rows.end, display_map.max_point(app).row() + 1); + if rows.start >= rows.end { + return Ok(Vec::new()); + } + + let settings = smol::block_on(self.settings.read()); + let font_id = + font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?; + let font_size = settings.buffer_font_size; + + let cpus = num_cpus::get(); + let chunk_size = (rows.len() + cpus - 1) / cpus; + + let mut layouts = Vec::new(); + layouts.resize_with(rows.len(), Default::default); + + crossbeam::thread::scope(|s| { + for (ix, chunk) in layouts.chunks_mut(chunk_size as usize).enumerate() { + let font_cache = &font_cache; + let chunk_start = rows.start as usize + ix * chunk_size; + let chunk_end = cmp::min(chunk_start + chunk_size, rows.end as usize); + + s.spawn(move |_| { + let mut row = chunk_start; + let mut line = String::new(); + let mut line_len = 0; + let chars = display_map + .chars_at(DisplayPoint::new(chunk_start as u32, 0), app) + .unwrap(); + for char in chars.chain(Some('\n')) { + if char == '\n' { + chunk[(row - chunk_start) as usize] = layout_cache.layout_str( + &line, + font_size, + &[(0..line_len, font_id)], + font_cache, + ); + line.clear(); + line_len = 0; + row += 1; + if row == chunk_end { + break; + } + } else { + line_len += 1; + line.push(char); + } + } + }); + } + }) + .unwrap(); + + Ok(layouts) + } + + pub fn layout_line( + &self, + row: u32, + font_cache: &FontCache, + layout_cache: &LayoutCache, + app: &AppContext, + ) -> Result> { + let settings = smol::block_on(self.settings.read()); + let font_id = + font_cache.select_font(settings.buffer_font_family, &FontProperties::new())?; + + let line = self.line(row, app)?; + + Ok(layout_cache.layout_str( + &line, + settings.buffer_font_size, + &[(0..self.line_len(row, app)? as usize, font_id)], + font_cache, + )) + } + + fn next_blink_epoch(&mut self) -> usize { + self.blink_epoch += 1; + self.blink_epoch + } + + fn pause_cursor_blinking(&mut self, ctx: &mut ViewContext) { + self.cursors_visible = true; + ctx.notify(); + + let epoch = self.next_blink_epoch(); + let _ = ctx.spawn_local( + async move { + Timer::after(CURSOR_BLINK_INTERVAL).await; + epoch + }, + Self::resume_cursor_blinking, + ); + } + + fn resume_cursor_blinking(&mut self, epoch: usize, ctx: &mut ViewContext) { + if epoch == self.blink_epoch { + self.blinking_paused = false; + self.blink_cursors(epoch, ctx); + } + } + + fn blink_cursors(&mut self, epoch: usize, ctx: &mut ViewContext) { + if epoch == self.blink_epoch && self.focused && !self.blinking_paused { + self.cursors_visible = !self.cursors_visible; + ctx.notify(); + + let epoch = self.next_blink_epoch(); + let _ = ctx.spawn_local( + async move { + Timer::after(CURSOR_BLINK_INTERVAL).await; + epoch + }, + Self::blink_cursors, + ); + } + } + + pub fn cursors_visible(&self) -> bool { + self.cursors_visible + } + + fn on_buffer_changed(&mut self, _: ModelHandle, ctx: &mut ViewContext) { + ctx.notify(); + } + + fn on_display_map_changed(&mut self, _: ModelHandle, ctx: &mut ViewContext) { + ctx.notify(); + } + + fn on_buffer_event( + &mut self, + _: ModelHandle, + event: &buffer::Event, + ctx: &mut ViewContext, + ) { + match event { + buffer::Event::Edited(_) => ctx.emit(Event::Edited), + } + } +} + +struct Selection { + start: Anchor, + end: Anchor, + reversed: bool, + goal_column: Option, +} + +pub enum Event { + Activate, + Edited, + Blurred, +} + +impl app::Entity for BufferView { + type Event = Event; +} + +impl app::View for BufferView { + fn render<'a>(&self, bump: &'a Bump, app: &AppContext) -> &'a mut dyn Element<'a> { + BufferElement::new(self.handle.upgrade(app).unwrap()).finish(bump) + } + + fn ui_name() -> &'static str { + "BufferView" + } + + fn on_focus(&mut self, ctx: &mut ViewContext) { + self.focused = true; + self.blink_cursors(self.blink_epoch, ctx); + } + + fn on_blur(&mut self, ctx: &mut ViewContext) { + self.focused = false; + self.cursors_visible = false; + ctx.emit(Event::Blurred); + ctx.notify(); + } +} + +impl workspace::ItemView for BufferView { + fn is_activate_event(event: &Self::Event) -> bool { + match event { + Event::Activate => true, + _ => false, + } + } + + fn title(&self, app: &AppContext) -> std::string::String { + if let Some(path) = self.buffer.as_ref(app).path(app) { + path.file_name() + .expect("buffer's path is always to a file") + .to_string_lossy() + .into() + } else { + "untitled".into() + } + } + + fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)> { + self.buffer.as_ref(app).entry_id() + } + + fn clone_on_split(&self, ctx: &mut ViewContext) -> Option + where + Self: Sized, + { + let clone = BufferView::for_buffer(self.buffer.clone(), self.settings.clone(), ctx); + *clone.scroll_position.lock() = *self.scroll_position.lock(); + Some(clone) + } +} + +impl Selection { + fn head(&self) -> &Anchor { + if self.reversed { + &self.start + } else { + &self.end + } + } + + fn set_head(&mut self, buffer: &Buffer, cursor: Anchor) { + if cursor.cmp(self.tail(), buffer).unwrap() < Ordering::Equal { + if !self.reversed { + mem::swap(&mut self.start, &mut self.end); + self.reversed = true; + } + self.start = cursor; + } else { + if self.reversed { + mem::swap(&mut self.start, &mut self.end); + self.reversed = false; + } + self.end = cursor; + } + } + + fn tail(&self) -> &Anchor { + if self.reversed { + &self.end + } else { + &self.start + } + } + + fn range(&self, buffer: &Buffer) -> Range { + let start = self.start.to_point(buffer).unwrap(); + let end = self.end.to_point(buffer).unwrap(); + if self.reversed { + end..start + } else { + start..end + } + } + + fn display_range(&self, map: &DisplayMap, app: &AppContext) -> Range { + let start = self.start.to_display_point(map, app).unwrap(); + let end = self.end.to_display_point(map, app).unwrap(); + if self.reversed { + end..start + } else { + start..end + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::buffer::Point; + use crate::test_utils::*; + use anyhow::Error; + use unindent::Unindent; + + #[test] + fn test_selection_with_mouse() { + App::run(|mut app| async move { + let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n")); + let settings = settings_rx(None); + let (_, buffer_view) = + app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); + + buffer_view.update(&mut app, |view, ctx| { + view.begin_selection(DisplayPoint::new(2, 2), false, ctx); + }); + + buffer_view.read(&app, |view, app| { + let selections = view + .selections_in_range(DisplayPoint::zero()..view.max_point(app), app) + .collect::>(); + assert_eq!( + selections, + [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)] + ); + }); + + buffer_view.update(&mut app, |view, ctx| { + view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx); + }); + + buffer_view.read(&app, |view, app| { + let selections = view + .selections_in_range(DisplayPoint::zero()..view.max_point(app), app) + .collect::>(); + assert_eq!( + selections, + [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] + ); + }); + + buffer_view.update(&mut app, |view, ctx| { + view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx); + }); + + buffer_view.read(&app, |view, app| { + let selections = view + .selections_in_range(DisplayPoint::zero()..view.max_point(app), app) + .collect::>(); + assert_eq!( + selections, + [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] + ); + }); + + buffer_view.update(&mut app, |view, ctx| { + view.end_selection(ctx); + view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx); + }); + + buffer_view.read(&app, |view, app| { + let selections = view + .selections_in_range(DisplayPoint::zero()..view.max_point(app), app) + .collect::>(); + assert_eq!( + selections, + [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)] + ); + }); + + buffer_view.update(&mut app, |view, ctx| { + view.begin_selection(DisplayPoint::new(3, 3), true, ctx); + view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx); + }); + + buffer_view.read(&app, |view, app| { + let selections = view + .selections_in_range(DisplayPoint::zero()..view.max_point(app), app) + .collect::>(); + assert_eq!( + selections, + [ + DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1), + DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0) + ] + ); + }); + + buffer_view.update(&mut app, |view, ctx| { + view.end_selection(ctx); + }); + + buffer_view.read(&app, |view, app| { + let selections = view + .selections_in_range(DisplayPoint::zero()..view.max_point(app), app) + .collect::>(); + assert_eq!( + selections, + [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)] + ); + }); + }); + } + + #[test] + fn test_layout_line_numbers() -> Result<()> { + use crate::fonts::FontCache; + use crate::text_layout::LayoutCache; + + let font_cache = FontCache::new(); + let layout_cache = LayoutCache::new(); + + let mut app = App::new().unwrap(); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6))); + + let settings = settings_rx(Some(&font_cache)); + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + + view.read(&app, |view, app| { + let layouts = view.layout_line_numbers(1000.0, &font_cache, &layout_cache, app)?; + assert_eq!(layouts.len(), 6); + Result::<()>::Ok(()) + })?; + + Ok(()) + } + + #[test] + fn test_fold() -> Result<()> { + init_logger(); + + let mut app = App::new().unwrap(); + let buffer = app.add_model(|_| { + Buffer::new( + 0, + " + impl Foo { + // Hello! + + fn a() { + 1 + } + + fn b() { + 2 + } + + fn c() { + 3 + } + } + " + .unindent(), + ) + }); + let settings = settings_rx(None); + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + + view.update(&mut app, |view, ctx| { + view.select_ranges(Some(DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)), ctx)?; + view.fold(&(), ctx); + assert_eq!( + view.text(ctx.app()), + " + impl Foo { + // Hello! + + fn a() { + 1 + } + + fn b() {… + } + + fn c() {… + } + } + " + .unindent(), + ); + + view.fold(&(), ctx); + assert_eq!( + view.text(ctx.app()), + " + impl Foo {… + } + " + .unindent(), + ); + + view.unfold(&(), ctx); + assert_eq!( + view.text(ctx.app()), + " + impl Foo { + // Hello! + + fn a() { + 1 + } + + fn b() {… + } + + fn c() {… + } + } + " + .unindent(), + ); + + view.unfold(&(), ctx); + assert_eq!(view.text(ctx.app()), buffer.as_ref(ctx).text()); + + Ok::<(), Error>(()) + })?; + + Ok(()) + } + + #[test] + fn test_move_cursor() -> Result<()> { + let mut app = App::new().unwrap(); + let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6))); + let settings = settings_rx(None); + let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); + + buffer.update(&mut app, |buffer, ctx| { + buffer.edit( + vec![ + Point::new(1, 0)..Point::new(1, 0), + Point::new(1, 1)..Point::new(1, 1), + ], + "\t", + Some(ctx), + ) + })?; + + view.update(&mut app, |view, ctx| { + view.move_down(&(), ctx); + assert_eq!( + view.selections(ctx.app()), + &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)] + ); + view.move_right(&(), ctx); + assert_eq!( + view.selections(ctx.app()), + &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)] + ); + Ok::<(), Error>(()) + })?; + + Ok(()) + } + + impl BufferView { + fn selections(&self, app: &AppContext) -> Vec> { + self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app) + .collect::>() + } + } +} diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs new file mode 100644 index 0000000000..f1d3ee9ad9 --- /dev/null +++ b/zed/src/editor/display_map/fold_map.rs @@ -0,0 +1,698 @@ +use super::{ + buffer, Anchor, AnchorRangeExt, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset, +}; +use crate::{ + app::{AppContext, ModelHandle}, + sum_tree::{self, Cursor, SumTree}, + util::find_insertion_index, +}; +use anyhow::{anyhow, Result}; +use std::{ + cmp::{self, Ordering}, + iter::Take, + ops::Range, +}; +use sum_tree::{Dimension, SeekBias}; + +pub struct FoldMap { + buffer: ModelHandle, + transforms: SumTree, + folds: Vec>, +} + +impl FoldMap { + pub fn new(buffer: ModelHandle, app: &AppContext) -> Self { + let text_summary = buffer.as_ref(app).text_summary(); + Self { + buffer, + folds: Vec::new(), + transforms: SumTree::from_item(Transform { + summary: TransformSummary { + buffer: text_summary.clone(), + display: text_summary, + }, + display_text: None, + }), + } + } + + pub fn buffer_rows(&self, start_row: u32) -> Result { + if start_row > self.transforms.summary().display.lines.row { + return Err(anyhow!("invalid display row {}", start_row)); + } + + let display_point = Point::new(start_row, 0); + let mut cursor = self.transforms.cursor(); + cursor.seek(&DisplayPoint(display_point), SeekBias::Left); + + Ok(BufferRows { + display_point, + cursor, + }) + } + + pub fn len(&self) -> usize { + self.transforms.summary().display.chars + } + + pub fn line_len(&self, row: u32, ctx: &AppContext) -> Result { + let line_start = self.to_display_offset(DisplayPoint::new(row, 0), ctx)?.0; + let line_end = if row >= self.max_point().row() { + self.len() + } else { + self.to_display_offset(DisplayPoint::new(row + 1, 0), ctx)? + .0 + - 1 + }; + + Ok((line_end - line_start) as u32) + } + + pub fn chars_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Result> { + let offset = self.to_display_offset(point, app)?; + let mut cursor = self.transforms.cursor(); + cursor.seek(&offset, SeekBias::Right); + let buffer = self.buffer.as_ref(app); + Ok(Chars { + cursor, + offset: offset.0, + buffer, + buffer_chars: None, + }) + } + + pub fn max_point(&self) -> DisplayPoint { + DisplayPoint(self.transforms.summary().display.lines) + } + + pub fn rightmost_point(&self) -> DisplayPoint { + DisplayPoint(self.transforms.summary().display.rightmost_point) + } + + pub fn fold( + &mut self, + ranges: impl IntoIterator>, + app: &AppContext, + ) -> Result<()> { + let mut edits = Vec::new(); + let buffer = self.buffer.as_ref(app); + for range in ranges.into_iter() { + let start = range.start.to_offset(buffer)?; + let end = range.end.to_offset(buffer)?; + edits.push(Edit { + old_range: start..end, + new_range: start..end, + }); + + let fold = buffer.anchor_after(start)?..buffer.anchor_before(end)?; + let ix = find_insertion_index(&self.folds, |probe| probe.cmp(&fold, buffer))?; + self.folds.insert(ix, fold); + } + edits.sort_unstable_by(|a, b| { + a.old_range + .start + .cmp(&b.old_range.start) + .then_with(|| b.old_range.end.cmp(&a.old_range.end)) + }); + + self.apply_edits(&edits, app)?; + Ok(()) + } + + pub fn unfold( + &mut self, + ranges: impl IntoIterator>, + app: &AppContext, + ) -> Result<()> { + let buffer = self.buffer.as_ref(app); + + let mut edits = Vec::new(); + for range in ranges.into_iter() { + let start = buffer.anchor_before(range.start.to_offset(buffer)?)?; + let end = buffer.anchor_after(range.end.to_offset(buffer)?)?; + + // Remove intersecting folds and add their ranges to edits that are passed to apply_edits + self.folds.retain(|fold| { + if fold.start.cmp(&end, buffer).unwrap() > Ordering::Equal + || fold.end.cmp(&start, buffer).unwrap() < Ordering::Equal + { + true + } else { + let offset_range = + fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap(); + edits.push(Edit { + old_range: offset_range.clone(), + new_range: offset_range, + }); + false + } + }); + } + + self.apply_edits(&edits, app)?; + Ok(()) + } + + pub fn is_line_folded(&self, display_row: u32) -> bool { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&DisplayPoint::new(display_row, 0), SeekBias::Right); + while let Some(transform) = cursor.item() { + if transform.display_text.is_some() { + return true; + } + if cursor.end().row() == display_row { + cursor.next() + } else { + break; + } + } + false + } + + pub fn to_display_offset( + &self, + point: DisplayPoint, + app: &AppContext, + ) -> Result { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&point, SeekBias::Right); + let overshoot = point.0 - cursor.start().display.lines; + let mut offset = cursor.start().display.chars; + if !overshoot.is_zero() { + let transform = cursor + .item() + .ok_or_else(|| anyhow!("display point {:?} is out of range", point))?; + assert!(transform.display_text.is_none()); + let end_buffer_offset = + (cursor.start().buffer.lines + overshoot).to_offset(self.buffer.as_ref(app))?; + offset += end_buffer_offset - cursor.start().buffer.chars; + } + Ok(DisplayOffset(offset)) + } + + pub fn to_buffer_point(&self, display_point: DisplayPoint) -> Point { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&display_point, SeekBias::Right); + let overshoot = display_point.0 - cursor.start().display.lines; + cursor.start().buffer.lines + overshoot + } + + pub fn to_display_point(&self, point: Point) -> DisplayPoint { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&point, SeekBias::Right); + let overshoot = point - cursor.start().buffer.lines; + DisplayPoint(cmp::min( + cursor.start().display.lines + overshoot, + cursor.end().display.lines, + )) + } + + pub fn apply_edits(&mut self, edits: &[Edit], app: &AppContext) -> Result<()> { + let buffer = self.buffer.as_ref(app); + let mut edits = edits.iter().cloned().peekable(); + + let mut new_transforms = SumTree::new(); + let mut cursor = self.transforms.cursor::(); + cursor.seek(&0, SeekBias::Right); + + while let Some(mut edit) = edits.next() { + new_transforms.push_tree(cursor.slice(&edit.old_range.start, SeekBias::Left)); + edit.new_range.start -= edit.old_range.start - cursor.start(); + edit.old_range.start = *cursor.start(); + + cursor.seek(&edit.old_range.end, SeekBias::Right); + cursor.next(); + + let mut delta = edit.delta(); + loop { + edit.old_range.end = *cursor.start(); + + if let Some(next_edit) = edits.peek() { + if next_edit.old_range.start > edit.old_range.end { + break; + } + + let next_edit = edits.next().unwrap(); + delta += next_edit.delta(); + + if next_edit.old_range.end > edit.old_range.end { + edit.old_range.end = next_edit.old_range.end; + cursor.seek(&edit.old_range.end, SeekBias::Right); + cursor.next(); + } + } else { + break; + } + } + + edit.new_range.end = + ((edit.new_range.start + edit.old_extent()) as isize + delta) as usize; + + let anchor = buffer.anchor_before(edit.new_range.start)?; + let folds_start = + find_insertion_index(&self.folds, |probe| probe.start.cmp(&anchor, buffer))?; + let mut folds = self.folds[folds_start..] + .iter() + .map(|fold| { + fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap() + }) + .peekable(); + + while folds + .peek() + .map_or(false, |fold| fold.start < edit.new_range.end) + { + let mut fold = folds.next().unwrap(); + let sum = new_transforms.summary(); + + assert!(fold.start >= sum.buffer.chars); + + while folds + .peek() + .map_or(false, |next_fold| next_fold.start <= fold.end) + { + let next_fold = folds.next().unwrap(); + if next_fold.end > fold.end { + fold.end = next_fold.end; + } + } + + if fold.start > sum.buffer.chars { + let text_summary = buffer.text_summary_for_range(sum.buffer.chars..fold.start); + new_transforms.push(Transform { + summary: TransformSummary { + display: text_summary.clone(), + buffer: text_summary, + }, + display_text: None, + }); + } + + if fold.end > fold.start { + new_transforms.push(Transform { + summary: TransformSummary { + display: TextSummary { + chars: 1, + bytes: '…'.len_utf8(), + lines: Point::new(0, 1), + first_line_len: 1, + rightmost_point: Point::new(0, 1), + }, + buffer: buffer.text_summary_for_range(fold.start..fold.end), + }, + display_text: Some('…'), + }); + } + } + + let sum = new_transforms.summary(); + if sum.buffer.chars < edit.new_range.end { + let text_summary = + buffer.text_summary_for_range(sum.buffer.chars..edit.new_range.end); + new_transforms.push(Transform { + summary: TransformSummary { + display: text_summary.clone(), + buffer: text_summary, + }, + display_text: None, + }); + } + } + + new_transforms.push_tree(cursor.suffix()); + + drop(cursor); + self.transforms = new_transforms; + + Ok(()) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct Transform { + summary: TransformSummary, + display_text: Option, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct TransformSummary { + display: TextSummary, + buffer: TextSummary, +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + self.summary.clone() + } +} + +impl<'a> std::ops::AddAssign<&'a Self> for TransformSummary { + fn add_assign(&mut self, other: &'a Self) { + self.buffer += &other.buffer; + self.display += &other.display; + } +} + +impl<'a> Dimension<'a, TransformSummary> for TransformSummary { + fn add_summary(&mut self, summary: &'a TransformSummary) { + *self += summary; + } +} + +pub struct BufferRows<'a> { + cursor: Cursor<'a, Transform, DisplayPoint, TransformSummary>, + display_point: Point, +} + +impl<'a> Iterator for BufferRows<'a> { + type Item = u32; + + fn next(&mut self) -> Option { + while self.display_point > self.cursor.end().display.lines { + self.cursor.next(); + if self.cursor.item().is_none() { + // TODO: Return a bool from next? + break; + } + } + + if self.cursor.item().is_some() { + let overshoot = self.display_point - self.cursor.start().display.lines; + let buffer_point = self.cursor.start().buffer.lines + overshoot; + self.display_point.row += 1; + Some(buffer_point.row) + } else { + None + } + } +} + +pub struct Chars<'a> { + cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, + offset: usize, + buffer: &'a Buffer, + buffer_chars: Option>>, +} + +impl<'a> Iterator for Chars<'a> { + type Item = char; + + fn next(&mut self) -> Option { + if let Some(c) = self.buffer_chars.as_mut().and_then(|chars| chars.next()) { + self.offset += 1; + return Some(c); + } + + if self.offset == self.cursor.end().display.chars { + self.cursor.next(); + } + + self.cursor.item().and_then(|transform| { + if let Some(c) = transform.display_text { + self.offset += 1; + Some(c) + } else { + let overshoot = self.offset - self.cursor.start().display.chars; + let buffer_start = self.cursor.start().buffer.chars + overshoot; + let char_count = self.cursor.end().buffer.chars - buffer_start; + self.buffer_chars = + Some(self.buffer.chars_at(buffer_start).unwrap().take(char_count)); + self.next() + } + }) + } +} + +impl<'a> Dimension<'a, TransformSummary> for DisplayPoint { + fn add_summary(&mut self, summary: &'a TransformSummary) { + self.0 += &summary.display.lines; + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DisplayOffset(usize); + +impl<'a> Dimension<'a, TransformSummary> for DisplayOffset { + fn add_summary(&mut self, summary: &'a TransformSummary) { + self.0 += &summary.display.chars; + } +} + +impl<'a> Dimension<'a, TransformSummary> for Point { + fn add_summary(&mut self, summary: &'a TransformSummary) { + *self += &summary.buffer.lines; + } +} + +impl<'a> Dimension<'a, TransformSummary> for usize { + fn add_summary(&mut self, summary: &'a TransformSummary) { + *self += &summary.buffer.chars; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::app::App; + use crate::test_utils::sample_text; + + #[test] + fn test_basic_folds() -> Result<()> { + let mut app = App::new()?; + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); + let mut map = app.read(|app| FoldMap::new(buffer.clone(), app)); + + app.read(|app| { + map.fold( + vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(2, 4)..Point::new(4, 1), + ], + app, + )?; + assert_eq!(map.text(app), "aa…cc…eeeee"); + Ok::<(), anyhow::Error>(()) + })?; + + let edits = buffer.update(&mut app, |buffer, ctx| { + let start_version = buffer.version.clone(); + buffer.edit( + vec![ + Point::new(0, 0)..Point::new(0, 1), + Point::new(2, 3)..Point::new(2, 3), + ], + "123", + Some(ctx), + )?; + Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::>()) + })?; + + app.read(|app| { + map.apply_edits(&edits, app)?; + assert_eq!(map.text(app), "123a…c123c…eeeee"); + Ok::<(), anyhow::Error>(()) + })?; + + let edits = buffer.update(&mut app, |buffer, ctx| { + let start_version = buffer.version.clone(); + buffer.edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))?; + Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::>()) + })?; + + app.read(|app| { + map.apply_edits(&edits, app)?; + assert_eq!(map.text(app), "123a…c123456eee"); + + map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app)?; + assert_eq!(map.text(app), "123aaaaa\nbbbbbb\nccc123456eee"); + + Ok(()) + }) + } + + #[test] + fn test_overlapping_folds() -> Result<()> { + let mut app = App::new()?; + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); + app.read(|app| { + let mut map = FoldMap::new(buffer.clone(), app); + map.fold( + vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(0, 4)..Point::new(1, 0), + Point::new(1, 2)..Point::new(3, 2), + Point::new(3, 1)..Point::new(4, 1), + ], + app, + )?; + assert_eq!(map.text(app), "aa…eeeee"); + Ok(()) + }) + } + + #[test] + fn test_merging_folds_via_edit() -> Result<()> { + let mut app = App::new()?; + let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6))); + let mut map = app.read(|app| FoldMap::new(buffer.clone(), app)); + + app.read(|app| { + map.fold( + vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(3, 1)..Point::new(4, 1), + ], + app, + )?; + assert_eq!(map.text(app), "aa…cccc\nd…eeeee"); + Ok::<(), anyhow::Error>(()) + })?; + + let edits = buffer.update(&mut app, |buffer, ctx| { + let start_version = buffer.version.clone(); + buffer.edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))?; + Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::>()) + })?; + + app.read(|app| { + map.apply_edits(&edits, app)?; + assert_eq!(map.text(app), "aa…eeeee"); + Ok(()) + }) + } + + #[test] + fn test_random_folds() -> Result<()> { + use crate::buffer::ToPoint; + use crate::util::RandomCharIter; + use rand::prelude::*; + + for seed in 0..100 { + println!("{:?}", seed); + let mut rng = StdRng::seed_from_u64(seed); + + let mut app = App::new()?; + let buffer = app.add_model(|_| { + let len = rng.gen_range(0, 10); + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + Buffer::new(0, text) + }); + let mut map = app.read(|app| FoldMap::new(buffer.clone(), app)); + + app.read(|app| { + let buffer = buffer.as_ref(app); + + let fold_count = rng.gen_range(0, 10); + let mut fold_ranges: Vec> = Vec::new(); + for _ in 0..fold_count { + let end = rng.gen_range(0, buffer.len() + 1); + let start = rng.gen_range(0, end + 1); + fold_ranges.push(start..end); + } + + map.fold(fold_ranges, app)?; + + let mut expected_text = buffer.text(); + for fold_range in map.merged_fold_ranges(app).into_iter().rev() { + expected_text.replace_range(fold_range.start..fold_range.end, "…"); + } + + assert_eq!(map.text(app), expected_text); + + for fold_range in map.merged_fold_ranges(app) { + let display_point = + map.to_display_point(fold_range.start.to_point(buffer).unwrap()); + assert!(map.is_line_folded(display_point.row())); + } + + Ok::<(), anyhow::Error>(()) + })?; + + let edits = buffer.update(&mut app, |buffer, ctx| { + let start_version = buffer.version.clone(); + let edit_count = rng.gen_range(1, 10); + buffer.randomly_edit(&mut rng, edit_count, Some(ctx)); + Ok::<_, anyhow::Error>(buffer.edits_since(start_version).collect::>()) + })?; + + app.read(|app| { + map.apply_edits(&edits, app)?; + + let buffer = map.buffer.as_ref(app); + let mut expected_text = buffer.text(); + for fold_range in map.merged_fold_ranges(app).into_iter().rev() { + expected_text.replace_range(fold_range.start..fold_range.end, "…"); + } + assert_eq!(map.text(app), expected_text); + + Ok::<(), anyhow::Error>(()) + })?; + } + + Ok(()) + } + + #[test] + fn test_buffer_rows() -> Result<()> { + let mut app = App::new()?; + let text = sample_text(6, 6) + "\n"; + let buffer = app.add_model(|_| Buffer::new(0, text)); + + app.read(|app| { + let mut map = FoldMap::new(buffer.clone(), app); + + map.fold( + vec![ + Point::new(0, 2)..Point::new(2, 2), + Point::new(3, 1)..Point::new(4, 1), + ], + app, + )?; + + assert_eq!(map.text(app), "aa…cccc\nd…eeeee\nffffff\n"); + assert_eq!(map.buffer_rows(0)?.collect::>(), vec![0, 3, 5, 6]); + assert_eq!(map.buffer_rows(3)?.collect::>(), vec![6]); + + Ok(()) + }) + } + + impl FoldMap { + fn text(&self, app: &AppContext) -> String { + self.chars_at(DisplayPoint(Point::zero()), app) + .unwrap() + .collect() + } + + fn merged_fold_ranges(&self, app: &AppContext) -> Vec> { + let buffer = self.buffer.as_ref(app); + let mut fold_ranges = self + .folds + .iter() + .map(|fold| { + fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap() + }) + .peekable(); + + let mut merged_ranges = Vec::new(); + while let Some(mut fold_range) = fold_ranges.next() { + while let Some(next_range) = fold_ranges.peek() { + if fold_range.end >= next_range.start { + if next_range.end > fold_range.end { + fold_range.end = next_range.end; + } + fold_ranges.next(); + } else { + break; + } + } + if fold_range.end > fold_range.start { + merged_ranges.push(fold_range); + } + } + merged_ranges + } + } +} diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs new file mode 100644 index 0000000000..1139d34aca --- /dev/null +++ b/zed/src/editor/display_map/mod.rs @@ -0,0 +1,375 @@ +mod fold_map; + +use super::ToPoint; +use super::{buffer, Anchor, AnchorRangeExt, Buffer, Edit, Point, TextSummary, ToOffset}; +use crate::app::{AppContext, Entity, ModelContext, ModelHandle}; +use anyhow::Result; +pub use fold_map::BufferRows; +use fold_map::FoldMap; +use std::ops::Range; + +#[derive(Copy, Clone)] +pub enum Bias { + Left, + Right, +} + +pub struct DisplayMap { + buffer: ModelHandle, + fold_map: FoldMap, + tab_size: usize, +} + +impl Entity for DisplayMap { + type Event = (); +} + +impl DisplayMap { + pub fn new(buffer: ModelHandle, tab_size: usize, ctx: &mut ModelContext) -> Self { + ctx.subscribe(&buffer, Self::handle_buffer_event); + + DisplayMap { + buffer: buffer.clone(), + fold_map: FoldMap::new(buffer, ctx.app()), + tab_size, + } + } + + pub fn fold( + &mut self, + ranges: impl IntoIterator>, + ctx: &mut ModelContext, + ) -> Result<()> { + self.fold_map.fold(ranges, ctx.app())?; + ctx.notify(); + Ok(()) + } + + pub fn unfold( + &mut self, + ranges: impl IntoIterator>, + ctx: &mut ModelContext, + ) -> Result<()> { + self.fold_map.unfold(ranges, ctx.app())?; + ctx.notify(); + Ok(()) + } + + pub fn is_line_folded(&self, display_row: u32) -> bool { + self.fold_map.is_line_folded(display_row) + } + + pub fn text(&self, app: &AppContext) -> String { + self.chars_at(DisplayPoint::zero(), app).unwrap().collect() + } + + pub fn line(&self, display_row: u32, app: &AppContext) -> Result { + let chars = self.chars_at(DisplayPoint::new(display_row, 0), app)?; + Ok(chars.take_while(|c| *c != '\n').collect()) + } + + pub fn chars_at<'a>(&'a self, point: DisplayPoint, app: &'a AppContext) -> Result> { + let column = point.column() as usize; + let (point, to_next_stop) = point.collapse_tabs(self, Bias::Left, app)?; + let mut fold_chars = self.fold_map.chars_at(point, app)?; + if to_next_stop > 0 { + fold_chars.next(); + } + + Ok(Chars { + fold_chars, + column, + to_next_stop, + tab_size: self.tab_size, + }) + } + + pub fn buffer_rows(&self, start_row: u32) -> Result { + self.fold_map.buffer_rows(start_row) + } + + pub fn line_len(&self, row: u32, ctx: &AppContext) -> Result { + DisplayPoint::new(row, self.fold_map.line_len(row, ctx)?) + .expand_tabs(self, ctx) + .map(|point| point.column()) + } + + pub fn max_point(&self, app: &AppContext) -> DisplayPoint { + self.fold_map.max_point().expand_tabs(self, app).unwrap() + } + + pub fn rightmost_point(&self) -> DisplayPoint { + self.fold_map.rightmost_point() + } + + pub fn anchor_before( + &self, + point: DisplayPoint, + bias: Bias, + app: &AppContext, + ) -> Result { + self.buffer + .as_ref(app) + .anchor_before(point.to_buffer_point(self, bias, app)?) + } + + pub fn anchor_after( + &self, + point: DisplayPoint, + bias: Bias, + app: &AppContext, + ) -> Result { + self.buffer + .as_ref(app) + .anchor_after(point.to_buffer_point(self, bias, app)?) + } + + fn handle_buffer_event(&mut self, event: &buffer::Event, ctx: &mut ModelContext) { + match event { + buffer::Event::Edited(edits) => self.fold_map.apply_edits(edits, ctx.app()).unwrap(), + } + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DisplayPoint(Point); + +impl DisplayPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + + pub fn zero() -> Self { + Self::new(0, 0) + } + + pub fn row(self) -> u32 { + self.0.row + } + + pub fn column(self) -> u32 { + self.0.column + } + + pub fn row_mut(&mut self) -> &mut u32 { + &mut self.0.row + } + + pub fn column_mut(&mut self) -> &mut u32 { + &mut self.0.column + } + + pub fn to_buffer_point(self, map: &DisplayMap, bias: Bias, app: &AppContext) -> Result { + Ok(map + .fold_map + .to_buffer_point(self.collapse_tabs(map, bias, app)?.0)) + } + + fn expand_tabs(mut self, map: &DisplayMap, app: &AppContext) -> Result { + let chars = map + .fold_map + .chars_at(DisplayPoint(Point::new(self.row(), 0)), app)?; + let expanded = expand_tabs(chars, self.column() as usize, map.tab_size); + *self.column_mut() = expanded as u32; + + Ok(self) + } + + fn collapse_tabs( + mut self, + map: &DisplayMap, + bias: Bias, + app: &AppContext, + ) -> Result<(Self, usize)> { + let chars = map + .fold_map + .chars_at(DisplayPoint(Point::new(self.0.row, 0)), app)?; + let expanded = self.column() as usize; + let (collapsed, to_next_stop) = collapse_tabs(chars, expanded, bias, map.tab_size); + *self.column_mut() = collapsed as u32; + + Ok((self, to_next_stop)) + } +} + +impl Point { + pub fn to_display_point(self, map: &DisplayMap, app: &AppContext) -> Result { + let mut display_point = map.fold_map.to_display_point(self); + let chars = map + .fold_map + .chars_at(DisplayPoint::new(display_point.row(), 0), app)?; + *display_point.column_mut() = + expand_tabs(chars, display_point.column() as usize, map.tab_size) as u32; + Ok(display_point) + } +} + +impl Anchor { + pub fn to_display_point(&self, map: &DisplayMap, app: &AppContext) -> Result { + self.to_point(map.buffer.as_ref(app))? + .to_display_point(map, app) + } +} + +pub struct Chars<'a> { + fold_chars: fold_map::Chars<'a>, + column: usize, + to_next_stop: usize, + tab_size: usize, +} + +impl<'a> Iterator for Chars<'a> { + type Item = char; + + fn next(&mut self) -> Option { + if self.to_next_stop > 0 { + self.to_next_stop -= 1; + self.column += 1; + Some(' ') + } else { + self.fold_chars.next().map(|c| match c { + '\t' => { + self.to_next_stop = self.tab_size - self.column % self.tab_size - 1; + self.column += 1; + ' ' + } + '\n' => { + self.column = 0; + c + } + _ => { + self.column += 1; + c + } + }) + } + } +} + +pub fn expand_tabs(chars: impl Iterator, column: usize, tab_size: usize) -> usize { + let mut expanded = 0; + for c in chars.take(column) { + if c == '\t' { + expanded += tab_size - expanded % tab_size; + } else { + expanded += 1; + } + } + expanded +} + +pub fn collapse_tabs( + mut chars: impl Iterator, + column: usize, + bias: Bias, + tab_size: usize, +) -> (usize, usize) { + let mut expanded = 0; + let mut collapsed = 0; + while let Some(c) = chars.next() { + if expanded == column { + break; + } + + if c == '\t' { + expanded += tab_size - (expanded % tab_size); + if expanded > column { + return match bias { + Bias::Left => (collapsed, expanded - column), + Bias::Right => (collapsed + 1, 0), + }; + } + collapsed += 1; + } else { + expanded += 1; + collapsed += 1; + } + } + (collapsed, 0) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::app::App; + use crate::test_utils::*; + use anyhow::Error; + + #[test] + fn test_chars_at() -> Result<()> { + let mut app = App::new()?; + let text = sample_text(6, 6); + let buffer = app.add_model(|_| Buffer::new(0, text)); + let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx)); + buffer.update(&mut app, |buffer, ctx| { + buffer.edit( + vec![ + Point::new(1, 0)..Point::new(1, 0), + Point::new(1, 1)..Point::new(1, 1), + Point::new(2, 1)..Point::new(2, 1), + ], + "\t", + Some(ctx), + ) + })?; + + map.read(&app, |map, ctx| { + assert_eq!( + map.chars_at(DisplayPoint::new(1, 0), ctx)? + .take(10) + .collect::(), + " b bb" + ); + assert_eq!( + map.chars_at(DisplayPoint::new(1, 2), ctx)? + .take(10) + .collect::(), + " b bbbb" + ); + assert_eq!( + map.chars_at(DisplayPoint::new(1, 6), ctx)? + .take(13) + .collect::(), + " bbbbb\nc c" + ); + + Ok::<(), Error>(()) + })?; + + Ok(()) + } + + #[test] + fn test_expand_tabs() { + assert_eq!(expand_tabs("\t".chars(), 0, 4), 0); + assert_eq!(expand_tabs("\t".chars(), 1, 4), 4); + assert_eq!(expand_tabs("\ta".chars(), 2, 4), 5); + } + + #[test] + fn test_collapse_tabs() { + assert_eq!(collapse_tabs("\t".chars(), 0, Bias::Left, 4), (0, 0)); + assert_eq!(collapse_tabs("\t".chars(), 0, Bias::Right, 4), (0, 0)); + assert_eq!(collapse_tabs("\t".chars(), 1, Bias::Left, 4), (0, 3)); + assert_eq!(collapse_tabs("\t".chars(), 1, Bias::Right, 4), (1, 0)); + assert_eq!(collapse_tabs("\t".chars(), 2, Bias::Left, 4), (0, 2)); + assert_eq!(collapse_tabs("\t".chars(), 2, Bias::Right, 4), (1, 0)); + assert_eq!(collapse_tabs("\t".chars(), 3, Bias::Left, 4), (0, 1)); + assert_eq!(collapse_tabs("\t".chars(), 3, Bias::Right, 4), (1, 0)); + assert_eq!(collapse_tabs("\t".chars(), 4, Bias::Left, 4), (1, 0)); + assert_eq!(collapse_tabs("\t".chars(), 4, Bias::Right, 4), (1, 0)); + assert_eq!(collapse_tabs("\ta".chars(), 5, Bias::Left, 4), (2, 0)); + assert_eq!(collapse_tabs("\ta".chars(), 5, Bias::Right, 4), (2, 0)); + } + + #[test] + fn test_max_point() -> Result<()> { + let mut app = App::new()?; + let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb")); + let map = app.add_model(|ctx| DisplayMap::new(buffer.clone(), 4, ctx)); + map.read(&app, |map, app| { + assert_eq!(map.max_point(app), DisplayPoint::new(1, 11)) + }); + Ok(()) + } +} diff --git a/zed/src/editor/mod.rs b/zed/src/editor/mod.rs new file mode 100644 index 0000000000..f998963376 --- /dev/null +++ b/zed/src/editor/mod.rs @@ -0,0 +1,25 @@ +mod buffer; +mod buffer_element; +pub mod buffer_view; +pub mod display_map; +pub mod movement; + +pub use buffer::*; +pub use buffer_element::*; +pub use buffer_view::*; +pub use display_map::DisplayPoint; +use display_map::*; +use std::{cmp, ops::Range}; + +trait RangeExt { + fn sorted(&self) -> (T, T); +} + +impl RangeExt for Range { + fn sorted(&self) -> (T, T) { + ( + cmp::min(&self.start, &self.end).clone(), + cmp::max(&self.start, &self.end).clone(), + ) + } +} diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs new file mode 100644 index 0000000000..3e38549a05 --- /dev/null +++ b/zed/src/editor/movement.rs @@ -0,0 +1,60 @@ +use super::{DisplayMap, DisplayPoint}; +use crate::app::AppContext; +use anyhow::Result; +use std::cmp; + +pub fn left(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Result { + if point.column() > 0 { + *point.column_mut() -= 1; + } else if point.row() > 0 { + *point.row_mut() -= 1; + *point.column_mut() = map.line_len(point.row(), app)?; + } + Ok(point) +} + +pub fn right(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Result { + let max_column = map.line_len(point.row(), app).unwrap(); + if point.column() < max_column { + *point.column_mut() += 1; + } else if point.row() < map.max_point(app).row() { + *point.row_mut() += 1; + *point.column_mut() = 0; + } + Ok(point) +} + +pub fn up( + map: &DisplayMap, + mut point: DisplayPoint, + goal_column: Option, + app: &AppContext, +) -> Result<(DisplayPoint, Option)> { + let goal_column = goal_column.or(Some(point.column())); + if point.row() > 0 { + *point.row_mut() -= 1; + *point.column_mut() = cmp::min(goal_column.unwrap(), map.line_len(point.row(), app)?); + } else { + point = DisplayPoint::new(0, 0); + } + + Ok((point, goal_column)) +} + +pub fn down( + map: &DisplayMap, + mut point: DisplayPoint, + goal_column: Option, + app: &AppContext, +) -> Result<(DisplayPoint, Option)> { + let goal_column = goal_column.or(Some(point.column())); + let max_point = map.max_point(app); + if point.row() < max_point.row() { + *point.row_mut() += 1; + *point.column_mut() = cmp::min(goal_column.unwrap(), map.line_len(point.row(), app)?) + } else { + point = max_point; + } + + Ok((point, goal_column)) +} diff --git a/zed/src/lib.rs b/zed/src/lib.rs new file mode 100644 index 0000000000..0fe2fd3aa3 --- /dev/null +++ b/zed/src/lib.rs @@ -0,0 +1,3 @@ +// mod editor; +mod sum_tree; +mod time; diff --git a/zed/src/sum_tree/cursor.rs b/zed/src/sum_tree/cursor.rs new file mode 100644 index 0000000000..6a0e0c115c --- /dev/null +++ b/zed/src/sum_tree/cursor.rs @@ -0,0 +1,752 @@ +use super::*; +use arrayvec::ArrayVec; +use std::{cmp::Ordering, sync::Arc}; + +#[derive(Clone)] +struct StackEntry<'a, T: Item, S, U> { + tree: &'a SumTree, + index: usize, + seek_dimension: S, + sum_dimension: U, +} + +#[derive(Clone)] +pub struct Cursor<'a, T: Item, S, U> { + tree: &'a SumTree, + stack: ArrayVec<[StackEntry<'a, T, S, U>; 16]>, + seek_dimension: S, + sum_dimension: U, + did_seek: bool, + at_end: bool, +} + +impl<'a, T, S, U> Cursor<'a, T, S, U> +where + T: Item, + S: Dimension<'a, T::Summary>, + U: Dimension<'a, T::Summary>, +{ + pub fn new(tree: &'a SumTree) -> Self { + Self { + tree, + stack: ArrayVec::new(), + seek_dimension: S::default(), + sum_dimension: U::default(), + did_seek: false, + at_end: false, + } + } + + fn reset(&mut self) { + self.did_seek = false; + self.at_end = false; + self.stack.truncate(0); + self.seek_dimension = S::default(); + self.sum_dimension = U::default(); + } + + pub fn start(&self) -> &U { + &self.sum_dimension + } + + pub fn end(&self) -> U { + if let Some(item_summary) = self.item_summary() { + let mut end = self.start().clone(); + end.add_summary(item_summary); + end + } else { + self.start().clone() + } + } + + pub fn item(&self) -> Option<&'a T> { + assert!(self.did_seek, "Must seek before calling this method"); + if let Some(entry) = self.stack.last() { + match *entry.tree.0 { + Node::Leaf { ref items, .. } => { + if entry.index == items.len() { + None + } else { + Some(&items[entry.index]) + } + } + _ => unreachable!(), + } + } else { + None + } + } + + fn item_summary(&self) -> Option<&'a T::Summary> { + assert!(self.did_seek, "Must seek before calling this method"); + if let Some(entry) = self.stack.last() { + match *entry.tree.0 { + Node::Leaf { + ref item_summaries, .. + } => { + if entry.index == item_summaries.len() { + None + } else { + Some(&item_summaries[entry.index]) + } + } + _ => unreachable!(), + } + } else { + None + } + } + + pub fn prev_item(&self) -> Option<&'a T> { + assert!(self.did_seek, "Must seek before calling this method"); + if let Some(entry) = self.stack.last() { + if entry.index == 0 { + if let Some(prev_leaf) = self.prev_leaf() { + Some(prev_leaf.0.items().last().unwrap()) + } else { + None + } + } else { + match *entry.tree.0 { + Node::Leaf { ref items, .. } => Some(&items[entry.index - 1]), + _ => unreachable!(), + } + } + } else if self.at_end { + self.tree.last() + } else { + None + } + } + + fn prev_leaf(&self) -> Option<&'a SumTree> { + for entry in self.stack.iter().rev().skip(1) { + if entry.index != 0 { + match *entry.tree.0 { + Node::Internal { + ref child_trees, .. + } => return Some(child_trees[entry.index - 1].rightmost_leaf()), + Node::Leaf { .. } => unreachable!(), + }; + } + } + None + } + + pub fn prev(&mut self) { + assert!(self.did_seek, "Must seek before calling this method"); + + if self.at_end { + self.seek_dimension = S::default(); + self.sum_dimension = U::default(); + self.descend_to_last_item(self.tree); + self.at_end = false; + } else { + while let Some(entry) = self.stack.pop() { + if entry.index > 0 { + let new_index = entry.index - 1; + + if let Some(StackEntry { + seek_dimension, + sum_dimension, + .. + }) = self.stack.last() + { + self.seek_dimension = seek_dimension.clone(); + self.sum_dimension = sum_dimension.clone(); + } else { + self.seek_dimension = S::default(); + self.sum_dimension = U::default(); + } + + match entry.tree.0.as_ref() { + Node::Internal { + child_trees, + child_summaries, + .. + } => { + for summary in &child_summaries[0..new_index] { + self.seek_dimension.add_summary(summary); + self.sum_dimension.add_summary(summary); + } + self.stack.push(StackEntry { + tree: entry.tree, + index: new_index, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + self.descend_to_last_item(&child_trees[new_index]); + } + Node::Leaf { item_summaries, .. } => { + for item_summary in &item_summaries[0..new_index] { + self.seek_dimension.add_summary(item_summary); + self.sum_dimension.add_summary(item_summary); + } + self.stack.push(StackEntry { + tree: entry.tree, + index: new_index, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + } + } + + break; + } + } + } + } + + pub fn next(&mut self) { + self.next_internal(|_| true) + } + + fn next_internal(&mut self, filter_node: F) + where + F: Fn(&T::Summary) -> bool, + { + assert!(self.did_seek, "Must seek before calling this method"); + + if self.stack.is_empty() { + if !self.at_end { + self.descend_to_first_item(self.tree, filter_node); + } + } else { + while self.stack.len() > 0 { + let new_subtree = { + let entry = self.stack.last_mut().unwrap(); + match entry.tree.0.as_ref() { + Node::Internal { + child_trees, + child_summaries, + .. + } => { + while entry.index < child_summaries.len() { + entry + .seek_dimension + .add_summary(&child_summaries[entry.index]); + entry + .sum_dimension + .add_summary(&child_summaries[entry.index]); + + entry.index += 1; + if let Some(next_summary) = child_summaries.get(entry.index) { + if filter_node(next_summary) { + break; + } else { + self.seek_dimension.add_summary(next_summary); + self.sum_dimension.add_summary(next_summary); + } + } + } + + child_trees.get(entry.index) + } + Node::Leaf { item_summaries, .. } => loop { + let item_summary = &item_summaries[entry.index]; + self.seek_dimension.add_summary(item_summary); + entry.seek_dimension.add_summary(item_summary); + self.sum_dimension.add_summary(item_summary); + entry.sum_dimension.add_summary(item_summary); + entry.index += 1; + if let Some(next_item_summary) = item_summaries.get(entry.index) { + if filter_node(next_item_summary) { + return; + } + } else { + break None; + } + }, + } + }; + + if let Some(subtree) = new_subtree { + self.descend_to_first_item(subtree, filter_node); + break; + } else { + self.stack.pop(); + } + } + } + + self.at_end = self.stack.is_empty(); + } + + pub fn descend_to_first_item(&mut self, mut subtree: &'a SumTree, filter_node: F) + where + F: Fn(&T::Summary) -> bool, + { + self.did_seek = true; + loop { + subtree = match *subtree.0 { + Node::Internal { + ref child_trees, + ref child_summaries, + .. + } => { + let mut new_index = None; + for (index, summary) in child_summaries.iter().enumerate() { + if filter_node(summary) { + new_index = Some(index); + break; + } + self.seek_dimension.add_summary(summary); + self.sum_dimension.add_summary(summary); + } + + if let Some(new_index) = new_index { + self.stack.push(StackEntry { + tree: subtree, + index: new_index, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + &child_trees[new_index] + } else { + break; + } + } + Node::Leaf { + ref item_summaries, .. + } => { + let mut new_index = None; + for (index, item_summary) in item_summaries.iter().enumerate() { + if filter_node(item_summary) { + new_index = Some(index); + break; + } + self.seek_dimension.add_summary(item_summary); + self.sum_dimension.add_summary(item_summary); + } + + if let Some(new_index) = new_index { + self.stack.push(StackEntry { + tree: subtree, + index: new_index, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + } + break; + } + } + } + } + + fn descend_to_last_item(&mut self, mut subtree: &'a SumTree) { + self.did_seek = true; + loop { + match subtree.0.as_ref() { + Node::Internal { + child_trees, + child_summaries, + .. + } => { + for summary in &child_summaries[0..child_summaries.len() - 1] { + self.seek_dimension.add_summary(summary); + self.sum_dimension.add_summary(summary); + } + + self.stack.push(StackEntry { + tree: subtree, + index: child_trees.len() - 1, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + subtree = child_trees.last().unwrap(); + } + Node::Leaf { item_summaries, .. } => { + let last_index = item_summaries.len().saturating_sub(1); + for item_summary in &item_summaries[0..last_index] { + self.seek_dimension.add_summary(item_summary); + self.sum_dimension.add_summary(item_summary); + } + self.stack.push(StackEntry { + tree: subtree, + index: last_index, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + break; + } + } + } + } +} + +impl<'a, T, S, U> Cursor<'a, T, S, U> +where + T: Item, + S: Dimension<'a, T::Summary> + Ord, + U: Dimension<'a, T::Summary>, +{ + pub fn seek(&mut self, pos: &S, bias: SeekBias) -> bool { + self.reset(); + self.seek_internal::<()>(pos, bias, &mut SeekAggregate::None) + } + + pub fn seek_forward(&mut self, pos: &S, bias: SeekBias) -> bool { + self.seek_internal::<()>(pos, bias, &mut SeekAggregate::None) + } + + pub fn slice(&mut self, end: &S, bias: SeekBias) -> SumTree { + let mut slice = SeekAggregate::Slice(SumTree::new()); + self.seek_internal::<()>(end, bias, &mut slice); + if let SeekAggregate::Slice(slice) = slice { + slice + } else { + unreachable!() + } + } + + pub fn suffix(&mut self) -> SumTree { + let extent = self.tree.extent::(); + let mut slice = SeekAggregate::Slice(SumTree::new()); + self.seek_internal::<()>(&extent, SeekBias::Right, &mut slice); + if let SeekAggregate::Slice(slice) = slice { + slice + } else { + unreachable!() + } + } + + pub fn summary(&mut self, end: &S, bias: SeekBias) -> D + where + D: Dimension<'a, T::Summary>, + { + let mut summary = SeekAggregate::Summary(D::default()); + self.seek_internal(end, bias, &mut summary); + if let SeekAggregate::Summary(summary) = summary { + summary + } else { + unreachable!() + } + } + + fn seek_internal( + &mut self, + target: &S, + bias: SeekBias, + aggregate: &mut SeekAggregate, + ) -> bool + where + D: Dimension<'a, T::Summary>, + { + debug_assert!(target >= &self.seek_dimension); + let mut containing_subtree = None; + + if self.did_seek { + 'outer: while let Some(entry) = self.stack.last_mut() { + { + match *entry.tree.0 { + Node::Internal { + ref child_summaries, + ref child_trees, + .. + } => { + entry.index += 1; + for (child_tree, child_summary) in child_trees[entry.index..] + .iter() + .zip(&child_summaries[entry.index..]) + { + let mut child_end = self.seek_dimension.clone(); + child_end.add_summary(&child_summary); + + let comparison = target.cmp(&child_end); + if comparison == Ordering::Greater + || (comparison == Ordering::Equal && bias == SeekBias::Right) + { + self.seek_dimension.add_summary(child_summary); + self.sum_dimension.add_summary(child_summary); + match aggregate { + SeekAggregate::None => {} + SeekAggregate::Slice(slice) => { + slice.push_tree(child_tree.clone()); + } + SeekAggregate::Summary(summary) => { + summary.add_summary(child_summary); + } + } + entry.index += 1; + } else { + containing_subtree = Some(child_tree); + break 'outer; + } + } + } + Node::Leaf { + ref items, + ref item_summaries, + .. + } => { + let mut slice_items = ArrayVec::<[T; 2 * TREE_BASE]>::new(); + let mut slice_item_summaries = + ArrayVec::<[T::Summary; 2 * TREE_BASE]>::new(); + let mut slice_items_summary = match aggregate { + SeekAggregate::Slice(_) => Some(T::Summary::default()), + _ => None, + }; + + for (item, item_summary) in items[entry.index..] + .iter() + .zip(&item_summaries[entry.index..]) + { + let mut item_end = self.seek_dimension.clone(); + item_end.add_summary(item_summary); + + let comparison = target.cmp(&item_end); + if comparison == Ordering::Greater + || (comparison == Ordering::Equal && bias == SeekBias::Right) + { + self.seek_dimension.add_summary(item_summary); + self.sum_dimension.add_summary(item_summary); + match aggregate { + SeekAggregate::None => {} + SeekAggregate::Slice(_) => { + slice_items.push(item.clone()); + slice_item_summaries.push(item_summary.clone()); + *slice_items_summary.as_mut().unwrap() += item_summary; + } + SeekAggregate::Summary(summary) => { + summary.add_summary(item_summary); + } + } + entry.index += 1; + } else { + if let SeekAggregate::Slice(slice) = aggregate { + slice.push_tree(SumTree(Arc::new(Node::Leaf { + summary: slice_items_summary.unwrap(), + items: slice_items, + item_summaries: slice_item_summaries, + }))); + } + break 'outer; + } + } + + if let SeekAggregate::Slice(slice) = aggregate { + if !slice_items.is_empty() { + slice.push_tree(SumTree(Arc::new(Node::Leaf { + summary: slice_items_summary.unwrap(), + items: slice_items, + item_summaries: slice_item_summaries, + }))); + } + } + } + } + } + + self.stack.pop(); + } + } else { + self.did_seek = true; + containing_subtree = Some(self.tree); + } + + if let Some(mut subtree) = containing_subtree { + loop { + let mut next_subtree = None; + match *subtree.0 { + Node::Internal { + ref child_summaries, + ref child_trees, + .. + } => { + for (index, (child_tree, child_summary)) in + child_trees.iter().zip(child_summaries).enumerate() + { + let mut child_end = self.seek_dimension.clone(); + child_end.add_summary(child_summary); + + let comparison = target.cmp(&child_end); + if comparison == Ordering::Greater + || (comparison == Ordering::Equal && bias == SeekBias::Right) + { + self.seek_dimension.add_summary(child_summary); + self.sum_dimension.add_summary(child_summary); + match aggregate { + SeekAggregate::None => {} + SeekAggregate::Slice(slice) => { + slice.push_tree(child_trees[index].clone()); + } + SeekAggregate::Summary(summary) => { + summary.add_summary(child_summary); + } + } + } else { + self.stack.push(StackEntry { + tree: subtree, + index, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + next_subtree = Some(child_tree); + break; + } + } + } + Node::Leaf { + ref items, + ref item_summaries, + .. + } => { + let mut slice_items = ArrayVec::<[T; 2 * TREE_BASE]>::new(); + let mut slice_item_summaries = + ArrayVec::<[T::Summary; 2 * TREE_BASE]>::new(); + let mut slice_items_summary = match aggregate { + SeekAggregate::Slice(_) => Some(T::Summary::default()), + _ => None, + }; + + for (index, (item, item_summary)) in + items.iter().zip(item_summaries).enumerate() + { + let mut child_end = self.seek_dimension.clone(); + child_end.add_summary(item_summary); + + let comparison = target.cmp(&child_end); + if comparison == Ordering::Greater + || (comparison == Ordering::Equal && bias == SeekBias::Right) + { + self.seek_dimension.add_summary(item_summary); + self.sum_dimension.add_summary(item_summary); + match aggregate { + SeekAggregate::None => {} + SeekAggregate::Slice(_) => { + slice_items.push(item.clone()); + *slice_items_summary.as_mut().unwrap() += item_summary; + slice_item_summaries.push(item_summary.clone()); + } + SeekAggregate::Summary(summary) => { + summary.add_summary(item_summary); + } + } + } else { + self.stack.push(StackEntry { + tree: subtree, + index, + seek_dimension: self.seek_dimension.clone(), + sum_dimension: self.sum_dimension.clone(), + }); + break; + } + } + + if let SeekAggregate::Slice(slice) = aggregate { + if !slice_items.is_empty() { + slice.push_tree(SumTree(Arc::new(Node::Leaf { + summary: slice_items_summary.unwrap(), + items: slice_items, + item_summaries: slice_item_summaries, + }))); + } + } + } + }; + + if let Some(next_subtree) = next_subtree { + subtree = next_subtree; + } else { + break; + } + } + } + + self.at_end = self.stack.is_empty(); + if bias == SeekBias::Left { + let mut end = self.seek_dimension.clone(); + if let Some(summary) = self.item_summary() { + end.add_summary(summary); + } + *target == end + } else { + *target == self.seek_dimension + } + } +} + +impl<'a, T, S, U> Iterator for Cursor<'a, T, S, U> +where + T: Item, + S: Dimension<'a, T::Summary>, + U: Dimension<'a, T::Summary>, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + if !self.did_seek { + self.descend_to_first_item(self.tree, |_| true); + } + + if let Some(item) = self.item() { + self.next(); + Some(item) + } else { + None + } + } +} + +pub struct FilterCursor<'a, F: Fn(&T::Summary) -> bool, T: Item, U> { + cursor: Cursor<'a, T, (), U>, + filter_node: F, +} + +impl<'a, F, T, U> FilterCursor<'a, F, T, U> +where + F: Fn(&T::Summary) -> bool, + T: Item, + U: Dimension<'a, T::Summary>, +{ + pub fn new(tree: &'a SumTree, filter_node: F) -> Self { + let mut cursor = tree.cursor::<(), U>(); + if filter_node(&tree.summary()) { + cursor.descend_to_first_item(tree, &filter_node); + } else { + cursor.did_seek = true; + cursor.at_end = true; + } + + Self { + cursor, + filter_node, + } + } + + pub fn start(&self) -> &U { + self.cursor.start() + } + + pub fn item(&self) -> Option<&'a T> { + self.cursor.item() + } + + pub fn next(&mut self) { + self.cursor.next_internal(&self.filter_node); + } +} + +impl<'a, F, T, U> Iterator for FilterCursor<'a, F, T, U> +where + F: Fn(&T::Summary) -> bool, + T: Item, + U: Dimension<'a, T::Summary>, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + if let Some(item) = self.item() { + self.cursor.next_internal(&self.filter_node); + Some(item) + } else { + None + } + } +} + +enum SeekAggregate { + None, + Slice(SumTree), + Summary(D), +} diff --git a/zed/src/sum_tree/mod.rs b/zed/src/sum_tree/mod.rs new file mode 100644 index 0000000000..47dc31797b --- /dev/null +++ b/zed/src/sum_tree/mod.rs @@ -0,0 +1,820 @@ +mod cursor; + +use arrayvec::ArrayVec; +pub use cursor::Cursor; +pub use cursor::FilterCursor; +use std::{fmt, iter::FromIterator, ops::AddAssign, sync::Arc}; + +#[cfg(test)] +const TREE_BASE: usize = 2; +#[cfg(not(test))] +const TREE_BASE: usize = 6; + +pub trait Item: Clone + Eq + fmt::Debug { + type Summary: for<'a> AddAssign<&'a Self::Summary> + Default + Clone + fmt::Debug; + + fn summary(&self) -> Self::Summary; +} + +pub trait KeyedItem: Item { + type Key: for<'a> Dimension<'a, Self::Summary> + Ord; + + fn key(&self) -> Self::Key; +} + +pub trait Dimension<'a, Summary: Default>: 'a + Clone + fmt::Debug + Default { + fn add_summary(&mut self, summary: &'a Summary); +} + +impl<'a, T: Default> Dimension<'a, T> for () { + fn add_summary(&mut self, _: &'a T) {} +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum SeekBias { + Left, + Right, +} + +#[derive(Debug, Clone)] +pub struct SumTree(Arc>); + +impl SumTree { + pub fn new() -> Self { + SumTree(Arc::new(Node::Leaf { + summary: T::Summary::default(), + items: ArrayVec::new(), + item_summaries: ArrayVec::new(), + })) + } + + pub fn from_item(item: T) -> Self { + let mut tree = Self::new(); + tree.push(item); + tree + } + + pub fn items(&self) -> Vec { + let mut cursor = self.cursor::<(), ()>(); + cursor.descend_to_first_item(self, |_| true); + cursor.cloned().collect() + } + + pub fn cursor<'a, S, U>(&'a self) -> Cursor + where + S: Dimension<'a, T::Summary>, + U: Dimension<'a, T::Summary>, + { + Cursor::new(self) + } + + pub fn filter<'a, F, U>(&'a self, filter_node: F) -> FilterCursor + where + F: Fn(&T::Summary) -> bool, + U: Dimension<'a, T::Summary>, + { + FilterCursor::new(self, filter_node) + } + + #[allow(dead_code)] + pub fn first(&self) -> Option<&T> { + self.leftmost_leaf().0.items().first() + } + + pub fn last(&self) -> Option<&T> { + self.rightmost_leaf().0.items().last() + } + + pub fn extent<'a, D: Dimension<'a, T::Summary>>(&'a self) -> D { + let mut extent = D::default(); + match self.0.as_ref() { + Node::Internal { summary, .. } | Node::Leaf { summary, .. } => { + extent.add_summary(summary) + } + } + extent + } + + pub fn summary(&self) -> T::Summary { + match self.0.as_ref() { + Node::Internal { summary, .. } => summary.clone(), + Node::Leaf { summary, .. } => summary.clone(), + } + } + + #[cfg(test)] + pub fn is_empty(&self) -> bool { + match self.0.as_ref() { + Node::Internal { .. } => false, + Node::Leaf { items, .. } => items.is_empty(), + } + } + + pub fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + let mut leaf: Option> = None; + + for item in iter { + if leaf.is_some() && leaf.as_ref().unwrap().items().len() == 2 * TREE_BASE { + self.push_tree(SumTree(Arc::new(leaf.take().unwrap()))); + } + + if leaf.is_none() { + leaf = Some(Node::Leaf:: { + summary: T::Summary::default(), + items: ArrayVec::new(), + item_summaries: ArrayVec::new(), + }); + } + + if let Some(Node::Leaf { + summary, + items, + item_summaries, + }) = leaf.as_mut() + { + let item_summary = item.summary(); + *summary += &item_summary; + items.push(item); + item_summaries.push(item_summary); + } else { + unreachable!() + } + } + + if leaf.is_some() { + self.push_tree(SumTree(Arc::new(leaf.take().unwrap()))); + } + } + + pub fn push(&mut self, item: T) { + let summary = item.summary(); + self.push_tree(SumTree::from_child_trees(vec![SumTree(Arc::new( + Node::Leaf { + summary: summary.clone(), + items: ArrayVec::from_iter(Some(item)), + item_summaries: ArrayVec::from_iter(Some(summary)), + }, + ))])) + } + + pub fn push_tree(&mut self, other: Self) { + let other_node = other.0.clone(); + if !other_node.is_leaf() || other_node.items().len() > 0 { + if self.0.height() < other_node.height() { + for tree in other_node.child_trees() { + self.push_tree(tree.clone()); + } + } else if let Some(split_tree) = self.push_tree_recursive(other) { + *self = Self::from_child_trees(vec![self.clone(), split_tree]); + } + } + } + + fn push_tree_recursive(&mut self, other: SumTree) -> Option> { + match Arc::make_mut(&mut self.0) { + Node::Internal { + height, + summary, + child_summaries, + child_trees, + .. + } => { + let other_node = other.0.clone(); + *summary += other_node.summary(); + + let height_delta = *height - other_node.height(); + let mut summaries_to_append = ArrayVec::<[T::Summary; 2 * TREE_BASE]>::new(); + let mut trees_to_append = ArrayVec::<[SumTree; 2 * TREE_BASE]>::new(); + if height_delta == 0 { + summaries_to_append.extend(other_node.child_summaries().iter().cloned()); + trees_to_append.extend(other_node.child_trees().iter().cloned()); + } else if height_delta == 1 && !other_node.is_underflowing() { + summaries_to_append.push(other_node.summary().clone()); + trees_to_append.push(other) + } else { + let tree_to_append = child_trees.last_mut().unwrap().push_tree_recursive(other); + *child_summaries.last_mut().unwrap() = + child_trees.last().unwrap().0.summary().clone(); + + if let Some(split_tree) = tree_to_append { + summaries_to_append.push(split_tree.0.summary().clone()); + trees_to_append.push(split_tree); + } + } + + let child_count = child_trees.len() + trees_to_append.len(); + if child_count > 2 * TREE_BASE { + let left_summaries: ArrayVec<_>; + let right_summaries: ArrayVec<_>; + let left_trees; + let right_trees; + + let midpoint = (child_count + child_count % 2) / 2; + { + let mut all_summaries = child_summaries + .iter() + .chain(summaries_to_append.iter()) + .cloned(); + left_summaries = all_summaries.by_ref().take(midpoint).collect(); + right_summaries = all_summaries.collect(); + let mut all_trees = + child_trees.iter().chain(trees_to_append.iter()).cloned(); + left_trees = all_trees.by_ref().take(midpoint).collect(); + right_trees = all_trees.collect(); + } + *summary = sum(left_summaries.iter()); + *child_summaries = left_summaries; + *child_trees = left_trees; + + Some(SumTree(Arc::new(Node::Internal { + height: *height, + summary: sum(right_summaries.iter()), + child_summaries: right_summaries, + child_trees: right_trees, + }))) + } else { + child_summaries.extend(summaries_to_append); + child_trees.extend(trees_to_append); + None + } + } + Node::Leaf { + summary, + items, + item_summaries, + } => { + let other_node = other.0; + + let child_count = items.len() + other_node.items().len(); + if child_count > 2 * TREE_BASE { + let left_items; + let right_items; + let left_summaries; + let right_summaries: ArrayVec<[T::Summary; 2 * TREE_BASE]>; + + let midpoint = (child_count + child_count % 2) / 2; + { + let mut all_items = items.iter().chain(other_node.items().iter()).cloned(); + left_items = all_items.by_ref().take(midpoint).collect(); + right_items = all_items.collect(); + + let mut all_summaries = item_summaries + .iter() + .chain(other_node.child_summaries()) + .cloned(); + left_summaries = all_summaries.by_ref().take(midpoint).collect(); + right_summaries = all_summaries.collect(); + } + *items = left_items; + *item_summaries = left_summaries; + *summary = sum(item_summaries.iter()); + Some(SumTree(Arc::new(Node::Leaf { + items: right_items, + summary: sum(right_summaries.iter()), + item_summaries: right_summaries, + }))) + } else { + *summary += other_node.summary(); + items.extend(other_node.items().iter().cloned()); + item_summaries.extend(other_node.child_summaries().iter().cloned()); + None + } + } + } + } + + fn from_child_trees(child_trees: Vec>) -> Self { + let height = child_trees[0].0.height() + 1; + let mut child_summaries = ArrayVec::new(); + for child in &child_trees { + child_summaries.push(child.0.summary().clone()); + } + let summary = sum(child_summaries.iter()); + SumTree(Arc::new(Node::Internal { + height, + summary, + child_summaries, + child_trees: ArrayVec::from_iter(child_trees), + })) + } + + fn leftmost_leaf(&self) -> &Self { + match *self.0 { + Node::Leaf { .. } => self, + Node::Internal { + ref child_trees, .. + } => child_trees.first().unwrap().leftmost_leaf(), + } + } + + fn rightmost_leaf(&self) -> &Self { + match *self.0 { + Node::Leaf { .. } => self, + Node::Internal { + ref child_trees, .. + } => child_trees.last().unwrap().rightmost_leaf(), + } + } +} + +impl SumTree { + pub fn insert(&mut self, item: T) { + *self = { + let mut cursor = self.cursor::(); + let mut new_tree = cursor.slice(&item.key(), SeekBias::Left); + new_tree.push(item); + new_tree.push_tree(cursor.suffix()); + new_tree + }; + } + + pub fn edit(&mut self, edits: &mut [Edit]) { + if edits.is_empty() { + return; + } + + edits.sort_unstable_by_key(|item| item.key()); + + *self = { + let mut cursor = self.cursor::(); + let mut new_tree = SumTree::new(); + let mut buffered_items = Vec::new(); + + cursor.seek(&T::Key::default(), SeekBias::Left); + for edit in edits { + let new_key = edit.key(); + let mut old_item = cursor.item(); + + if old_item + .as_ref() + .map_or(false, |old_item| old_item.key() < new_key) + { + new_tree.extend(buffered_items.drain(..)); + let slice = cursor.slice(&new_key, SeekBias::Left); + new_tree.push_tree(slice); + old_item = cursor.item(); + } + if old_item.map_or(false, |old_item| old_item.key() == new_key) { + cursor.next(); + } + match edit { + Edit::Insert(item) => { + buffered_items.push(item.clone()); + } + Edit::Remove(_) => {} + } + } + + new_tree.extend(buffered_items); + new_tree.push_tree(cursor.suffix()); + new_tree + }; + } +} + +#[derive(Clone, Debug)] +pub enum Node { + Internal { + height: u8, + summary: T::Summary, + child_summaries: ArrayVec<[T::Summary; 2 * TREE_BASE]>, + child_trees: ArrayVec<[SumTree; 2 * TREE_BASE]>, + }, + Leaf { + summary: T::Summary, + items: ArrayVec<[T; 2 * TREE_BASE]>, + item_summaries: ArrayVec<[T::Summary; 2 * TREE_BASE]>, + }, +} + +impl Node { + fn is_leaf(&self) -> bool { + match self { + Node::Leaf { .. } => true, + _ => false, + } + } + + fn height(&self) -> u8 { + match self { + Node::Internal { height, .. } => *height, + Node::Leaf { .. } => 0, + } + } + + fn summary(&self) -> &T::Summary { + match self { + Node::Internal { summary, .. } => summary, + Node::Leaf { summary, .. } => summary, + } + } + + fn child_summaries(&self) -> &[T::Summary] { + match self { + Node::Internal { + child_summaries, .. + } => child_summaries.as_slice(), + Node::Leaf { item_summaries, .. } => item_summaries.as_slice(), + } + } + + fn child_trees(&self) -> &ArrayVec<[SumTree; 2 * TREE_BASE]> { + match self { + Node::Internal { child_trees, .. } => child_trees, + Node::Leaf { .. } => panic!("Leaf nodes have no child trees"), + } + } + + fn items(&self) -> &ArrayVec<[T; 2 * TREE_BASE]> { + match self { + Node::Leaf { items, .. } => items, + Node::Internal { .. } => panic!("Internal nodes have no items"), + } + } + + fn is_underflowing(&self) -> bool { + match self { + Node::Internal { child_trees, .. } => child_trees.len() < TREE_BASE, + Node::Leaf { items, .. } => items.len() < TREE_BASE, + } + } +} + +#[derive(Debug)] +pub enum Edit { + Insert(T), + Remove(T), +} + +impl Edit { + fn key(&self) -> T::Key { + match self { + Edit::Insert(item) | Edit::Remove(item) => item.key(), + } + } +} + +fn sum<'a, T, I>(iter: I) -> T +where + T: 'a + Default + AddAssign<&'a T>, + I: Iterator, +{ + let mut sum = T::default(); + for value in iter { + sum += value; + } + sum +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ops::Add; + + #[test] + fn test_extend_and_push_tree() { + let mut tree1 = SumTree::new(); + tree1.extend(0..20); + + let mut tree2 = SumTree::new(); + tree2.extend(50..100); + + tree1.push_tree(tree2); + assert_eq!(tree1.items(), (0..20).chain(50..100).collect::>()); + } + + #[test] + fn test_random() { + for seed in 0..100 { + use rand::{distributions, prelude::*}; + + let rng = &mut StdRng::seed_from_u64(seed); + + let mut tree = SumTree::::new(); + let count = rng.gen_range(0..10); + tree.extend(rng.sample_iter(distributions::Standard).take(count)); + + for _ in 0..5 { + let splice_end = rng.gen_range(0..tree.extent::().0 + 1); + let splice_start = rng.gen_range(0..splice_end + 1); + let count = rng.gen_range(0..3); + let tree_end = tree.extent::(); + let new_items = rng + .sample_iter(distributions::Standard) + .take(count) + .collect::>(); + + let mut reference_items = tree.items(); + reference_items.splice(splice_start..splice_end, new_items.clone()); + + tree = { + let mut cursor = tree.cursor::(); + let mut new_tree = cursor.slice(&Count(splice_start), SeekBias::Right); + new_tree.extend(new_items); + cursor.seek(&Count(splice_end), SeekBias::Right); + new_tree.push_tree(cursor.slice(&tree_end, SeekBias::Right)); + new_tree + }; + + assert_eq!(tree.items(), reference_items); + + let mut filter_cursor = tree.filter::<_, Count>(|summary| summary.contains_even); + let mut reference_filter = tree + .items() + .into_iter() + .enumerate() + .filter(|(_, item)| (item & 1) == 0); + while let Some(actual_item) = filter_cursor.item() { + let (reference_index, reference_item) = reference_filter.next().unwrap(); + assert_eq!(actual_item, &reference_item); + assert_eq!(filter_cursor.start().0, reference_index); + filter_cursor.next(); + } + assert!(reference_filter.next().is_none()); + + let mut pos = rng.gen_range(0..tree.extent::().0 + 1); + let mut before_start = false; + let mut cursor = tree.cursor::(); + cursor.seek(&Count(pos), SeekBias::Right); + + for i in 0..10 { + assert_eq!(cursor.start().0, pos); + + if pos > 0 { + assert_eq!(cursor.prev_item().unwrap(), &reference_items[pos - 1]); + } else { + assert_eq!(cursor.prev_item(), None); + } + + if pos < reference_items.len() && !before_start { + assert_eq!(cursor.item().unwrap(), &reference_items[pos]); + } else { + assert_eq!(cursor.item(), None); + } + + if i < 5 { + cursor.next(); + if pos < reference_items.len() { + pos += 1; + before_start = false; + } + } else { + cursor.prev(); + if pos == 0 { + before_start = true; + } + pos = pos.saturating_sub(1); + } + } + } + + for _ in 0..10 { + let end = rng.gen_range(0..tree.extent::().0 + 1); + let start = rng.gen_range(0..end + 1); + let start_bias = if rng.gen() { + SeekBias::Left + } else { + SeekBias::Right + }; + let end_bias = if rng.gen() { + SeekBias::Left + } else { + SeekBias::Right + }; + + let mut cursor = tree.cursor::(); + cursor.seek(&Count(start), start_bias); + let slice = cursor.slice(&Count(end), end_bias); + + cursor.seek(&Count(start), start_bias); + let summary = cursor.summary::(&Count(end), end_bias); + + assert_eq!(summary, slice.summary().sum); + } + } + } + + #[test] + fn test_cursor() { + // Empty tree + let tree = SumTree::::new(); + let mut cursor = tree.cursor::(); + assert_eq!( + cursor.slice(&Count(0), SeekBias::Right).items(), + Vec::::new() + ); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.start(), &Sum(0)); + + // Single-element tree + let mut tree = SumTree::::new(); + tree.extend(vec![1]); + let mut cursor = tree.cursor::(); + assert_eq!( + cursor.slice(&Count(0), SeekBias::Right).items(), + Vec::::new() + ); + assert_eq!(cursor.item(), Some(&1)); + assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.start(), &Sum(0)); + + cursor.next(); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.start(), &Sum(1)); + + cursor.prev(); + assert_eq!(cursor.item(), Some(&1)); + assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.start(), &Sum(0)); + + let mut cursor = tree.cursor::(); + assert_eq!(cursor.slice(&Count(1), SeekBias::Right).items(), [1]); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.start(), &Sum(1)); + + cursor.seek(&Count(0), SeekBias::Right); + assert_eq!( + cursor + .slice(&tree.extent::(), SeekBias::Right) + .items(), + [1] + ); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.start(), &Sum(1)); + + // Multiple-element tree + let mut tree = SumTree::new(); + tree.extend(vec![1, 2, 3, 4, 5, 6]); + let mut cursor = tree.cursor::(); + + assert_eq!(cursor.slice(&Count(2), SeekBias::Right).items(), [1, 2]); + assert_eq!(cursor.item(), Some(&3)); + assert_eq!(cursor.prev_item(), Some(&2)); + assert_eq!(cursor.start(), &Sum(3)); + + cursor.next(); + assert_eq!(cursor.item(), Some(&4)); + assert_eq!(cursor.prev_item(), Some(&3)); + assert_eq!(cursor.start(), &Sum(6)); + + cursor.next(); + assert_eq!(cursor.item(), Some(&5)); + assert_eq!(cursor.prev_item(), Some(&4)); + assert_eq!(cursor.start(), &Sum(10)); + + cursor.next(); + assert_eq!(cursor.item(), Some(&6)); + assert_eq!(cursor.prev_item(), Some(&5)); + assert_eq!(cursor.start(), &Sum(15)); + + cursor.next(); + cursor.next(); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.start(), &Sum(21)); + + cursor.prev(); + assert_eq!(cursor.item(), Some(&6)); + assert_eq!(cursor.prev_item(), Some(&5)); + assert_eq!(cursor.start(), &Sum(15)); + + cursor.prev(); + assert_eq!(cursor.item(), Some(&5)); + assert_eq!(cursor.prev_item(), Some(&4)); + assert_eq!(cursor.start(), &Sum(10)); + + cursor.prev(); + assert_eq!(cursor.item(), Some(&4)); + assert_eq!(cursor.prev_item(), Some(&3)); + assert_eq!(cursor.start(), &Sum(6)); + + cursor.prev(); + assert_eq!(cursor.item(), Some(&3)); + assert_eq!(cursor.prev_item(), Some(&2)); + assert_eq!(cursor.start(), &Sum(3)); + + cursor.prev(); + assert_eq!(cursor.item(), Some(&2)); + assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.start(), &Sum(1)); + + cursor.prev(); + assert_eq!(cursor.item(), Some(&1)); + assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.start(), &Sum(0)); + + cursor.prev(); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.start(), &Sum(0)); + + cursor.next(); + assert_eq!(cursor.item(), Some(&1)); + assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.start(), &Sum(0)); + + let mut cursor = tree.cursor::(); + assert_eq!( + cursor + .slice(&tree.extent::(), SeekBias::Right) + .items(), + tree.items() + ); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.start(), &Sum(21)); + + cursor.seek(&Count(3), SeekBias::Right); + assert_eq!( + cursor + .slice(&tree.extent::(), SeekBias::Right) + .items(), + [4, 5, 6] + ); + assert_eq!(cursor.item(), None); + assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.start(), &Sum(21)); + + // Seeking can bias left or right + cursor.seek(&Count(1), SeekBias::Left); + assert_eq!(cursor.item(), Some(&1)); + cursor.seek(&Count(1), SeekBias::Right); + assert_eq!(cursor.item(), Some(&2)); + + // Slicing without resetting starts from where the cursor is parked at. + cursor.seek(&Count(1), SeekBias::Right); + assert_eq!(cursor.slice(&Count(3), SeekBias::Right).items(), vec![2, 3]); + assert_eq!(cursor.slice(&Count(6), SeekBias::Left).items(), vec![4, 5]); + assert_eq!(cursor.slice(&Count(6), SeekBias::Right).items(), vec![6]); + } + + #[derive(Clone, Default, Debug)] + pub struct IntegersSummary { + count: Count, + sum: Sum, + contains_even: bool, + } + + #[derive(Ord, PartialOrd, Default, Eq, PartialEq, Clone, Debug)] + struct Count(usize); + + #[derive(Ord, PartialOrd, Default, Eq, PartialEq, Clone, Debug)] + struct Sum(usize); + + impl Item for u8 { + type Summary = IntegersSummary; + + fn summary(&self) -> Self::Summary { + IntegersSummary { + count: Count(1), + sum: Sum(*self as usize), + contains_even: (*self & 1) == 0, + } + } + } + + impl<'a> AddAssign<&'a Self> for IntegersSummary { + fn add_assign(&mut self, other: &Self) { + self.count.0 += &other.count.0; + self.sum.0 += &other.sum.0; + self.contains_even |= other.contains_even; + } + } + + impl<'a> Dimension<'a, IntegersSummary> for Count { + fn add_summary(&mut self, summary: &IntegersSummary) { + self.0 += summary.count.0; + } + } + + // impl<'a> Add<&'a Self> for Count { + // type Output = Self; + // + // fn add(mut self, other: &Self) -> Self { + // self.0 += other.0; + // self + // } + // } + + impl<'a> Dimension<'a, IntegersSummary> for Sum { + fn add_summary(&mut self, summary: &IntegersSummary) { + self.0 += summary.sum.0; + } + } + + impl<'a> Add<&'a Self> for Sum { + type Output = Self; + + fn add(mut self, other: &Self) -> Self { + self.0 += other.0; + self + } + } +} diff --git a/zed/src/time.rs b/zed/src/time.rs new file mode 100644 index 0000000000..9467535441 --- /dev/null +++ b/zed/src/time.rs @@ -0,0 +1,140 @@ +use std::cmp::{self, Ordering}; +use std::collections::HashMap; +use std::mem; +use std::ops::{Add, AddAssign}; +use std::sync::Arc; + +pub type ReplicaId = u16; + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd)] +pub struct Local { + pub replica_id: ReplicaId, + pub value: u64, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Global(Arc>); + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Lamport { + pub value: u64, + pub replica_id: ReplicaId, +} + +impl Local { + pub fn new(replica_id: ReplicaId) -> Self { + Self { + replica_id, + value: 1, + } + } + + pub fn tick(&mut self) -> Self { + let timestamp = *self; + self.value += 1; + timestamp + } + + pub fn observe(&mut self, timestamp: Self) { + if timestamp.replica_id == self.replica_id { + self.value = cmp::max(self.value, timestamp.value + 1); + } + } +} + +impl<'a> Add<&'a Self> for Local { + type Output = Local; + + fn add(self, other: &'a Self) -> Self::Output { + cmp::max(&self, other).clone() + } +} + +impl<'a> AddAssign<&'a Local> for Local { + fn add_assign(&mut self, other: &Self) { + if *self < *other { + *self = other.clone(); + } + } +} + +impl Global { + pub fn new() -> Self { + Global(Arc::new(HashMap::new())) + } + + pub fn get(&self, replica_id: ReplicaId) -> u64 { + *self.0.get(&replica_id).unwrap_or(&0) + } + + pub fn observe(&mut self, timestamp: Local) { + let map = Arc::make_mut(&mut self.0); + let value = map.entry(timestamp.replica_id).or_insert(0); + *value = cmp::max(*value, timestamp.value); + } + + pub fn observe_all(&mut self, other: &Self) { + for (replica_id, value) in other.0.as_ref() { + self.observe(Local { + replica_id: *replica_id, + value: *value, + }); + } + } + + pub fn observed(&self, timestamp: Local) -> bool { + self.get(timestamp.replica_id) >= timestamp.value + } + + pub fn changed_since(&self, other: &Self) -> bool { + self.0 + .iter() + .any(|(replica_id, value)| *value > other.get(*replica_id)) + } +} + +impl PartialOrd for Global { + fn partial_cmp(&self, other: &Self) -> Option { + let mut global_ordering = Ordering::Equal; + + for replica_id in self.0.keys().chain(other.0.keys()) { + let ordering = self.get(*replica_id).cmp(&other.get(*replica_id)); + if ordering != Ordering::Equal { + if global_ordering == Ordering::Equal { + global_ordering = ordering; + } else if ordering != global_ordering { + return None; + } + } + } + + Some(global_ordering) + } +} + +impl Lamport { + pub fn new(replica_id: ReplicaId) -> Self { + Self { + value: 1, + replica_id, + } + } + + pub fn tick(&mut self) -> Self { + let timestamp = *self; + self.value += 1; + timestamp + } + + pub fn observe(&mut self, timestamp: Self) { + self.value = cmp::max(self.value, timestamp.value) + 1; + } + + pub fn to_bytes(&self) -> [u8; 24] { + let mut bytes = [0; 24]; + bytes[0..8].copy_from_slice(unsafe { &mem::transmute::(self.value.to_be()) }); + bytes[8..10] + .copy_from_slice(unsafe { &mem::transmute::(self.replica_id.to_be()) }); + bytes + } +}