From 23308e17a931a25ab3dfdf205ef24cd7633d9432 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 18 Mar 2021 13:13:31 -0600 Subject: [PATCH] WIP on rebuilding with extracted UI framework --- Cargo.lock | 105 +++- gpui/Cargo.toml | 1 + gpui/src/app.rs | 220 +++++---- gpui/src/assets.rs | 27 + gpui/src/elements/label.rs | 1 + gpui/src/elements/mod.rs | 7 +- gpui/src/elements/svg.rs | 17 +- gpui/src/elements/uniform_list.rs | 2 +- gpui/src/fonts.rs | 11 - gpui/src/lib.rs | 18 +- gpui/src/presenter.rs | 18 +- gpui/src/text_layout.rs | 407 ++++++++++++++++ gpui/src/util.rs | 72 --- zed/Cargo.toml | 10 + zed/src/editor/buffer/mod.rs | 39 +- zed/src/editor/buffer/text.rs | 8 +- zed/src/editor/buffer_element.rs | 517 ++++++++------------ zed/src/editor/buffer_view.rs | 163 +++---- zed/src/editor/display_map/fold_map.rs | 18 +- zed/src/editor/display_map/mod.rs | 9 +- zed/src/editor/movement.rs | 2 +- zed/src/lib.rs | 10 +- zed/src/operation_queue.rs | 142 ++++++ zed/src/settings.rs | 30 ++ zed/src/test.rs | 99 ++++ zed/src/time.rs | 1 - zed/src/timer.rs | 42 ++ zed/src/util.rs | 77 +++ zed/src/watch.rs | 63 +++ zed/src/worktree/char_bag.rs | 44 ++ zed/src/worktree/fuzzy.rs | 494 +++++++++++++++++++ zed/src/worktree/mod.rs | 5 + zed/src/worktree/worktree.rs | 651 +++++++++++++++++++++++++ 33 files changed, 2673 insertions(+), 657 deletions(-) create mode 100644 gpui/src/assets.rs create mode 100644 gpui/src/text_layout.rs create mode 100644 zed/src/operation_queue.rs create mode 100644 zed/src/settings.rs create mode 100644 zed/src/test.rs create mode 100644 zed/src/timer.rs create mode 100644 zed/src/util.rs create mode 100644 zed/src/watch.rs create mode 100644 zed/src/worktree/char_bag.rs create mode 100644 zed/src/worktree/fuzzy.rs create mode 100644 zed/src/worktree/mod.rs create mode 100644 zed/src/worktree/worktree.rs diff --git a/Cargo.lock b/Cargo.lock index 8e6b7357d6..d798682be9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "bstr" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +dependencies = [ + "memchr", +] + [[package]] name = "byteorder" version = "1.4.2" @@ -415,6 +424,37 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.2", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + [[package]] name = "crossbeam-utils" version = "0.8.2" @@ -490,6 +530,12 @@ dependencies = [ "wio", ] +[[package]] +name = "easy-parallel" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd4afd79212583ff429b913ad6605242ed7eec277e950b1438f300748f948f4" + [[package]] name = "env_logger" version = "0.8.3" @@ -534,6 +580,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "font-kit" version = "0.10.0" @@ -689,6 +741,18 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +[[package]] +name = "globset" +version = "0.4.4" +source = "git+https://github.com/zed-industries/ripgrep?rev=1d152118f35b3e3590216709b86277062d79b8a0#1d152118f35b3e3590216709b86277062d79b8a0" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + [[package]] name = "gpui" version = "0.1.0" @@ -713,6 +777,7 @@ dependencies = [ "pathfinder_color", "pathfinder_geometry", "rand", + "smallvec", "smol", "tree-sitter", ] @@ -732,6 +797,24 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "ignore" +version = "0.4.11" +source = "git+https://github.com/zed-industries/ripgrep?rev=1d152118f35b3e3590216709b86277062d79b8a0#1d152118f35b3e3590216709b86277062d79b8a0" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils 0.7.2", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "instant" version = "0.1.9" @@ -807,6 +890,12 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "memchr" version = "2.3.4" @@ -1127,7 +1216,7 @@ dependencies = [ "base64", "blake2b_simd", "constant_time_eq", - "crossbeam-utils", + "crossbeam-utils 0.8.2", ] [[package]] @@ -1350,6 +1439,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "unindent" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7" + [[package]] name = "vec-arena" version = "1.0.0" @@ -1461,11 +1556,19 @@ version = "0.1.0" dependencies = [ "anyhow", "arrayvec", + "crossbeam-queue", "dirs", + "easy-parallel", "gpui", + "ignore", "lazy_static", "libc", "log", + "num_cpus", + "parking_lot", "rand", "simplelog", + "smallvec", + "smol", + "unindent", ] diff --git a/gpui/Cargo.toml b/gpui/Cargo.toml index dae6a45c57..43a95abcc7 100644 --- a/gpui/Cargo.toml +++ b/gpui/Cargo.toml @@ -13,6 +13,7 @@ parking_lot = "0.11.1" pathfinder_color = "0.5" pathfinder_geometry = "0.5" rand = "0.8.3" +smallvec = "1.6.1" smol = "1.2" tree-sitter = "0.17" diff --git a/gpui/src/app.rs b/gpui/src/app.rs index 2cc662fa6c..75fef19094 100644 --- a/gpui/src/app.rs +++ b/gpui/src/app.rs @@ -16,7 +16,6 @@ use std::{ fmt::{self, Debug}, hash::{Hash, Hasher}, marker::PhantomData, - mem, rc::{self, Rc}, sync::{Arc, Weak}, }; @@ -66,9 +65,8 @@ pub trait UpdateView { 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()); + pub fn test>(f: impl FnOnce(App) -> F) -> T { + let foreground = Rc::new(executor::Foreground::test()); let app = Self(Rc::new(RefCell::new( MutableAppContext::with_foreground_executor(foreground.clone()), ))); @@ -297,7 +295,7 @@ pub struct MutableAppContext { impl MutableAppContext { pub fn new() -> Result { Ok(Self::with_foreground_executor(Rc::new( - executor::Foreground::new()?, + executor::Foreground::platform(todo!())?, ))) } @@ -590,11 +588,11 @@ impl MutableAppContext { 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, - }); + // self.emit_ui_update(UiUpdate::OpenWindow { + // window_id, + // width: 1024.0, + // height: 768.0, + // }); (window_id, root_handle) } @@ -1175,8 +1173,8 @@ impl AppContext { .map(|w| { w.views .iter() - .map(|(id, view)| view.render(self)) - .collect::>() + .map(|(id, view)| (*id, view.render(self))) + .collect::>>() }) .ok_or(anyhow!("window not found")) } @@ -1287,7 +1285,7 @@ where } fn render<'a>(&self, app: &AppContext) -> Box { - View::render(self, bump, app) + View::render(self, app) } fn on_focus(&mut self, app: &mut MutableAppContext, window_id: usize, view_id: usize) { @@ -2392,7 +2390,7 @@ mod tests { type Event = (); } - App::run(|mut app| async move { + App::test(|mut app| async move { let handle = app.add_model(|_| Model::default()); handle .update(&mut app, |_, c| { @@ -2425,7 +2423,7 @@ mod tests { type Event = (); } - App::run(|mut app| async move { + App::test(|mut app| async move { let handle = app.add_model(|_| Model::default()); handle .update(&mut app, |_, c| { @@ -2454,7 +2452,7 @@ mod tests { impl super::View for View { fn render<'a>(&self, _: &AppContext) -> Box { - Empty::new().finish(bump) + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2525,8 +2523,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2581,8 +2579,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2634,8 +2632,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2679,8 +2677,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2729,8 +2727,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2790,8 +2788,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2799,7 +2797,7 @@ mod tests { } } - App::run(|mut app| async move { + App::test(|mut app| async move { let (_, handle) = app.add_window(|_| View::default()); handle .update(&mut app, |_, c| { @@ -2832,8 +2830,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2841,11 +2839,11 @@ mod tests { } } - App::run(|mut app| async move { + App::test(|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, _| { + c.spawn_stream_local(smol::stream::iter(vec![1, 2, 3]), |me, output, _| { me.events.push(output); }) }) @@ -2868,8 +2866,8 @@ mod tests { } impl View for ViewA { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2886,8 +2884,8 @@ mod tests { } impl View for ViewB { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -2990,8 +2988,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -3049,86 +3047,86 @@ mod tests { Ok(()) } - #[test] - fn test_ui_and_window_updates() { - struct View { - count: usize, - } + // #[test] + // fn test_ui_and_window_updates() { + // struct View { + // count: usize, + // } - impl Entity for View { - type Event = (); - } + // impl Entity for View { + // type Event = (); + // } - impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) - } + // impl super::View for View { + // fn render<'a>(&self, _: &AppContext) -> Box { + // Empty::new().boxed() + // } - fn ui_name() -> &'static str { - "View" - } - } + // 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 }); + // App::test(|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)); + // // 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, - }] - ); + // 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 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 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 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 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()); + // 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; + // 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()); - }); - } + // 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() { @@ -3139,8 +3137,8 @@ mod tests { } impl super::View for View { - fn render<'a>(&self, bump: &'a Bump, _: &AppContext) -> Box { - Empty::new().finish(bump) + fn render<'a>(&self, _: &AppContext) -> Box { + Empty::new().boxed() } fn ui_name() -> &'static str { @@ -3154,20 +3152,20 @@ mod tests { type Event = (); } - App::run(|mut app| async move { + App::test(|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]), |_, _, _| {}); + let _ = ctx.spawn_stream_local(smol::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]), |_, _, _| {}); + let _ = ctx.spawn_stream_local(smol::stream::iter(vec![1, 2, 3]), |_, _, _| {}); }); assert!(!app.0.borrow().task_callbacks.is_empty()); diff --git a/gpui/src/assets.rs b/gpui/src/assets.rs new file mode 100644 index 0000000000..a17ac03ac5 --- /dev/null +++ b/gpui/src/assets.rs @@ -0,0 +1,27 @@ +use anyhow::{anyhow, Result}; +use std::borrow::Cow; + +pub trait AssetSource: 'static { + fn load(&self, path: &str) -> Result>; +} + +impl AssetSource for () { + fn load(&self, path: &str) -> Result> { + Err(anyhow!( + "get called on empty asset provider with \"{}\"", + path + )) + } +} + +pub struct AssetCache { + source: Box, +} + +impl AssetCache { + pub fn new(source: impl AssetSource) -> Self { + Self { + source: Box::new(source), + } + } +} diff --git a/gpui/src/elements/label.rs b/gpui/src/elements/label.rs index 4756d0cb5c..67945d2b5c 100644 --- a/gpui/src/elements/label.rs +++ b/gpui/src/elements/label.rs @@ -2,6 +2,7 @@ use crate::{ color::ColorU, fonts::{FamilyId, Properties}, geometry::vector::{vec2f, Vector2F}, + text_layout::Line, AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, PaintContext, SizeConstraint, }; diff --git a/gpui/src/elements/mod.rs b/gpui/src/elements/mod.rs index 09b171ec96..0e98669c35 100644 --- a/gpui/src/elements/mod.rs +++ b/gpui/src/elements/mod.rs @@ -49,7 +49,10 @@ pub trait Element { fn dispatch_event(&self, event: &Event, ctx: &mut EventContext, app: &AppContext) -> bool; - fn boxed(self) -> Box { + fn boxed(self) -> Box + where + Self: 'static + Sized, + { Box::new(self) } } @@ -60,7 +63,7 @@ pub trait ParentElement<'a>: Extend> + Sized { } fn add_child(&mut self, child: Box) { - self.add_childen(Some(child)); + self.add_children(Some(child)); } fn with_children(mut self, children: impl IntoIterator>) -> Self { diff --git a/gpui/src/elements/svg.rs b/gpui/src/elements/svg.rs index 8ea3903944..aab265541d 100644 --- a/gpui/src/elements/svg.rs +++ b/gpui/src/elements/svg.rs @@ -1,12 +1,7 @@ use crate::{ - geometry::{ - rect::RectF, - vector::{vec2f, Vector2F}, - }, - AfterLayoutContext, AppContext, Element, Event, EventContext, LayoutContext, MutableAppContext, - PaintContext, SizeConstraint, + geometry::vector::Vector2F, AfterLayoutContext, AppContext, Element, Event, EventContext, + LayoutContext, MutableAppContext, PaintContext, SizeConstraint, }; -use std::rc::Rc; pub struct Svg { path: String, @@ -65,10 +60,10 @@ impl Element for Svg { 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())); - } + // if let Some(tree) = self.tree.as_ref() { + // ctx.canvas + // .draw_svg(tree, RectF::new(origin, self.size.unwrap())); + // } } fn size(&self) -> Option { diff --git a/gpui/src/elements/uniform_list.rs b/gpui/src/elements/uniform_list.rs index dc38c14109..a5527bad61 100644 --- a/gpui/src/elements/uniform_list.rs +++ b/gpui/src/elements/uniform_list.rs @@ -136,7 +136,7 @@ where 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 { + if let Some(mut 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; diff --git a/gpui/src/fonts.rs b/gpui/src/fonts.rs index 7f5ce45189..27824bcc6d 100644 --- a/gpui/src/fonts.rs +++ b/gpui/src/fonts.rs @@ -285,14 +285,3 @@ fn push_font(state: &mut FontCacheState, font: Font) -> FontId { 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 e467d65e08..d55b402971 100644 --- a/gpui/src/lib.rs +++ b/gpui/src/lib.rs @@ -1,17 +1,21 @@ mod app; +pub use app::*; +mod assets; +pub use assets::*; pub mod elements; -pub mod executor; -mod fonts; -pub mod keymap; -pub mod platform; +pub mod fonts; +pub use fonts::FontCache; mod presenter; mod scene; +pub use scene::Scene; +pub mod text_layout; +pub use text_layout::TextLayoutCache; mod util; - -pub use app::*; pub use elements::Element; +pub mod executor; +pub mod keymap; +pub mod platform; 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/presenter.rs b/gpui/src/presenter.rs index f873128b3f..fc4b31667a 100644 --- a/gpui/src/presenter.rs +++ b/gpui/src/presenter.rs @@ -1,8 +1,10 @@ use crate::{ app::{AppContext, MutableAppContext, WindowInvalidation}, elements::Element, + fonts::FontCache, platform::Event, - Scene, + text_layout::TextLayoutCache, + AssetCache, Scene, }; use pathfinder_geometry::vector::{vec2f, Vector2F}; use std::{any::Any, collections::HashMap, rc::Rc}; @@ -12,7 +14,7 @@ pub struct Presenter { rendered_views: HashMap>, parents: HashMap, font_cache: Rc, - text_layout_cache: LayoutCache, + text_layout_cache: TextLayoutCache, asset_cache: Rc, } @@ -28,7 +30,7 @@ impl Presenter { rendered_views: app.render_views(window_id).unwrap(), parents: HashMap::new(), font_cache, - text_layout_cache: LayoutCache::new(), + text_layout_cache: TextLayoutCache::new(), asset_cache, } } @@ -82,7 +84,7 @@ impl Presenter { } } - fn paint(&mut self, size: Vector2F, scale_factor: f32, app: &AppContext) -> Scene { + 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); @@ -135,7 +137,7 @@ 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 text_layout_cache: &'a TextLayoutCache, pub asset_cache: &'a AssetCache, view_stack: Vec, } @@ -157,7 +159,7 @@ impl<'a> LayoutContext<'a> { pub struct AfterLayoutContext<'a> { rendered_views: &'a mut HashMap>, pub font_cache: &'a FontCache, - pub text_layout_cache: &'a LayoutCache, + pub text_layout_cache: &'a TextLayoutCache, } impl<'a> AfterLayoutContext<'a> { @@ -173,7 +175,7 @@ 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, + pub text_layout_cache: &'a TextLayoutCache, } impl<'a> PaintContext<'a> { @@ -189,7 +191,7 @@ 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, + pub text_layout_cache: &'a TextLayoutCache, view_stack: Vec, } diff --git a/gpui/src/text_layout.rs b/gpui/src/text_layout.rs new file mode 100644 index 0000000000..9eef840e0b --- /dev/null +++ b/gpui/src/text_layout.rs @@ -0,0 +1,407 @@ +use crate::{ + color::ColorU, + fonts::{FontCache, FontId, GlyphId}, + geometry::rect::RectF, + scene::Scene, +}; +use core_foundation::{ + attributed_string::CFMutableAttributedString, + base::{CFRange, TCFType}, + string::CFString, +}; +use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName}; +use ordered_float::OrderedFloat; +use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; +use pathfinder_geometry::vector::{vec2f, Vector2F}; +use smallvec::SmallVec; +use std::{ + borrow::Borrow, + char, + collections::HashMap, + convert::TryFrom, + hash::{Hash, Hasher}, + ops::Range, + sync::Arc, +}; + +pub struct TextLayoutCache { + prev_frame: Mutex>>, + curr_frame: RwLock>>, +} + +impl TextLayoutCache { + pub fn new() -> Self { + Self { + prev_frame: Mutex::new(HashMap::new()), + curr_frame: RwLock::new(HashMap::new()), + } + } + + pub fn finish_frame(&self) { + let mut prev_frame = self.prev_frame.lock(); + let mut curr_frame = self.curr_frame.write(); + std::mem::swap(&mut *prev_frame, &mut *curr_frame); + curr_frame.clear(); + } + + pub fn layout_str<'a>( + &'a self, + text: &'a str, + font_size: f32, + runs: &'a [(Range, FontId)], + font_cache: &'a FontCache, + ) -> Arc { + let key = &CacheKeyRef { + text, + font_size: OrderedFloat(font_size), + runs, + } as &dyn CacheKey; + let curr_frame = self.curr_frame.upgradable_read(); + if let Some(line) = curr_frame.get(key) { + return line.clone(); + } + + let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame); + if let Some((key, line)) = self.prev_frame.lock().remove_entry(key) { + curr_frame.insert(key, line.clone()); + line.clone() + } else { + let line = Arc::new(layout_str(text, font_size, runs, font_cache)); + let key = CacheKeyValue { + text: text.into(), + font_size: OrderedFloat(font_size), + runs: SmallVec::from(runs), + }; + curr_frame.insert(key, line.clone()); + line + } + } +} + +trait CacheKey { + fn key<'a>(&'a self) -> CacheKeyRef<'a>; +} + +impl<'a> PartialEq for (dyn CacheKey + 'a) { + fn eq(&self, other: &dyn CacheKey) -> bool { + self.key() == other.key() + } +} + +impl<'a> Eq for (dyn CacheKey + 'a) {} + +impl<'a> Hash for (dyn CacheKey + 'a) { + fn hash(&self, state: &mut H) { + self.key().hash(state) + } +} + +#[derive(Eq, PartialEq)] +struct CacheKeyValue { + text: String, + font_size: OrderedFloat, + runs: SmallVec<[(Range, FontId); 1]>, +} + +impl CacheKey for CacheKeyValue { + fn key<'a>(&'a self) -> CacheKeyRef<'a> { + CacheKeyRef { + text: &self.text.as_str(), + font_size: self.font_size, + runs: self.runs.as_slice(), + } + } +} + +impl Hash for CacheKeyValue { + fn hash(&self, state: &mut H) { + self.key().hash(state); + } +} + +impl<'a> Borrow for CacheKeyValue { + fn borrow(&self) -> &(dyn CacheKey + 'a) { + self as &dyn CacheKey + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +struct CacheKeyRef<'a> { + text: &'a str, + font_size: OrderedFloat, + runs: &'a [(Range, FontId)], +} + +impl<'a> CacheKey for CacheKeyRef<'a> { + fn key<'b>(&'b self) -> CacheKeyRef<'b> { + *self + } +} + +#[derive(Default)] +pub struct Line { + pub width: f32, + pub runs: Vec, + pub len: usize, + font_size: f32, +} + +#[derive(Debug)] +pub struct Run { + pub font_id: FontId, + pub glyphs: Vec, +} + +#[derive(Debug)] +pub struct Glyph { + pub id: GlyphId, + pub position: Vector2F, + pub index: usize, +} + +impl Line { + pub fn x_for_index(&self, index: usize) -> f32 { + for run in &self.runs { + for glyph in &run.glyphs { + if glyph.index == index { + return glyph.position.x(); + } + } + } + self.width + } + + pub fn index_for_x(&self, x: f32) -> Option { + if x >= self.width { + None + } else { + for run in self.runs.iter().rev() { + for glyph in run.glyphs.iter().rev() { + if glyph.position.x() <= x { + return Some(glyph.index); + } + } + } + Some(0) + } + } + + pub fn paint( + &self, + _origin: Vector2F, + _viewport_rect: RectF, + _colors: &[(Range, ColorU)], + _scene: Scene, + _font_cache: &FontCache, + ) { + // canvas.set_font_size(self.font_size); + // let mut colors = colors.iter().peekable(); + + // for run in &self.runs { + // let bounding_box = font_cache.bounding_box(run.font_id, self.font_size); + // let ascent = font_cache.scale_metric( + // font_cache.metric(run.font_id, |m| m.ascent), + // run.font_id, + // self.font_size, + // ); + // let descent = font_cache.scale_metric( + // font_cache.metric(run.font_id, |m| m.descent), + // run.font_id, + // self.font_size, + // ); + + // let max_glyph_width = bounding_box.x(); + // let font = font_cache.font(run.font_id); + // let font_name = font_cache.font_name(run.font_id); + // let is_emoji = font_cache.is_emoji(run.font_id); + // for glyph in &run.glyphs { + // let glyph_origin = origin + glyph.position - vec2f(0.0, descent); + + // if glyph_origin.x() + max_glyph_width < viewport_rect.origin().x() { + // continue; + // } + + // if glyph_origin.x() > viewport_rect.upper_right().x() { + // break; + // } + + // while let Some((range, color)) = colors.peek() { + // if glyph.index >= range.end { + // colors.next(); + // } else { + // if glyph.index == range.start { + // canvas.set_fill_style(FillStyle::Color(*color)); + // } + // break; + // } + // } + + // if is_emoji { + // match font_cache.render_emoji(glyph.id, self.font_size) { + // Ok(image) => { + // canvas.draw_image(image, RectF::new(glyph_origin, bounding_box)); + // } + // Err(error) => log::error!("rasterizing emoji: {}", error), + // } + // } else { + // canvas.fill_glyph( + // &font, + // &font_name, + // glyph.id, + // glyph_origin + vec2f(0.0, ascent), + // ); + // } + // } + // } + } +} + +pub fn layout_str( + text: &str, + font_size: f32, + runs: &[(Range, FontId)], + font_cache: &FontCache, +) -> Line { + let mut string = CFMutableAttributedString::new(); + string.replace_str(&CFString::new(text), CFRange::init(0, 0)); + + let mut utf16_lens = text.chars().map(|c| c.len_utf16()); + let mut prev_char_ix = 0; + let mut prev_utf16_ix = 0; + + for (range, font_id) in runs { + let utf16_start = prev_utf16_ix + + utf16_lens + .by_ref() + .take(range.start - prev_char_ix) + .sum::(); + let utf16_end = utf16_start + + utf16_lens + .by_ref() + .take(range.end - range.start) + .sum::(); + prev_char_ix = range.end; + prev_utf16_ix = utf16_end; + + let cf_range = CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize); + let native_font = font_cache.native_font(*font_id, font_size); + unsafe { + string.set_attribute(cf_range, kCTFontAttributeName, &native_font); + } + } + + let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef()); + + let width = line.get_typographic_bounds().width as f32; + + let mut utf16_chars = text.encode_utf16(); + let mut char_ix = 0; + let mut prev_utf16_ix = 0; + + let mut runs = Vec::new(); + for run in line.glyph_runs().into_iter() { + let font_id = font_cache.font_id_for_native_font(unsafe { + run.attributes() + .unwrap() + .get(kCTFontAttributeName) + .downcast::() + .unwrap() + }); + + let mut glyphs = Vec::new(); + for ((glyph_id, position), utf16_ix) in run + .glyphs() + .iter() + .zip(run.positions().iter()) + .zip(run.string_indices().iter()) + { + let utf16_ix = usize::try_from(*utf16_ix).unwrap(); + char_ix += + char::decode_utf16(utf16_chars.by_ref().take(utf16_ix - prev_utf16_ix)).count(); + prev_utf16_ix = utf16_ix; + + glyphs.push(Glyph { + id: *glyph_id as GlyphId, + position: vec2f(position.x as f32, position.y as f32), + index: char_ix, + }); + } + + runs.push(Run { font_id, glyphs }) + } + + Line { + width, + runs, + font_size, + len: char_ix + 1, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + use font_kit::properties::{ + Properties as FontProperties, Style as FontStyle, Weight as FontWeight, + }; + + #[test] + fn test_layout_str() -> Result<()> { + let mut font_cache = FontCache::new(); + let menlo = font_cache.load_family(&["Menlo"])?; + let menlo_regular = font_cache.select_font(menlo, &FontProperties::new())?; + let menlo_italic = + font_cache.select_font(menlo, &FontProperties::new().style(FontStyle::Italic))?; + let menlo_bold = + font_cache.select_font(menlo, &FontProperties::new().weight(FontWeight::BOLD))?; + + let line = layout_str( + "hello world πŸ˜ƒ", + 16.0, + &[ + (0..2, menlo_bold), + (2..6, menlo_italic), + (6..13, menlo_regular), + ], + &mut font_cache, + ); + + assert!(font_cache.is_emoji(line.runs.last().unwrap().font_id)); + + Ok(()) + } + + #[test] + fn test_char_indices() -> Result<()> { + let mut font_cache = FontCache::new(); + let zapfino = font_cache.load_family(&["Zapfino"])?; + let zapfino_regular = font_cache.select_font(zapfino, &FontProperties::new())?; + let menlo = font_cache.load_family(&["Menlo"])?; + let menlo_regular = font_cache.select_font(menlo, &FontProperties::new())?; + + let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈"; + let line = layout_str( + text, + 16.0, + &[ + (0..9, zapfino_regular), + (11..22, menlo_regular), + (22..text.encode_utf16().count(), zapfino_regular), + ], + &mut font_cache, + ); + assert_eq!( + line.runs + .iter() + .flat_map(|r| r.glyphs.iter()) + .map(|g| g.index) + .collect::>(), + vec![ + 0, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 31, 32 + ] + ); + Ok(()) + } +} diff --git a/gpui/src/util.rs b/gpui/src/util.rs index 5a58e7b24d..473c8d00f1 100644 --- a/gpui/src/util.rs +++ b/gpui/src/util.rs @@ -1,77 +1,5 @@ -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 f9faa90a0f..c473c263af 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -15,10 +15,20 @@ path = "src/main.rs" [dependencies] anyhow = "1.0.38" arrayvec = "0.5.2" +crossbeam-queue = "0.3.1" dirs = "3.0" +easy-parallel = "3.1.0" gpui = {path = "../gpui"} +ignore = {git = "https://github.com/zed-industries/ripgrep", rev = "1d152118f35b3e3590216709b86277062d79b8a0"} lazy_static = "1.4.0" libc = "0.2" log = "0.4" +num_cpus = "1.13.0" +parking_lot = "0.11.1" rand = "0.8.3" simplelog = "0.9" +smallvec = "1.6.1" +smol = "1.2.5" + +[dev-dependencies] +unindent = "0.1.7" diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 16ba5f5843..0e684e26f7 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -7,15 +7,14 @@ 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, + time::{self, ReplicaId}, util::RandomCharIter, worktree::FileHandle, - ReplicaId, }; use anyhow::{anyhow, Result}; +use gpui::{AppContext, Entity, ModelContext}; use lazy_static::lazy_static; use rand::prelude::*; use std::{ @@ -423,11 +422,11 @@ impl Buffer { } 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 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_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() { @@ -452,11 +451,11 @@ impl Buffer { 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); + 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_len = rng.gen_range(0..10); let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); let operations = self @@ -1375,7 +1374,7 @@ pub enum Event { Edited(Vec), } -impl app::Entity for Buffer { +impl Entity for Buffer { type Event = Event; } @@ -1905,7 +1904,7 @@ mod tests { #[test] fn test_edit_events() { - use crate::app::App; + use gpui::App; use std::{cell::RefCell, rc::Rc}; let mut app = App::new().unwrap(); @@ -1956,7 +1955,7 @@ mod tests { println!("{:?}", seed); let mut rng = &mut StdRng::seed_from_u64(seed); - let reference_string_len = rng.gen_range(0, 3); + let reference_string_len = rng.gen_range(0..3); let mut reference_string = RandomCharIter::new(&mut rng) .take(reference_string_len) .collect::(); @@ -1991,8 +1990,8 @@ mod tests { } for _ in 0..5 { - let end = rng.gen_range(0, buffer.len() + 1); - let start = rng.gen_range(0, end + 1); + 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(); @@ -2236,7 +2235,7 @@ mod tests { 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 index = rng.gen_range(1..ids.len()); let left = ids[index - 1].clone(); let right = ids[index].clone(); @@ -2429,7 +2428,7 @@ mod tests { #[test] fn test_random_concurrent_edits() { - use crate::tests::Network; + use crate::test::Network; const PEERS: usize = 3; @@ -2437,7 +2436,7 @@ mod tests { println!("{:?}", seed); let mut rng = &mut StdRng::seed_from_u64(seed); - let base_text_len = rng.gen_range(0, 10); + let base_text_len = rng.gen_range(0..10); let base_text = RandomCharIter::new(&mut rng) .take(base_text_len) .collect::(); @@ -2453,7 +2452,7 @@ mod tests { let mut mutation_count = 10; loop { - let replica_index = rng.gen_range(0, PEERS); + 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() { @@ -2510,9 +2509,9 @@ mod tests { } else { let mut ranges = Vec::new(); for _ in 0..5 { - let start = rng.gen_range(0, self.len() + 1); + 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 = rng.gen_range(0..self.len() + 1); let end_point = self.point_for_offset(end).unwrap(); ranges.push(start_point..end_point); } diff --git a/zed/src/editor/buffer/text.rs b/zed/src/editor/buffer/text.rs index 342fa38904..1a074d4a75 100644 --- a/zed/src/editor/buffer/text.rs +++ b/zed/src/editor/buffer/text.rs @@ -366,7 +366,7 @@ mod tests { println!("buffer::text seed: {}", seed); let rng = &mut StdRng::seed_from_u64(seed); - let len = rng.gen_range(0, 50); + let len = rng.gen_range(0..50); let mut string = String::new(); for _ in 0..len { if rng.gen_ratio(1, 5) { @@ -378,8 +378,8 @@ mod tests { 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 start = rng.gen_range(0..text.len() + 1); + let end = rng.gen_range(start..text.len() + 2); let string_slice = string .chars() @@ -414,7 +414,7 @@ mod tests { assert!(rightmost_points.contains(&text_slice.rightmost_point())); for _ in 0..10 { - let offset = rng.gen_range(0, string_slice.chars().count() + 1); + 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); diff --git a/zed/src/editor/buffer_element.rs b/zed/src/editor/buffer_element.rs index 92364442bc..739d63fc04 100644 --- a/zed/src/editor/buffer_element.rs +++ b/zed/src/editor/buffer_element.rs @@ -1,24 +1,15 @@ 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 gpui::{ + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, }, + text_layout::{self, TextLayoutCache}, + AfterLayoutContext, AppContext, Element, Event, EventContext, FontCache, LayoutContext, + MutableAppContext, PaintContext, Scene, SizeConstraint, ViewHandle, }; -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}, + cmp::{self}, sync::Arc, }; @@ -176,166 +167,165 @@ impl BufferElement { } 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; + // if let Some(layout) = self.layout.as_ref() { + // let view = self.view.as_ref(app); + // let scene = &mut ctx.scene; + // 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())); + // scene.save(); + // scene.translate(rect.origin()); + // scene.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 rect = RectF::new(Vector2F::zero(), rect.size()); + // let mut rect_path = Path2D::new(); + // rect_path.rect(rect); + // scene.clip_path(rect_path, FillRule::EvenOdd); + // scene.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, - ); - } + // 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())], + // scene, + // font_cache, + // ); + // } - canvas.restore(); - } + // scene.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; + // if let Some(layout) = self.layout.as_ref() { + // let scene = &mut ctx.scene; + // 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); + // scene.save(); + // scene.translate(rect.origin()); + // scene.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); + // scene.clip_path(rect_path, FillRule::EvenOdd); + // scene.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; + // 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(); + // // Draw selections + // scene.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) - }; + // 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(), - }; + // 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); - } + // selection.paint(scene); + // } - 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(); + // 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, + // }); + // } + // } + // } + // scene.restore(); - // Draw glyphs + // // Draw glyphs - canvas.set_fill_style(FillStyle::Color(ColorU::black())); + // scene.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, - ); + // 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, - ); - } + // line.paint( + // line_origin, + // rect, + // &[(0..line.len, ColorU::black())], + // scene, + // font_cache, + // ); + // } - for cursor in cursors { - cursor.paint(canvas); - } + // for cursor in cursors { + // cursor.paint(scene); + // } - canvas.restore() - } + // scene.restore() + // } } } -impl<'a> Element<'a> for BufferElement { +impl Element for BufferElement { fn layout( &mut self, constraint: SizeConstraint, - _: &'a Bump, ctx: &mut LayoutContext, app: &AppContext, ) -> Vector2F { @@ -480,7 +470,7 @@ impl<'a> Element<'a> for BufferElement { } } - fn size(&self) -> Option { + fn size(&self) -> Option { self.layout.as_ref().map(|layout| layout.size) } } @@ -501,7 +491,7 @@ impl LayoutState { &self, view: &BufferView, font_cache: &FontCache, - layout_cache: &LayoutCache, + layout_cache: &TextLayoutCache, app: &AppContext, ) -> f32 { let row = view.rightmost_point(app).row(); @@ -516,7 +506,7 @@ impl LayoutState { &self, view: &BufferView, font_cache: &FontCache, - layout_cache: &LayoutCache, + layout_cache: &TextLayoutCache, app: &AppContext, ) -> Vector2F { vec2f( @@ -569,12 +559,12 @@ struct Cursor { } 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), - )); + fn paint(&self, scene: &mut Scene) { + // scene.set_fill_style(FillStyle::Color(ColorU::black())); + // scene.fill_rect(RectF::new( + // vec2f(self.x, self.y), + // vec2f(2.0, self.line_height), + // )); } } @@ -592,167 +582,84 @@ struct SelectionLine { } impl Selection { - fn paint(&self, canvas: &mut CanvasRenderingContext2D) { + fn paint(&self, scene: &mut Scene) { 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); + self.paint_lines(self.start_y, &self.lines[0..1], scene); + self.paint_lines(self.start_y + self.line_height, &self.lines[1..], scene); } else { - self.paint_lines(self.start_y, &self.lines, canvas); + self.paint_lines(self.start_y, &self.lines, scene); } } - fn paint_lines( - &self, - start_y: f32, - lines: &[SelectionLine], - canvas: &mut CanvasRenderingContext2D, - ) { - use Direction::*; + fn paint_lines(&self, start_y: f32, lines: &[SelectionLine], scene: &mut Scene) { + // use Direction::*; - if lines.is_empty() { - return; - } + // if lines.is_empty() { + // return; + // } - let mut path = Path2D::new(); - let corner_radius = 0.08 * self.line_height; + // 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 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 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); + // 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()); + // 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); + // 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); - } - } + // 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); - } + // 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(); + // 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"), + // scene.set_fill_style(FillStyle::Color( + // ColorF::new(0.639, 0.839, 1.0, 1.0).to_u8(), + // )); + // scene.fill_path(path, FillRule::Winding); } } diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 428e801e3c..57c33ad56f 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -2,20 +2,16 @@ 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 crate::{settings::Settings, watch}; use anyhow::Result; use easy_parallel::Parallel; -use font_kit::properties::Properties as FontProperties; +use gpui::{ + fonts::{FontCache, Properties as FontProperties}, + keymap::Binding, + text_layout, App, AppContext, Element, Entity, ModelHandle, View, ViewContext, WeakViewHandle, +}; +use gpui::{geometry::vector::Vector2F, TextLayoutCache}; use parking_lot::Mutex; -use pathfinder_geometry::vector::Vector2F; use smallvec::SmallVec; use smol::Timer; use std::{ @@ -26,7 +22,6 @@ use std::{ sync::Arc, time::Duration, }; -use text_layout::LayoutCache; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -88,17 +83,17 @@ pub enum SelectAction { End, } -impl workspace::Item for Buffer { - type View = BufferView; +// 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) - } -} +// fn build_view( +// buffer: ModelHandle, +// settings: watch::Receiver, +// ctx: &mut ViewContext, +// ) -> Self::View { +// BufferView::for_buffer(buffer, settings, ctx) +// } +// } pub struct BufferView { handle: WeakViewHandle, @@ -348,7 +343,7 @@ impl BufferView { 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); + self.merge_selections(ctx.app()); ctx.notify(); } else { log::error!("end_selection dispatched with no pending selection"); @@ -377,7 +372,7 @@ impl BufferView { } selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer).unwrap()); self.selections = selections; - self.merge_selections(ctx); + self.merge_selections(ctx.app()); ctx.notify(); Ok(()) } @@ -609,13 +604,13 @@ impl BufferView { } pub fn changed_selections(&mut self, ctx: &mut ViewContext) { - self.merge_selections(ctx); + self.merge_selections(ctx.app()); self.pause_cursor_blinking(ctx); *self.autoscroll_requested.lock() = true; ctx.notify(); } - fn merge_selections(&mut self, ctx: &A) { + fn merge_selections(&mut self, ctx: &AppContext) { let buffer = self.buffer.as_ref(ctx); let mut i = 1; while i < self.selections.len() { @@ -900,7 +895,7 @@ impl BufferView { pub fn max_line_number_width( &self, font_cache: &FontCache, - layout_cache: &LayoutCache, + layout_cache: &TextLayoutCache, app: &AppContext, ) -> Result { let settings = smol::block_on(self.settings.read()); @@ -926,7 +921,7 @@ impl BufferView { &self, viewport_height: f32, font_cache: &FontCache, - layout_cache: &LayoutCache, + layout_cache: &TextLayoutCache, app: &AppContext, ) -> Result>> { let display_map = self.display_map.as_ref(app); @@ -979,7 +974,7 @@ impl BufferView { &self, mut rows: Range, font_cache: &FontCache, - layout_cache: &LayoutCache, + layout_cache: &TextLayoutCache, app: &AppContext, ) -> Result>> { let display_map = self.display_map.as_ref(app); @@ -1000,13 +995,14 @@ impl BufferView { 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); + Parallel::new() + .each( + layouts.chunks_mut(chunk_size as usize).enumerate(), + |(ix, chunk)| { + 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; @@ -1032,10 +1028,9 @@ impl BufferView { line.push(char); } } - }); - } - }) - .unwrap(); + }, + ) + .run(); Ok(layouts) } @@ -1044,7 +1039,7 @@ impl BufferView { &self, row: u32, font_cache: &FontCache, - layout_cache: &LayoutCache, + layout_cache: &TextLayoutCache, app: &AppContext, ) -> Result> { let settings = smol::block_on(self.settings.read()); @@ -1140,13 +1135,13 @@ pub enum Event { Blurred, } -impl app::Entity for BufferView { +impl 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) +impl View for BufferView { + fn render<'a>(&self, app: &AppContext) -> Box { + BufferElement::new(self.handle.upgrade(app).unwrap()).boxed() } fn ui_name() -> &'static str { @@ -1166,38 +1161,38 @@ impl app::View for BufferView { } } -impl workspace::ItemView for BufferView { - fn is_activate_event(event: &Self::Event) -> bool { - match event { - Event::Activate => true, - _ => false, - } - } +// 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 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 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) - } -} +// 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 { @@ -1256,16 +1251,15 @@ impl Selection { #[cfg(test)] mod tests { use super::*; - use crate::buffer::Point; - use crate::test_utils::*; + use crate::{editor::Point, settings, test::sample_text}; use anyhow::Error; use unindent::Unindent; #[test] fn test_selection_with_mouse() { - App::run(|mut app| async move { + App::test(|mut app| async move { let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n")); - let settings = settings_rx(None); + let settings = settings::channel(&FontCache::new()).1; let (_, buffer_view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); @@ -1362,16 +1356,15 @@ mod tests { #[test] fn test_layout_line_numbers() -> Result<()> { - use crate::fonts::FontCache; - use crate::text_layout::LayoutCache; + use gpui::{fonts::FontCache, text_layout::TextLayoutCache}; let font_cache = FontCache::new(); - let layout_cache = LayoutCache::new(); + let layout_cache = TextLayoutCache::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 settings = settings::channel(&font_cache).unwrap().1; let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); view.read(&app, |view, app| { @@ -1385,8 +1378,6 @@ mod tests { #[test] fn test_fold() -> Result<()> { - init_logger(); - let mut app = App::new().unwrap(); let buffer = app.add_model(|_| { Buffer::new( @@ -1402,8 +1393,6 @@ mod tests { fn b() { 2 } - - fn c() { 3 } } @@ -1411,7 +1400,7 @@ mod tests { .unindent(), ) }); - let settings = settings_rx(None); + let settings = settings::channel(&FontCache::new()).unwrap().1; let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); view.update(&mut app, |view, ctx| { @@ -1481,7 +1470,7 @@ mod tests { 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 settings = settings::channel(&FontCache::new()).1; let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx)); buffer.update(&mut app, |buffer, ctx| { diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index f1d3ee9ad9..91ac40e692 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -2,11 +2,11 @@ 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 gpui::{AppContext, ModelHandle}; use std::{ cmp::{self, Ordering}, iter::Take, @@ -455,8 +455,8 @@ impl<'a> Dimension<'a, TransformSummary> for usize { #[cfg(test)] mod tests { use super::*; - use crate::app::App; - use crate::test_utils::sample_text; + use crate::test::sample_text; + use gpui::App; #[test] fn test_basic_folds() -> Result<()> { @@ -565,7 +565,7 @@ mod tests { #[test] fn test_random_folds() -> Result<()> { - use crate::buffer::ToPoint; + use crate::editor::ToPoint; use crate::util::RandomCharIter; use rand::prelude::*; @@ -575,7 +575,7 @@ mod tests { let mut app = App::new()?; let buffer = app.add_model(|_| { - let len = rng.gen_range(0, 10); + let len = rng.gen_range(0..10); let text = RandomCharIter::new(&mut rng).take(len).collect::(); Buffer::new(0, text) }); @@ -584,11 +584,11 @@ mod tests { app.read(|app| { let buffer = buffer.as_ref(app); - let fold_count = rng.gen_range(0, 10); + 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); + let end = rng.gen_range(0..buffer.len() + 1); + let start = rng.gen_range(0..end + 1); fold_ranges.push(start..end); } @@ -612,7 +612,7 @@ mod tests { let edits = buffer.update(&mut app, |buffer, ctx| { let start_version = buffer.version.clone(); - let edit_count = rng.gen_range(1, 10); + 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::>()) })?; diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 1139d34aca..acc48dafc6 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -1,11 +1,10 @@ mod fold_map; -use super::ToPoint; -use super::{buffer, Anchor, AnchorRangeExt, Buffer, Edit, Point, TextSummary, ToOffset}; -use crate::app::{AppContext, Entity, ModelContext, ModelHandle}; +use super::{buffer, Anchor, AnchorRangeExt, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint}; use anyhow::Result; pub use fold_map::BufferRows; use fold_map::FoldMap; +use gpui::{AppContext, Entity, ModelContext, ModelHandle}; use std::ops::Range; #[derive(Copy, Clone)] @@ -291,9 +290,9 @@ pub fn collapse_tabs( #[cfg(test)] mod tests { use super::*; - use crate::app::App; - use crate::test_utils::*; + use crate::test::*; use anyhow::Error; + use gpui::App; #[test] fn test_chars_at() -> Result<()> { diff --git a/zed/src/editor/movement.rs b/zed/src/editor/movement.rs index 3e38549a05..08e3600255 100644 --- a/zed/src/editor/movement.rs +++ b/zed/src/editor/movement.rs @@ -1,6 +1,6 @@ use super::{DisplayMap, DisplayPoint}; -use crate::app::AppContext; use anyhow::Result; +use gpui::AppContext; use std::cmp; pub fn left(map: &DisplayMap, mut point: DisplayPoint, app: &AppContext) -> Result { diff --git a/zed/src/lib.rs b/zed/src/lib.rs index 0fe2fd3aa3..2c9a4ca283 100644 --- a/zed/src/lib.rs +++ b/zed/src/lib.rs @@ -1,3 +1,11 @@ -// mod editor; +mod editor; +mod operation_queue; +mod settings; mod sum_tree; +#[cfg(test)] +mod test; mod time; +mod timer; +mod util; +mod watch; +mod worktree; diff --git a/zed/src/operation_queue.rs b/zed/src/operation_queue.rs new file mode 100644 index 0000000000..e3a0532332 --- /dev/null +++ b/zed/src/operation_queue.rs @@ -0,0 +1,142 @@ +use crate::{ + sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree}, + time, +}; +use std::{ + fmt::Debug, + ops::{Add, AddAssign}, +}; + +pub trait Operation: Clone + Debug + Eq { + fn timestamp(&self) -> time::Lamport; +} + +#[derive(Clone, Debug)] +pub struct OperationQueue(SumTree); + +#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +pub struct OperationKey(time::Lamport); + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct OperationSummary { + key: OperationKey, + len: usize, +} + +impl OperationQueue { + pub fn new() -> Self { + OperationQueue(SumTree::new()) + } + + #[cfg(test)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn len(&self) -> usize { + self.0.summary().len + } + + pub fn insert(&mut self, mut ops: Vec) { + ops.sort_by_key(|op| op.timestamp()); + ops.dedup_by_key(|op| op.timestamp()); + let mut edits = ops + .into_iter() + .map(|op| Edit::Insert(op)) + .collect::>(); + self.0.edit(&mut edits); + } + + pub fn drain(&mut self) -> Self { + let clone = self.clone(); + self.0 = SumTree::new(); + clone + } + + pub fn cursor(&self) -> Cursor { + self.0.cursor() + } +} + +impl Item for T { + type Summary = OperationSummary; + + fn summary(&self) -> Self::Summary { + OperationSummary { + key: OperationKey(self.timestamp()), + len: 1, + } + } +} + +impl KeyedItem for T { + type Key = OperationKey; + + fn key(&self) -> Self::Key { + OperationKey(self.timestamp()) + } +} + +impl<'a> AddAssign<&'a Self> for OperationSummary { + fn add_assign(&mut self, other: &Self) { + assert!(self.key < other.key); + self.key = other.key; + self.len += other.len; + } +} + +impl<'a> Add<&'a Self> for OperationSummary { + type Output = Self; + + fn add(self, other: &Self) -> Self { + assert!(self.key < other.key); + OperationSummary { + key: other.key, + len: self.len + other.len, + } + } +} + +impl<'a> Dimension<'a, OperationSummary> for OperationKey { + fn add_summary(&mut self, summary: &OperationSummary) { + assert!(*self <= summary.key); + *self = summary.key; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len() { + let mut clock = time::Lamport::new(0); + + let mut queue = OperationQueue::new(); + assert_eq!(queue.len(), 0); + + queue.insert(vec![ + TestOperation(clock.tick()), + TestOperation(clock.tick()), + ]); + assert_eq!(queue.len(), 2); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 3); + + drop(queue.drain()); + assert_eq!(queue.len(), 0); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 1); + } + + #[derive(Clone, Debug, Eq, PartialEq)] + struct TestOperation(time::Lamport); + + impl Operation for TestOperation { + fn timestamp(&self) -> time::Lamport { + self.0 + } + } +} diff --git a/zed/src/settings.rs b/zed/src/settings.rs new file mode 100644 index 0000000000..f3ad978b4b --- /dev/null +++ b/zed/src/settings.rs @@ -0,0 +1,30 @@ +use crate::watch; +use anyhow::Result; +use gpui::fonts::{FamilyId, FontCache}; + +#[derive(Clone)] +pub struct Settings { + pub buffer_font_family: FamilyId, + pub buffer_font_size: f32, + pub tab_size: usize, + pub ui_font_family: FamilyId, + pub ui_font_size: f32, +} + +impl Settings { + pub fn new(font_cache: &FontCache) -> Result { + Ok(Self { + buffer_font_family: font_cache.load_family(&["Fira Code", "Monaco"])?, + buffer_font_size: 16.0, + tab_size: 4, + ui_font_family: font_cache.load_family(&["SF Pro Display"])?, + ui_font_size: 12.0, + }) + } +} + +pub fn channel( + font_cache: &FontCache, +) -> Result<(watch::Sender, watch::Receiver)> { + Ok(watch::channel(Settings::new(font_cache)?)) +} diff --git a/zed/src/test.rs b/zed/src/test.rs new file mode 100644 index 0000000000..2d15f34d23 --- /dev/null +++ b/zed/src/test.rs @@ -0,0 +1,99 @@ +use rand::Rng; +use std::collections::BTreeMap; + +use crate::time::ReplicaId; + +#[derive(Clone)] +struct Envelope { + message: T, + sender: ReplicaId, +} + +pub(crate) struct Network { + inboxes: BTreeMap>>, + all_messages: Vec, +} + +impl Network { + pub fn new() -> Self { + Network { + inboxes: BTreeMap::new(), + all_messages: Vec::new(), + } + } + + pub fn add_peer(&mut self, id: ReplicaId) { + self.inboxes.insert(id, Vec::new()); + } + + pub fn is_idle(&self) -> bool { + self.inboxes.values().all(|i| i.is_empty()) + } + + pub fn broadcast(&mut self, sender: ReplicaId, messages: Vec, rng: &mut R) + where + R: Rng, + { + for (replica, inbox) in self.inboxes.iter_mut() { + if *replica != sender { + for message in &messages { + let min_index = inbox + .iter() + .enumerate() + .rev() + .find_map(|(index, envelope)| { + if sender == envelope.sender { + Some(index + 1) + } else { + None + } + }) + .unwrap_or(0); + + // Insert one or more duplicates of this message *after* the previous + // message delivered by this replica. + for _ in 0..rng.gen_range(1, 4) { + let insertion_index = rng.gen_range(min_index, inbox.len() + 1); + inbox.insert( + insertion_index, + Envelope { + message: message.clone(), + sender, + }, + ); + } + } + } + } + self.all_messages.extend(messages); + } + + pub fn has_unreceived(&self, receiver: ReplicaId) -> bool { + !self.inboxes[&receiver].is_empty() + } + + pub fn receive(&mut self, receiver: ReplicaId, rng: &mut R) -> Vec + where + R: Rng, + { + let inbox = self.inboxes.get_mut(&receiver).unwrap(); + let count = rng.gen_range(0, inbox.len() + 1); + inbox + .drain(0..count) + .map(|envelope| envelope.message) + .collect() + } +} + +pub fn sample_text(rows: usize, cols: usize) -> String { + let mut text = String::new(); + for row in 0..rows { + let c: char = ('a' as u32 + row as u32) as u8 as char; + let mut line = c.to_string().repeat(cols); + if row < rows - 1 { + line.push('\n'); + } + text += &line; + } + text +} diff --git a/zed/src/time.rs b/zed/src/time.rs index 9467535441..3c088fc4d1 100644 --- a/zed/src/time.rs +++ b/zed/src/time.rs @@ -5,7 +5,6 @@ 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, diff --git a/zed/src/timer.rs b/zed/src/timer.rs new file mode 100644 index 0000000000..de3f9e17b0 --- /dev/null +++ b/zed/src/timer.rs @@ -0,0 +1,42 @@ +use smol::prelude::*; +use std::{ + pin::Pin, + task::Poll, + time::{Duration, Instant}, +}; + +pub struct Repeat { + timer: smol::Timer, + period: Duration, +} + +impl Stream for Repeat { + type Item = Instant; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + match self.as_mut().timer().poll(cx) { + Poll::Ready(instant) => { + let period = self.as_ref().period; + self.as_mut().timer().set_after(period); + Poll::Ready(Some(instant)) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl Repeat { + fn timer(self: std::pin::Pin<&mut Self>) -> Pin<&mut smol::Timer> { + unsafe { self.map_unchecked_mut(|s| &mut s.timer) } + } +} + +pub fn repeat(period: Duration) -> Repeat { + Repeat { + timer: smol::Timer::after(period), + period, + } +} diff --git a/zed/src/util.rs b/zed/src/util.rs new file mode 100644 index 0000000000..80ff736d24 --- /dev/null +++ b/zed/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/src/watch.rs b/zed/src/watch.rs new file mode 100644 index 0000000000..7cc7f59fbe --- /dev/null +++ b/zed/src/watch.rs @@ -0,0 +1,63 @@ +// TODO: This implementation is actually broken in that it will only + +use gpui::{Entity, ModelContext, View, ViewContext}; +use smol::{channel, lock::RwLock}; +use std::ops::Deref; +use std::sync::Arc; + +pub struct Sender { + value: Arc>, + updated: channel::Sender<()>, +} + +#[derive(Clone)] +pub struct Receiver { + value: Arc>, + updated: channel::Receiver<()>, +} + +impl Sender { + pub async fn update(&mut self, f: impl FnOnce(&mut T) -> R) -> R { + let result = f(&mut *self.value.write().await); + self.updated.send(()).await.unwrap(); + result + } +} + +impl Receiver { + pub async fn updated(&self) { + let _ = self.updated.recv().await; + } + + pub async fn read<'a>(&'a self) -> impl 'a + Deref { + self.value.read().await + } +} + +// TODO: These implementations are broken because they only handle a single update. +impl Receiver { + pub fn notify_model_on_change(&self, ctx: &mut ModelContext) { + let watch = self.clone(); + let _ = ctx.spawn_local(async move { watch.updated().await }, |_, _, ctx| { + ctx.notify() + }); + } + + pub fn notify_view_on_change(&self, ctx: &mut ViewContext) { + let watch = self.clone(); + let _ = ctx.spawn_local(async move { watch.updated().await }, |_, _, ctx| { + ctx.notify() + }); + } +} + +pub fn channel(value: T) -> (Sender, Receiver) { + let value = Arc::new(RwLock::new(value)); + let (s, r) = channel::unbounded(); + let sender = Sender { + value: value.clone(), + updated: s, + }; + let receiver = Receiver { value, updated: r }; + (sender, receiver) +} diff --git a/zed/src/worktree/char_bag.rs b/zed/src/worktree/char_bag.rs new file mode 100644 index 0000000000..9e3c5314e9 --- /dev/null +++ b/zed/src/worktree/char_bag.rs @@ -0,0 +1,44 @@ +#[derive(Copy, Clone, Debug)] +pub struct CharBag(u64); + +impl CharBag { + pub fn is_superset(self, other: CharBag) -> bool { + self.0 & other.0 == other.0 + } + + fn insert(&mut self, c: char) { + if c >= 'a' && c <= 'z' { + let mut count = self.0; + let idx = c as u8 - 'a' as u8; + count = count >> (idx * 2); + count = ((count << 1) | 1) & 3; + count = count << idx * 2; + self.0 |= count; + } else if c >= '0' && c <= '9' { + let idx = c as u8 - '0' as u8; + self.0 |= 1 << (idx + 52); + } else if c == '-' { + self.0 |= 1 << 62; + } + } +} + +impl From<&str> for CharBag { + fn from(s: &str) -> Self { + let mut bag = Self(0); + for c in s.chars() { + bag.insert(c); + } + bag + } +} + +impl From<&[char]> for CharBag { + fn from(chars: &[char]) -> Self { + let mut bag = Self(0); + for c in chars { + bag.insert(*c); + } + bag + } +} diff --git a/zed/src/worktree/fuzzy.rs b/zed/src/worktree/fuzzy.rs new file mode 100644 index 0000000000..9277f78c4e --- /dev/null +++ b/zed/src/worktree/fuzzy.rs @@ -0,0 +1,494 @@ +use easy_parallel::Parallel; + +use super::char_bag::CharBag; + +use std::{ + cmp::{max, min, Ordering, Reverse}, + collections::BinaryHeap, +}; + +const BASE_DISTANCE_PENALTY: f64 = 0.6; +const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05; +const MIN_DISTANCE_PENALTY: f64 = 0.2; + +pub struct PathEntry { + pub entry_id: usize, + pub path_chars: CharBag, + pub path: Vec, + pub lowercase_path: Vec, + pub is_ignored: bool, +} + +#[derive(Clone, Debug)] +pub struct PathMatch { + pub score: f64, + pub positions: Vec, + pub tree_id: usize, + pub entry_id: usize, + pub skipped_prefix_len: usize, +} + +impl PartialEq for PathMatch { + fn eq(&self, other: &Self) -> bool { + self.score.eq(&other.score) + } +} + +impl Eq for PathMatch {} + +impl PartialOrd for PathMatch { + fn partial_cmp(&self, other: &Self) -> Option { + self.score.partial_cmp(&other.score) + } +} + +impl Ord for PathMatch { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +pub fn match_paths( + paths_by_tree_id: &[(usize, usize, &[PathEntry])], + query: &str, + include_ignored: bool, + smart_case: bool, + max_results: usize, +) -> Vec { + let lowercase_query = query.to_lowercase().chars().collect::>(); + let query = query.chars().collect::>(); + let lowercase_query = &lowercase_query; + let query = &query; + let query_chars = CharBag::from(&lowercase_query[..]); + + let cpus = num_cpus::get(); + let path_count = paths_by_tree_id + .iter() + .fold(0, |sum, (_, _, paths)| sum + paths.len()); + let segment_size = (path_count + cpus - 1) / cpus; + let mut segment_results = (0..cpus).map(|_| BinaryHeap::new()).collect::>(); + + Parallel::new().each( + segment_results.iter_mut().enumerate(), + |(segment_idx, results)| { + let segment_start = segment_idx * segment_size; + let segment_end = segment_start + segment_size; + + let mut min_score = 0.0; + let mut last_positions = Vec::new(); + last_positions.resize(query.len(), 0); + let mut match_positions = Vec::new(); + match_positions.resize(query.len(), 0); + let mut score_matrix = Vec::new(); + let mut best_position_matrix = Vec::new(); + + let mut tree_start = 0; + for (tree_id, skipped_prefix_len, paths) in paths_by_tree_id { + let tree_end = tree_start + paths.len(); + if tree_start < segment_end && segment_start < tree_end { + let start = max(tree_start, segment_start) - tree_start; + let end = min(tree_end, segment_end) - tree_start; + + match_single_tree_paths( + *tree_id, + *skipped_prefix_len, + paths, + start, + end, + query, + lowercase_query, + query_chars, + include_ignored, + smart_case, + results, + max_results, + &mut min_score, + &mut match_positions, + &mut last_positions, + &mut score_matrix, + &mut best_position_matrix, + ); + } + if tree_end >= segment_end { + break; + } + tree_start = tree_end; + } + }, + ); + + let mut results = segment_results + .into_iter() + .flatten() + .map(|r| r.0) + .collect::>(); + results.sort_unstable_by(|a, b| b.score.partial_cmp(&a.score).unwrap()); + results.truncate(max_results); + results +} + +fn match_single_tree_paths( + tree_id: usize, + skipped_prefix_len: usize, + path_entries: &[PathEntry], + start: usize, + end: usize, + query: &[char], + lowercase_query: &[char], + query_chars: CharBag, + include_ignored: bool, + smart_case: bool, + results: &mut BinaryHeap>, + max_results: usize, + min_score: &mut f64, + match_positions: &mut Vec, + last_positions: &mut Vec, + score_matrix: &mut Vec>, + best_position_matrix: &mut Vec, +) { + for i in start..end { + let path_entry = unsafe { &path_entries.get_unchecked(i) }; + + if !include_ignored && path_entry.is_ignored { + continue; + } + + if !path_entry.path_chars.is_superset(query_chars) { + continue; + } + + if !find_last_positions( + last_positions, + skipped_prefix_len, + &path_entry.lowercase_path, + &lowercase_query[..], + ) { + continue; + } + + let matrix_len = query.len() * (path_entry.path.len() - skipped_prefix_len); + score_matrix.clear(); + score_matrix.resize(matrix_len, None); + best_position_matrix.clear(); + best_position_matrix.resize(matrix_len, skipped_prefix_len); + + let score = score_match( + &query[..], + &lowercase_query[..], + &path_entry.path, + &path_entry.lowercase_path, + skipped_prefix_len, + smart_case, + &last_positions, + score_matrix, + best_position_matrix, + match_positions, + *min_score, + ); + + if score > 0.0 { + results.push(Reverse(PathMatch { + tree_id, + entry_id: path_entry.entry_id, + score, + positions: match_positions.clone(), + skipped_prefix_len, + })); + if results.len() == max_results { + *min_score = results.peek().unwrap().0.score; + } + } + } +} + +fn find_last_positions( + last_positions: &mut Vec, + skipped_prefix_len: usize, + path: &[char], + query: &[char], +) -> bool { + let mut path = path.iter(); + for (i, char) in query.iter().enumerate().rev() { + if let Some(j) = path.rposition(|c| c == char) { + if j >= skipped_prefix_len { + last_positions[i] = j; + } else { + return false; + } + } else { + return false; + } + } + true +} + +fn score_match( + query: &[char], + query_cased: &[char], + path: &[char], + path_cased: &[char], + skipped_prefix_len: usize, + smart_case: bool, + last_positions: &[usize], + score_matrix: &mut [Option], + best_position_matrix: &mut [usize], + match_positions: &mut [usize], + min_score: f64, +) -> f64 { + let score = recursive_score_match( + query, + query_cased, + path, + path_cased, + skipped_prefix_len, + smart_case, + last_positions, + score_matrix, + best_position_matrix, + min_score, + 0, + skipped_prefix_len, + query.len() as f64, + ) * query.len() as f64; + + if score <= 0.0 { + return 0.0; + } + + let path_len = path.len() - skipped_prefix_len; + let mut cur_start = 0; + for i in 0..query.len() { + match_positions[i] = best_position_matrix[i * path_len + cur_start] - skipped_prefix_len; + cur_start = match_positions[i] + 1; + } + + score +} + +fn recursive_score_match( + query: &[char], + query_cased: &[char], + path: &[char], + path_cased: &[char], + skipped_prefix_len: usize, + smart_case: bool, + last_positions: &[usize], + score_matrix: &mut [Option], + best_position_matrix: &mut [usize], + min_score: f64, + query_idx: usize, + path_idx: usize, + cur_score: f64, +) -> f64 { + if query_idx == query.len() { + return 1.0; + } + + let path_len = path.len() - skipped_prefix_len; + + if let Some(memoized) = score_matrix[query_idx * path_len + path_idx - skipped_prefix_len] { + return memoized; + } + + let mut score = 0.0; + let mut best_position = 0; + + let query_char = query_cased[query_idx]; + let limit = last_positions[query_idx]; + + let mut last_slash = 0; + for j in path_idx..=limit { + let path_char = path_cased[j]; + let is_path_sep = path_char == '/' || path_char == '\\'; + + if query_idx == 0 && is_path_sep { + last_slash = j; + } + + if query_char == path_char || (is_path_sep && query_char == '_' || query_char == '\\') { + let mut char_score = 1.0; + if j > path_idx { + let last = path[j - 1]; + let curr = path[j]; + + if last == '/' { + char_score = 0.9; + } else if last == '-' || last == '_' || last == ' ' || last.is_numeric() { + char_score = 0.8; + } else if last.is_lowercase() && curr.is_uppercase() { + char_score = 0.8; + } else if last == '.' { + char_score = 0.7; + } else if query_idx == 0 { + char_score = BASE_DISTANCE_PENALTY; + } else { + char_score = MIN_DISTANCE_PENALTY.max( + BASE_DISTANCE_PENALTY + - (j - path_idx - 1) as f64 * ADDITIONAL_DISTANCE_PENALTY, + ); + } + } + + // Apply a severe penalty if the case doesn't match. + // This will make the exact matches have higher score than the case-insensitive and the + // path insensitive matches. + if (smart_case || path[j] == '/') && query[query_idx] != path[j] { + char_score *= 0.001; + } + + let mut multiplier = char_score; + + // Scale the score based on how deep within the patch we found the match. + if query_idx == 0 { + multiplier /= (path.len() - last_slash) as f64; + } + + let mut next_score = 1.0; + if min_score > 0.0 { + next_score = cur_score * multiplier; + // Scores only decrease. If we can't pass the previous best, bail + if next_score < min_score { + // Ensure that score is non-zero so we use it in the memo table. + if score == 0.0 { + score = 1e-18; + } + continue; + } + } + + let new_score = recursive_score_match( + query, + query_cased, + path, + path_cased, + skipped_prefix_len, + smart_case, + last_positions, + score_matrix, + best_position_matrix, + min_score, + query_idx + 1, + j + 1, + next_score, + ) * multiplier; + + if new_score > score { + score = new_score; + best_position = j; + // Optimization: can't score better than 1. + if new_score == 1.0 { + break; + } + } + } + } + + if best_position != 0 { + best_position_matrix[query_idx * path_len + path_idx - skipped_prefix_len] = best_position; + } + + score_matrix[query_idx * path_len + path_idx - skipped_prefix_len] = Some(score); + score +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_match_path_entries() { + let paths = vec![ + "", + "a", + "ab", + "abC", + "abcd", + "alphabravocharlie", + "AlphaBravoCharlie", + "thisisatestdir", + "/////ThisIsATestDir", + "/this/is/a/test/dir", + "/test/tiatd", + ]; + + assert_eq!( + match_query("abc", false, &paths), + vec![ + ("abC", vec![0, 1, 2]), + ("abcd", vec![0, 1, 2]), + ("AlphaBravoCharlie", vec![0, 5, 10]), + ("alphabravocharlie", vec![4, 5, 10]), + ] + ); + assert_eq!( + match_query("t/i/a/t/d", false, &paths), + vec![("/this/is/a/test/dir", vec![1, 5, 6, 8, 9, 10, 11, 15, 16]),] + ); + + assert_eq!( + match_query("tiatd", false, &paths), + vec![ + ("/test/tiatd", vec![6, 7, 8, 9, 10]), + ("/this/is/a/test/dir", vec![1, 6, 9, 11, 16]), + ("/////ThisIsATestDir", vec![5, 9, 11, 12, 16]), + ("thisisatestdir", vec![0, 2, 6, 7, 11]), + ] + ); + } + + fn match_query<'a>( + query: &str, + smart_case: bool, + paths: &Vec<&'a str>, + ) -> Vec<(&'a str, Vec)> { + let lowercase_query = query.to_lowercase().chars().collect::>(); + let query = query.chars().collect::>(); + let query_chars = CharBag::from(&lowercase_query[..]); + + let mut path_entries = Vec::new(); + for (i, path) in paths.iter().enumerate() { + let lowercase_path = path.to_lowercase().chars().collect::>(); + let path_chars = CharBag::from(&lowercase_path[..]); + let path = path.chars().collect(); + path_entries.push(PathEntry { + entry_id: i, + path_chars, + path, + lowercase_path, + is_ignored: false, + }); + } + + let mut match_positions = Vec::new(); + let mut last_positions = Vec::new(); + match_positions.resize(query.len(), 0); + last_positions.resize(query.len(), 0); + + let mut results = BinaryHeap::new(); + match_single_tree_paths( + 0, + 0, + &path_entries, + 0, + path_entries.len(), + &query[..], + &lowercase_query[..], + query_chars, + true, + smart_case, + &mut results, + 100, + &mut 0.0, + &mut match_positions, + &mut last_positions, + &mut Vec::new(), + &mut Vec::new(), + ); + + results + .into_iter() + .rev() + .map(|result| (paths[result.0.entry_id].clone(), result.0.positions)) + .collect() + } +} diff --git a/zed/src/worktree/mod.rs b/zed/src/worktree/mod.rs new file mode 100644 index 0000000000..3aa3ada945 --- /dev/null +++ b/zed/src/worktree/mod.rs @@ -0,0 +1,5 @@ +mod char_bag; +mod fuzzy; +mod worktree; + +pub use worktree::{match_paths, FileHandle, PathMatch, Worktree}; diff --git a/zed/src/worktree/worktree.rs b/zed/src/worktree/worktree.rs new file mode 100644 index 0000000000..f3d8ab3cd7 --- /dev/null +++ b/zed/src/worktree/worktree.rs @@ -0,0 +1,651 @@ +pub use super::fuzzy::PathMatch; +use super::{ + char_bag::CharBag, + fuzzy::{self, PathEntry}, +}; +use crate::{editor::History, timer, util::post_inc}; +use anyhow::{anyhow, Result}; +use crossbeam_queue::ArrayQueue; +use easy_parallel::Parallel; +use gpui::{AppContext, Entity, ModelContext, ModelHandle}; +use ignore::dir::{Ignore, IgnoreBuilder}; +use parking_lot::RwLock; +use smol::prelude::*; +use std::{ + collections::HashMap, + ffi::{OsStr, OsString}, + fmt, fs, io, + os::unix::fs::MetadataExt, + path::Path, + path::PathBuf, + sync::Arc, + time::Duration, +}; + +#[derive(Clone)] +pub struct Worktree(Arc>); + +struct WorktreeState { + id: usize, + path: PathBuf, + entries: Vec, + file_paths: Vec, + histories: HashMap, + scanning: bool, +} + +struct DirToScan { + id: usize, + path: PathBuf, + relative_path: PathBuf, + ignore: Option, + dirs_to_scan: Arc>>, +} + +impl Worktree { + pub fn new(id: usize, path: T, ctx: Option<&mut ModelContext>) -> Self + where + T: Into, + { + let tree = Self(Arc::new(RwLock::new(WorktreeState { + id, + path: path.into(), + entries: Vec::new(), + file_paths: Vec::new(), + histories: HashMap::new(), + scanning: ctx.is_some(), + }))); + + if let Some(ctx) = ctx { + tree.0.write().scanning = true; + + let tree = tree.clone(); + let (tx, rx) = smol::channel::bounded(1); + std::thread::spawn(move || { + let _ = smol::block_on(tx.send(tree.scan_dirs())); + }); + let _ = ctx.spawn(async move { rx.recv().await.unwrap() }, Self::done_scanning); + + let _ = ctx.spawn_stream_local( + timer::repeat(Duration::from_millis(100)).map(|_| ()), + Self::scanning, + ); + } + + tree + } + + fn scan_dirs(&self) -> io::Result<()> { + let path = self.0.read().path.clone(); + let metadata = fs::metadata(&path)?; + let ino = metadata.ino(); + let is_symlink = fs::symlink_metadata(&path)?.file_type().is_symlink(); + let name = path + .file_name() + .map(|name| OsString::from(name)) + .unwrap_or(OsString::from("/")); + let relative_path = PathBuf::from(&name); + + let mut ignore = IgnoreBuilder::new().build().add_parents(&path).unwrap(); + if metadata.is_dir() { + ignore = ignore.add_child(&path).unwrap(); + } + let is_ignored = ignore.matched(&path, metadata.is_dir()).is_ignore(); + + if metadata.file_type().is_dir() { + let is_ignored = is_ignored || name == ".git"; + let id = self.push_dir(None, name, ino, is_symlink, is_ignored); + let queue = Arc::new(ArrayQueue::new(1000)); + + queue.push(Ok(DirToScan { + id, + path, + relative_path, + ignore: Some(ignore), + dirs_to_scan: queue.clone(), + })); + + Parallel::>::new() + .each(0..16, |_| { + while let Some(result) = queue.pop() { + self.scan_dir(result?)?; + } + Ok(()) + }) + .run() + .into_iter() + .collect::>()?; + } else { + self.push_file(None, name, ino, is_symlink, is_ignored, relative_path); + } + + Ok(()) + } + + fn scan_dir(&self, to_scan: DirToScan) -> io::Result<()> { + let mut new_children = Vec::new(); + + for child_entry in fs::read_dir(&to_scan.path)? { + let child_entry = child_entry?; + let name = child_entry.file_name(); + let relative_path = to_scan.relative_path.join(&name); + let metadata = child_entry.metadata()?; + let ino = metadata.ino(); + let is_symlink = metadata.file_type().is_symlink(); + + if metadata.is_dir() { + let path = to_scan.path.join(&name); + let mut is_ignored = true; + let mut ignore = None; + + if let Some(parent_ignore) = to_scan.ignore.as_ref() { + let child_ignore = parent_ignore.add_child(&path).unwrap(); + is_ignored = child_ignore.matched(&path, true).is_ignore() || name == ".git"; + if !is_ignored { + ignore = Some(child_ignore); + } + } + + let id = self.push_dir(Some(to_scan.id), name, ino, is_symlink, is_ignored); + new_children.push(id); + + let dirs_to_scan = to_scan.dirs_to_scan.clone(); + let _ = to_scan.dirs_to_scan.push(Ok(DirToScan { + id, + path, + relative_path, + ignore, + dirs_to_scan, + })); + } else { + let is_ignored = to_scan.ignore.as_ref().map_or(true, |i| { + i.matched(to_scan.path.join(&name), false).is_ignore() + }); + + new_children.push(self.push_file( + Some(to_scan.id), + name, + ino, + is_symlink, + is_ignored, + relative_path, + )); + }; + } + + if let Entry::Dir { children, .. } = &mut self.0.write().entries[to_scan.id] { + *children = new_children.clone(); + } + + Ok(()) + } + + fn push_dir( + &self, + parent: Option, + name: OsString, + ino: u64, + is_symlink: bool, + is_ignored: bool, + ) -> usize { + let entries = &mut self.0.write().entries; + let dir_id = entries.len(); + entries.push(Entry::Dir { + parent, + name, + ino, + is_symlink, + is_ignored, + children: Vec::new(), + }); + dir_id + } + + fn push_file( + &self, + parent: Option, + name: OsString, + ino: u64, + is_symlink: bool, + is_ignored: bool, + path: PathBuf, + ) -> usize { + let path = path.to_string_lossy(); + let lowercase_path = path.to_lowercase().chars().collect::>(); + let path = path.chars().collect::>(); + let path_chars = CharBag::from(&path[..]); + + let mut state = self.0.write(); + let entry_id = state.entries.len(); + state.entries.push(Entry::File { + parent, + name, + ino, + is_symlink, + is_ignored, + }); + state.file_paths.push(PathEntry { + entry_id, + path_chars, + path, + lowercase_path, + is_ignored, + }); + entry_id + } + + pub fn entry_path(&self, mut entry_id: usize) -> Result { + let state = self.0.read(); + + if entry_id >= state.entries.len() { + return Err(anyhow!("Entry does not exist in tree")); + } + + let mut entries = Vec::new(); + loop { + let entry = &state.entries[entry_id]; + entries.push(entry); + if let Some(parent_id) = entry.parent() { + entry_id = parent_id; + } else { + break; + } + } + + let mut path = PathBuf::new(); + for entry in entries.into_iter().rev() { + path.push(entry.name()); + } + Ok(path) + } + + pub fn abs_entry_path(&self, entry_id: usize) -> Result { + let mut path = self.0.read().path.clone(); + path.pop(); + Ok(path.join(self.entry_path(entry_id)?)) + } + + fn fmt_entry(&self, f: &mut fmt::Formatter<'_>, entry_id: usize, indent: usize) -> fmt::Result { + match &self.0.read().entries[entry_id] { + Entry::Dir { name, children, .. } => { + write!( + f, + "{}{}/ ({})\n", + " ".repeat(indent), + name.to_string_lossy(), + entry_id + )?; + for child_id in children.iter() { + self.fmt_entry(f, *child_id, indent + 2)?; + } + Ok(()) + } + Entry::File { name, .. } => write!( + f, + "{}{} ({})\n", + " ".repeat(indent), + name.to_string_lossy(), + entry_id + ), + } + } + + pub fn path(&self) -> PathBuf { + PathBuf::from(&self.0.read().path) + } + + pub fn contains_path(&self, path: &Path) -> bool { + path.starts_with(self.path()) + } + + pub fn iter(&self) -> Iter { + Iter { + tree: self.clone(), + stack: Vec::new(), + started: false, + } + } + + pub fn files(&self) -> FilesIter { + FilesIter { + iter: self.iter(), + path: PathBuf::new(), + } + } + + pub fn entry_count(&self) -> usize { + self.0.read().entries.len() + } + + pub fn file_count(&self) -> usize { + self.0.read().file_paths.len() + } + + pub fn load_history(&self, entry_id: usize) -> impl Future> { + let tree = self.clone(); + + async move { + if let Some(history) = tree.0.read().histories.get(&entry_id) { + return Ok(history.clone()); + } + + let path = tree.abs_entry_path(entry_id)?; + + let mut file = smol::fs::File::open(&path).await?; + let mut base_text = String::new(); + file.read_to_string(&mut base_text).await?; + let history = History { base_text }; + tree.0.write().histories.insert(entry_id, history.clone()); + Ok(history) + } + } + + fn scanning(&mut self, _: Option<()>, ctx: &mut ModelContext) { + if self.0.read().scanning { + ctx.notify(); + } else { + ctx.halt_stream(); + } + } + + fn done_scanning(&mut self, result: io::Result<()>, ctx: &mut ModelContext) { + self.0.write().scanning = false; + if let Err(error) = result { + log::error!("error populating worktree: {}", error); + } else { + ctx.notify(); + } + } +} + +impl fmt::Debug for Worktree { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.entry_count() == 0 { + write!(f, "Empty tree\n") + } else { + self.fmt_entry(f, 0, 0) + } + } +} + +impl Entity for Worktree { + type Event = (); +} + +pub trait WorktreeHandle { + fn file(&self, entry_id: usize, app: &AppContext) -> Result; +} + +impl WorktreeHandle for ModelHandle { + fn file(&self, entry_id: usize, app: &AppContext) -> Result { + if entry_id >= self.as_ref(app).entry_count() { + return Err(anyhow!("Entry does not exist in tree")); + } + + Ok(FileHandle { + worktree: self.clone(), + entry_id, + }) + } +} + +#[derive(Clone, Debug)] +pub enum Entry { + Dir { + parent: Option, + name: OsString, + ino: u64, + is_symlink: bool, + is_ignored: bool, + children: Vec, + }, + File { + parent: Option, + name: OsString, + ino: u64, + is_symlink: bool, + is_ignored: bool, + }, +} + +impl Entry { + fn parent(&self) -> Option { + match self { + Entry::Dir { parent, .. } | Entry::File { parent, .. } => *parent, + } + } + + fn name(&self) -> &OsStr { + match self { + Entry::Dir { name, .. } | Entry::File { name, .. } => name, + } + } +} + +#[derive(Clone)] +pub struct FileHandle { + worktree: ModelHandle, + entry_id: usize, +} + +impl FileHandle { + pub fn path(&self, app: &AppContext) -> PathBuf { + self.worktree.as_ref(app).entry_path(self.entry_id).unwrap() + } + + pub fn load_history(&self, app: &AppContext) -> impl Future> { + self.worktree.as_ref(app).load_history(self.entry_id) + } + + pub fn entry_id(&self) -> (usize, usize) { + (self.worktree.id(), self.entry_id) + } +} + +struct IterStackEntry { + entry_id: usize, + child_idx: usize, +} + +pub struct Iter { + tree: Worktree, + stack: Vec, + started: bool, +} + +impl Iterator for Iter { + type Item = Traversal; + + fn next(&mut self) -> Option { + let state = self.tree.0.read(); + + if !self.started { + self.started = true; + + return if let Some(entry) = state.entries.first().cloned() { + self.stack.push(IterStackEntry { + entry_id: 0, + child_idx: 0, + }); + + Some(Traversal::Push { entry_id: 0, entry }) + } else { + None + }; + } + + while let Some(parent) = self.stack.last_mut() { + if let Entry::Dir { children, .. } = &state.entries[parent.entry_id] { + if parent.child_idx < children.len() { + let child_id = children[post_inc(&mut parent.child_idx)]; + + self.stack.push(IterStackEntry { + entry_id: child_id, + child_idx: 0, + }); + + return Some(Traversal::Push { + entry_id: child_id, + entry: state.entries[child_id].clone(), + }); + } else { + self.stack.pop(); + + return Some(Traversal::Pop); + } + } else { + self.stack.pop(); + + return Some(Traversal::Pop); + } + } + + None + } +} + +#[derive(Debug)] +pub enum Traversal { + Push { entry_id: usize, entry: Entry }, + Pop, +} + +pub struct FilesIter { + iter: Iter, + path: PathBuf, +} + +pub struct FilesIterItem { + pub entry_id: usize, + pub path: PathBuf, +} + +impl Iterator for FilesIter { + type Item = FilesIterItem; + + fn next(&mut self) -> Option { + loop { + match self.iter.next() { + Some(Traversal::Push { + entry_id, entry, .. + }) => match entry { + Entry::Dir { name, .. } => { + self.path.push(name); + } + Entry::File { name, .. } => { + self.path.push(name); + return Some(FilesIterItem { + entry_id, + path: self.path.clone(), + }); + } + }, + Some(Traversal::Pop) => { + self.path.pop(); + } + None => { + return None; + } + } + } + } +} + +trait UnwrapIgnoreTuple { + fn unwrap(self) -> Ignore; +} + +impl UnwrapIgnoreTuple for (Ignore, Option) { + fn unwrap(self) -> Ignore { + if let Some(error) = self.1 { + log::error!("error loading gitignore data: {}", error); + } + self.0 + } +} + +pub fn match_paths( + trees: &[Worktree], + query: &str, + include_ignored: bool, + smart_case: bool, + max_results: usize, +) -> Vec { + let tree_states = trees.iter().map(|tree| tree.0.read()).collect::>(); + fuzzy::match_paths( + &tree_states + .iter() + .map(|tree| { + let skip_prefix = if trees.len() == 1 { + if let Some(Entry::Dir { name, .. }) = tree.entries.get(0) { + let name = name.to_string_lossy(); + if name == "/" { + 1 + } else { + name.chars().count() + 1 + } + } else { + 0 + } + } else { + 0 + }; + + (tree.id, skip_prefix, &tree.file_paths[..]) + }) + .collect::>()[..], + query, + include_ignored, + smart_case, + max_results, + ) +} + +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::test_utils::*; +// use anyhow::Result; +// use std::os::unix; +// +// // #[test] +// // fn test_populate_and_search() -> Result<()> { +// // let dir = build_tempdir(json!({ +// // "root": { +// // "apple": "", +// // "banana": { +// // "carrot": { +// // "date": "", +// // "endive": "", +// // } +// // }, +// // "fennel": { +// // "grape": "", +// // } +// // } +// // })); +// // +// // let root_link_path = dir.path().join("root_link"); +// // unix::fs::symlink(&dir.path().join("root"), &root_link_path)?; +// // +// // let tree = Worktree::new(1, root_link_path, None); +// // let (tx, _) = channel::unbounded(); +// // tree.populate(&tx)?; +// // assert_eq!(tree.file_count(), 4); +// // +// // let results = match_paths(&[tree.clone()], "bna", false, false, 10) +// // .iter() +// // .map(|result| tree.entry_path(result.entry_id)) +// // .collect::, _>>()?; +// // +// // assert_eq!( +// // results, +// // vec![ +// // PathBuf::from("root_link/banana/carrot/date"), +// // PathBuf::from("root_link/banana/carrot/endive"), +// // ] +// // ); +// // +// // Ok(()) +// // } +// }