Detailed changes
@@ -449,7 +449,7 @@ dependencies = [
[[package]]
name = "cocoa"
version = "0.24.0"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/servo/core-foundation-rs?rev=025dcb3c0d1ef01530f57ef65f3b1deb948f5737#025dcb3c0d1ef01530f57ef65f3b1deb948f5737"
dependencies = [
"bitflags 1.2.1",
"block",
@@ -464,7 +464,7 @@ dependencies = [
[[package]]
name = "cocoa-foundation"
version = "0.1.0"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/servo/core-foundation-rs?rev=025dcb3c0d1ef01530f57ef65f3b1deb948f5737#025dcb3c0d1ef01530f57ef65f3b1deb948f5737"
dependencies = [
"bitflags 1.2.1",
"block",
@@ -499,7 +499,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "core-foundation"
version = "0.9.1"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/servo/core-foundation-rs?rev=025dcb3c0d1ef01530f57ef65f3b1deb948f5737#025dcb3c0d1ef01530f57ef65f3b1deb948f5737"
dependencies = [
"core-foundation-sys",
"libc",
@@ -508,12 +508,12 @@ dependencies = [
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/servo/core-foundation-rs?rev=025dcb3c0d1ef01530f57ef65f3b1deb948f5737#025dcb3c0d1ef01530f57ef65f3b1deb948f5737"
[[package]]
name = "core-graphics"
version = "0.22.2"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/servo/core-foundation-rs?rev=025dcb3c0d1ef01530f57ef65f3b1deb948f5737#025dcb3c0d1ef01530f57ef65f3b1deb948f5737"
dependencies = [
"bitflags 1.2.1",
"core-foundation",
@@ -525,7 +525,7 @@ dependencies = [
[[package]]
name = "core-graphics-types"
version = "0.1.1"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/servo/core-foundation-rs?rev=025dcb3c0d1ef01530f57ef65f3b1deb948f5737#025dcb3c0d1ef01530f57ef65f3b1deb948f5737"
dependencies = [
"bitflags 1.2.1",
"core-foundation",
@@ -4,11 +4,14 @@ members = ["zed", "gpui", "fsevent", "scoped_pool"]
[patch.crates-io]
async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
-# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/454
-cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
-cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
-core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
-core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
+# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
+cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
+cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
+core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
+core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "025dcb3c0d1ef01530f57ef65f3b1deb948f5737"}
[profile.dev]
split-debuginfo = "unpacked"
+
+[profile.release]
+debug = true
@@ -12,16 +12,16 @@ use keymap::MatchResult;
use parking_lot::{Mutex, RwLock};
use pathfinder_geometry::{rect::RectF, vector::vec2f};
use platform::Event;
-use postage::{sink::Sink as _, stream::Stream as _};
+use postage::{mpsc, sink::Sink as _, stream::Stream as _};
use smol::prelude::*;
use std::{
any::{type_name, Any, TypeId},
cell::RefCell,
- collections::{hash_map::Entry, HashMap, HashSet, VecDeque},
+ collections::{HashMap, HashSet, VecDeque},
fmt::{self, Debug},
hash::{Hash, Hasher},
marker::PhantomData,
- path::PathBuf,
+ path::{Path, PathBuf},
rc::{self, Rc},
sync::{Arc, Weak},
time::Duration,
@@ -87,7 +87,7 @@ pub enum MenuItem<'a> {
pub struct App(Rc<RefCell<MutableAppContext>>);
#[derive(Clone)]
-pub struct TestAppContext(Rc<RefCell<MutableAppContext>>);
+pub struct TestAppContext(Rc<RefCell<MutableAppContext>>, Rc<platform::test::Platform>);
impl App {
pub fn test<T, A: AssetSource, F: FnOnce(&mut MutableAppContext) -> T>(
@@ -111,13 +111,16 @@ impl App {
Fn: FnOnce(TestAppContext) -> F,
F: Future<Output = T>,
{
- let platform = platform::test::platform();
+ let platform = Rc::new(platform::test::platform());
let foreground = Rc::new(executor::Foreground::test());
- let ctx = TestAppContext(Rc::new(RefCell::new(MutableAppContext::new(
- foreground.clone(),
- Rc::new(platform),
- asset_source,
- ))));
+ let ctx = TestAppContext(
+ Rc::new(RefCell::new(MutableAppContext::new(
+ foreground.clone(),
+ platform.clone(),
+ asset_source,
+ ))),
+ platform,
+ );
ctx.0.borrow_mut().weak_self = Some(Rc::downgrade(&ctx.0));
let future = f(ctx);
@@ -332,6 +335,14 @@ impl TestAppContext {
pub fn platform(&self) -> Rc<dyn platform::Platform> {
self.0.borrow().platform.clone()
}
+
+ pub fn simulate_new_path_selection(&self, result: impl FnOnce(PathBuf) -> Option<PathBuf>) {
+ self.1.as_ref().simulate_new_path_selection(result);
+ }
+
+ pub fn did_prompt_for_new_path(&self) -> bool {
+ self.1.as_ref().did_prompt_for_new_path()
+ }
}
impl UpdateModel for TestAppContext {
@@ -381,7 +392,6 @@ pub struct MutableAppContext {
subscriptions: HashMap<usize, Vec<Subscription>>,
model_observations: HashMap<usize, Vec<ModelObservation>>,
view_observations: HashMap<usize, Vec<ViewObservation>>,
- async_observations: HashMap<usize, postage::broadcast::Sender<()>>,
presenters_and_platform_windows:
HashMap<usize, (Rc<RefCell<Presenter>>, Box<dyn platform::Window>)>,
debug_elements_callbacks: HashMap<usize, Box<dyn Fn(&AppContext) -> crate::json::Value>>,
@@ -423,7 +433,6 @@ impl MutableAppContext {
subscriptions: HashMap::new(),
model_observations: HashMap::new(),
view_observations: HashMap::new(),
- async_observations: HashMap::new(),
presenters_and_platform_windows: HashMap::new(),
debug_elements_callbacks: HashMap::new(),
foreground,
@@ -585,6 +594,22 @@ impl MutableAppContext {
);
}
+ pub fn prompt_for_new_path<F>(&self, directory: &Path, done_fn: F)
+ where
+ F: 'static + FnOnce(Option<PathBuf>, &mut MutableAppContext),
+ {
+ let app = self.weak_self.as_ref().unwrap().upgrade().unwrap();
+ let foreground = self.foreground.clone();
+ self.platform().prompt_for_new_path(
+ directory,
+ Box::new(move |path| {
+ foreground
+ .spawn(async move { (done_fn)(path, &mut *app.borrow_mut()) })
+ .detach();
+ }),
+ );
+ }
+
pub(crate) fn notify_view(&mut self, window_id: usize, view_id: usize) {
self.pending_effects
.push_back(Effect::ViewNotification { window_id, view_id });
@@ -878,13 +903,11 @@ impl MutableAppContext {
self.ctx.models.remove(&model_id);
self.subscriptions.remove(&model_id);
self.model_observations.remove(&model_id);
- self.async_observations.remove(&model_id);
}
for (window_id, view_id) in dropped_views {
self.subscriptions.remove(&view_id);
self.model_observations.remove(&view_id);
- self.async_observations.remove(&view_id);
self.ctx.views.remove(&(window_id, view_id));
let change_focus_to = self.ctx.windows.get_mut(&window_id).and_then(|window| {
window
@@ -1061,12 +1084,6 @@ impl MutableAppContext {
}
}
}
-
- if let Entry::Occupied(mut entry) = self.async_observations.entry(observed_id) {
- if entry.get_mut().blocking_send(()).is_err() {
- entry.remove_entry();
- }
- }
}
fn notify_view_observers(&mut self, window_id: usize, view_id: usize) {
@@ -1079,7 +1096,7 @@ impl MutableAppContext {
}
if let Some(observations) = self.view_observations.remove(&view_id) {
- if self.ctx.models.contains_key(&view_id) {
+ if self.ctx.views.contains_key(&(window_id, view_id)) {
for mut observation in observations {
let alive = if let Some(mut view) = self
.ctx
@@ -1111,12 +1128,6 @@ impl MutableAppContext {
}
}
}
-
- if let Entry::Occupied(mut entry) = self.async_observations.entry(view_id) {
- if entry.get_mut().blocking_send(()).is_err() {
- entry.remove_entry();
- }
- }
}
fn focus(&mut self, window_id: usize, focused_id: usize) {
@@ -1748,6 +1759,10 @@ impl<'a, T: View> ViewContext<'a, T> {
self.window_id
}
+ pub fn view_id(&self) -> usize {
+ self.view_id
+ }
+
pub fn foreground(&self) -> &Rc<executor::Foreground> {
self.app.foreground_executor()
}
@@ -1756,6 +1771,20 @@ impl<'a, T: View> ViewContext<'a, T> {
&self.app.ctx.background
}
+ pub fn prompt_for_paths<F>(&self, options: PathPromptOptions, done_fn: F)
+ where
+ F: 'static + FnOnce(Option<Vec<PathBuf>>, &mut MutableAppContext),
+ {
+ self.app.prompt_for_paths(options, done_fn)
+ }
+
+ pub fn prompt_for_new_path<F>(&self, directory: &Path, done_fn: F)
+ where
+ F: 'static + FnOnce(Option<PathBuf>, &mut MutableAppContext),
+ {
+ self.app.prompt_for_new_path(directory, done_fn)
+ }
+
pub fn debug_elements(&self) -> crate::json::Value {
self.app.debug_elements(self.window_id).unwrap()
}
@@ -1809,22 +1838,11 @@ impl<'a, T: View> ViewContext<'a, T> {
F: 'static + FnMut(&mut T, ModelHandle<E>, &E::Event, &mut ViewContext<T>),
{
let emitter_handle = handle.downgrade();
- self.app
- .subscriptions
- .entry(handle.id())
- .or_default()
- .push(Subscription::FromView {
- window_id: self.window_id,
- view_id: self.view_id,
- callback: Box::new(move |view, payload, app, window_id, view_id| {
- if let Some(emitter_handle) = emitter_handle.upgrade(app.as_ref()) {
- let model = view.downcast_mut().expect("downcast is type safe");
- let payload = payload.downcast_ref().expect("downcast is type safe");
- let mut ctx = ViewContext::new(app, window_id, view_id);
- callback(model, emitter_handle, payload, &mut ctx);
- }
- }),
- });
+ self.subscribe(handle, move |model, payload, ctx| {
+ if let Some(emitter_handle) = emitter_handle.upgrade(ctx.as_ref()) {
+ callback(model, emitter_handle, payload, ctx);
+ }
+ });
}
pub fn subscribe_to_view<V, F>(&mut self, handle: &ViewHandle<V>, mut callback: F)
@@ -1834,7 +1852,19 @@ impl<'a, T: View> ViewContext<'a, T> {
F: 'static + FnMut(&mut T, ViewHandle<V>, &V::Event, &mut ViewContext<T>),
{
let emitter_handle = handle.downgrade();
+ self.subscribe(handle, move |view, payload, ctx| {
+ if let Some(emitter_handle) = emitter_handle.upgrade(ctx.as_ref()) {
+ callback(view, emitter_handle, payload, ctx);
+ }
+ });
+ }
+ pub fn subscribe<E, F>(&mut self, handle: &impl Handle<E>, mut callback: F)
+ where
+ E: Entity,
+ E::Event: 'static,
+ F: 'static + FnMut(&mut T, &E::Event, &mut ViewContext<T>),
+ {
self.app
.subscriptions
.entry(handle.id())
@@ -1842,13 +1872,11 @@ impl<'a, T: View> ViewContext<'a, T> {
.push(Subscription::FromView {
window_id: self.window_id,
view_id: self.view_id,
- callback: Box::new(move |view, payload, app, window_id, view_id| {
- if let Some(emitter_handle) = emitter_handle.upgrade(&app) {
- let model = view.downcast_mut().expect("downcast is type safe");
- let payload = payload.downcast_ref().expect("downcast is type safe");
- let mut ctx = ViewContext::new(app, window_id, view_id);
- callback(model, emitter_handle, payload, &mut ctx);
- }
+ callback: Box::new(move |entity, payload, app, window_id, view_id| {
+ let entity = entity.downcast_mut().expect("downcast is type safe");
+ let payload = payload.downcast_ref().expect("downcast is type safe");
+ let mut ctx = ViewContext::new(app, window_id, view_id);
+ callback(entity, payload, &mut ctx);
}),
});
}
@@ -2072,7 +2100,7 @@ impl<T: Entity> ModelHandle<T> {
}
}
- fn downgrade(&self) -> WeakModelHandle<T> {
+ pub fn downgrade(&self) -> WeakModelHandle<T> {
WeakModelHandle::new(self.model_id)
}
@@ -2106,12 +2134,24 @@ impl<T: Entity> ModelHandle<T> {
ctx: &TestAppContext,
mut predicate: impl FnMut(&T, &AppContext) -> bool,
) -> impl Future<Output = ()> {
+ let (tx, mut rx) = mpsc::channel(1024);
+
let mut ctx = ctx.0.borrow_mut();
- let tx = ctx
- .async_observations
- .entry(self.id())
- .or_insert_with(|| postage::broadcast::channel(128).0);
- let mut rx = tx.subscribe();
+ self.update(&mut *ctx, |_, ctx| {
+ ctx.observe(self, {
+ let mut tx = tx.clone();
+ move |_, _, _| {
+ tx.blocking_send(()).ok();
+ }
+ });
+ ctx.subscribe(self, {
+ let mut tx = tx.clone();
+ move |_, _, _| {
+ tx.blocking_send(()).ok();
+ }
+ })
+ });
+
let ctx = ctx.weak_self.as_ref().unwrap().upgrade().unwrap();
let handle = self.downgrade();
@@ -2232,27 +2272,6 @@ impl<T> Clone for WeakModelHandle<T> {
}
}
-pub struct AnyModelHandle {
- model_id: usize,
- ref_counts: Arc<Mutex<RefCounts>>,
-}
-
-impl<T: Entity> From<ModelHandle<T>> for AnyModelHandle {
- fn from(handle: ModelHandle<T>) -> Self {
- handle.ref_counts.lock().inc_entity(handle.model_id);
- Self {
- model_id: handle.model_id,
- ref_counts: handle.ref_counts.clone(),
- }
- }
-}
-
-impl Drop for AnyModelHandle {
- fn drop(&mut self) {
- self.ref_counts.lock().dec_model(self.model_id);
- }
-}
-
pub struct ViewHandle<T> {
window_id: usize,
view_id: usize,
@@ -2303,19 +2322,41 @@ impl<T: View> ViewHandle<T> {
pub fn condition(
&self,
ctx: &TestAppContext,
- mut predicate: impl 'static + FnMut(&T, &AppContext) -> bool,
- ) -> impl 'static + Future<Output = ()> {
+ predicate: impl FnMut(&T, &AppContext) -> bool,
+ ) -> impl Future<Output = ()> {
+ self.condition_with_duration(Duration::from_millis(500), ctx, predicate)
+ }
+
+ pub fn condition_with_duration(
+ &self,
+ duration: Duration,
+ ctx: &TestAppContext,
+ mut predicate: impl FnMut(&T, &AppContext) -> bool,
+ ) -> impl Future<Output = ()> {
+ let (tx, mut rx) = mpsc::channel(1024);
+
let mut ctx = ctx.0.borrow_mut();
- let tx = ctx
- .async_observations
- .entry(self.id())
- .or_insert_with(|| postage::broadcast::channel(128).0);
- let mut rx = tx.subscribe();
+ self.update(&mut *ctx, |_, ctx| {
+ ctx.observe_view(self, {
+ let mut tx = tx.clone();
+ move |_, _, _| {
+ tx.blocking_send(()).ok();
+ }
+ });
+
+ ctx.subscribe(self, {
+ let mut tx = tx.clone();
+ move |_, _, _| {
+ tx.blocking_send(()).ok();
+ }
+ })
+ });
+
let ctx = ctx.weak_self.as_ref().unwrap().upgrade().unwrap();
let handle = self.downgrade();
async move {
- timeout(Duration::from_millis(200), async move {
+ timeout(duration, async move {
loop {
{
let ctx = ctx.borrow();
@@ -2323,7 +2364,7 @@ impl<T: View> ViewHandle<T> {
if predicate(
handle
.upgrade(ctx)
- .expect("model dropped with pending condition")
+ .expect("view dropped with pending condition")
.read(ctx),
ctx,
) {
@@ -2333,7 +2374,7 @@ impl<T: View> ViewHandle<T> {
rx.recv()
.await
- .expect("model dropped with pending condition");
+ .expect("view dropped with pending condition");
}
})
.await
@@ -2472,6 +2513,26 @@ impl Drop for AnyViewHandle {
}
}
+pub struct AnyModelHandle {
+ model_id: usize,
+ ref_counts: Arc<Mutex<RefCounts>>,
+}
+
+impl<T: Entity> From<ModelHandle<T>> for AnyModelHandle {
+ fn from(handle: ModelHandle<T>) -> Self {
+ handle.ref_counts.lock().inc_entity(handle.model_id);
+ Self {
+ model_id: handle.model_id,
+ ref_counts: handle.ref_counts.clone(),
+ }
+ }
+}
+
+impl Drop for AnyModelHandle {
+ fn drop(&mut self) {
+ self.ref_counts.lock().dec_model(self.model_id);
+ }
+}
pub struct WeakViewHandle<T> {
window_id: usize,
view_id: usize,
@@ -3576,9 +3637,7 @@ mod tests {
model.update(&mut app, |model, ctx| model.inc(ctx));
assert_eq!(poll_once(&mut condition2).await, Some(()));
- // Broadcast channel should be removed if no conditions remain on next notification.
model.update(&mut app, |_, ctx| ctx.notify());
- app.update(|ctx| assert!(ctx.async_observations.get(&model.id()).is_none()));
});
}
@@ -3656,10 +3715,7 @@ mod tests {
view.update(&mut app, |view, ctx| view.inc(ctx));
assert_eq!(poll_once(&mut condition2).await, Some(()));
-
- // Broadcast channel should be removed if no conditions remain on next notification.
view.update(&mut app, |_, ctx| ctx.notify());
- app.update(|ctx| assert!(ctx.async_observations.get(&view.id()).is_none()));
});
}
@@ -3689,7 +3745,7 @@ mod tests {
}
#[test]
- #[should_panic(expected = "model dropped with pending condition")]
+ #[should_panic(expected = "view dropped with pending condition")]
fn test_view_condition_panic_on_drop() {
struct View;
@@ -5,7 +5,7 @@ use cocoa::{
appkit::{
NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
- NSPasteboardTypeString, NSWindow,
+ NSPasteboardTypeString, NSSavePanel, NSWindow,
},
base::{id, nil, selector},
foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSURL},
@@ -25,7 +25,7 @@ use std::{
convert::TryInto,
ffi::{c_void, CStr},
os::raw::c_char,
- path::PathBuf,
+ path::{Path, PathBuf},
ptr,
rc::Rc,
slice, str,
@@ -305,6 +305,43 @@ impl platform::Platform for MacPlatform {
}
}
+ fn prompt_for_new_path(
+ &self,
+ directory: &Path,
+ done_fn: Box<dyn FnOnce(Option<std::path::PathBuf>)>,
+ ) {
+ unsafe {
+ let panel = NSSavePanel::savePanel(nil);
+ let path = ns_string(directory.to_string_lossy().as_ref());
+ let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
+ panel.setDirectoryURL(url);
+
+ let done_fn = Cell::new(Some(done_fn));
+ let block = ConcreteBlock::new(move |response: NSModalResponse| {
+ let result = if response == NSModalResponse::NSModalResponseOk {
+ let url = panel.URL();
+ let string = url.absoluteString();
+ let string = std::ffi::CStr::from_ptr(string.UTF8String())
+ .to_string_lossy()
+ .to_string();
+ if let Some(path) = string.strip_prefix("file://") {
+ Some(PathBuf::from(path))
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ if let Some(done_fn) = done_fn.take() {
+ (done_fn)(result);
+ }
+ });
+ let block = block.copy();
+ let _: () = msg_send![panel, beginWithCompletionHandler: block];
+ }
+ }
+
fn fonts(&self) -> Arc<dyn platform::FontSystem> {
self.fonts.clone()
}
@@ -19,7 +19,13 @@ use crate::{
};
use async_task::Runnable;
pub use event::Event;
-use std::{any::Any, ops::Range, path::PathBuf, rc::Rc, sync::Arc};
+use std::{
+ any::Any,
+ ops::Range,
+ path::{Path, PathBuf},
+ rc::Rc,
+ sync::Arc,
+};
pub trait Platform {
fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>);
@@ -45,6 +51,11 @@ pub trait Platform {
options: PathPromptOptions,
done_fn: Box<dyn FnOnce(Option<Vec<std::path::PathBuf>>)>,
);
+ fn prompt_for_new_path(
+ &self,
+ directory: &Path,
+ done_fn: Box<dyn FnOnce(Option<std::path::PathBuf>)>,
+ );
fn quit(&self);
fn write_to_clipboard(&self, item: ClipboardItem);
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
@@ -1,11 +1,18 @@
use crate::ClipboardItem;
use pathfinder_geometry::vector::Vector2F;
-use std::{any::Any, cell::RefCell, rc::Rc, sync::Arc};
-
-struct Platform {
+use std::{
+ any::Any,
+ cell::RefCell,
+ path::{Path, PathBuf},
+ rc::Rc,
+ sync::Arc,
+};
+
+pub(crate) struct Platform {
dispatcher: Arc<dyn super::Dispatcher>,
fonts: Arc<dyn super::FontSystem>,
current_clipboard_item: RefCell<Option<ClipboardItem>>,
+ last_prompt_for_new_path_args: RefCell<Option<(PathBuf, Box<dyn FnOnce(Option<PathBuf>)>)>>,
}
struct Dispatcher;
@@ -24,9 +31,25 @@ impl Platform {
Self {
dispatcher: Arc::new(Dispatcher),
fonts: Arc::new(super::current::FontSystem::new()),
- current_clipboard_item: RefCell::new(None),
+ current_clipboard_item: Default::default(),
+ last_prompt_for_new_path_args: Default::default(),
}
}
+
+ pub(crate) fn simulate_new_path_selection(
+ &self,
+ result: impl FnOnce(PathBuf) -> Option<PathBuf>,
+ ) {
+ let (dir_path, callback) = self
+ .last_prompt_for_new_path_args
+ .take()
+ .expect("prompt_for_new_path was not called");
+ callback(result(dir_path));
+ }
+
+ pub(crate) fn did_prompt_for_new_path(&self) -> bool {
+ self.last_prompt_for_new_path_args.borrow().is_some()
+ }
}
impl super::Platform for Platform {
@@ -78,6 +101,10 @@ impl super::Platform for Platform {
) {
}
+ fn prompt_for_new_path(&self, path: &Path, f: Box<dyn FnOnce(Option<std::path::PathBuf>)>) {
+ *self.last_prompt_for_new_path_args.borrow_mut() = Some((path.to_path_buf(), f));
+ }
+
fn write_to_clipboard(&self, item: ClipboardItem) {
*self.current_clipboard_item.borrow_mut() = Some(item);
}
@@ -138,6 +165,6 @@ impl super::Window for Window {
}
}
-pub fn platform() -> impl super::Platform {
+pub(crate) fn platform() -> Platform {
Platform::new()
}
@@ -8,6 +8,7 @@ use futures_core::future::LocalBoxFuture;
pub use point::*;
use seahash::SeaHasher;
pub use selection::*;
+use smol::future::FutureExt;
pub use text::*;
use crate::{
@@ -64,6 +65,7 @@ pub struct Buffer {
last_edit: time::Local,
undo_map: UndoMap,
history: History,
+ file: Option<FileHandle>,
selections: HashMap<SelectionSetId, Arc<[Selection]>>,
pub selections_last_update: SelectionsVersion,
deferred_ops: OperationQueue<Operation>,
@@ -351,15 +353,33 @@ pub struct UndoOperation {
}
impl Buffer {
- pub fn new<T: Into<Arc<str>>>(replica_id: ReplicaId, base_text: T) -> Self {
- Self::build(replica_id, History::new(base_text.into()))
+ pub fn new<T: Into<Arc<str>>>(
+ replica_id: ReplicaId,
+ base_text: T,
+ ctx: &mut ModelContext<Self>,
+ ) -> Self {
+ Self::build(replica_id, History::new(base_text.into()), None, ctx)
}
- pub fn from_history(replica_id: ReplicaId, history: History) -> Self {
- Self::build(replica_id, history)
+ pub fn from_history(
+ replica_id: ReplicaId,
+ history: History,
+ file: Option<FileHandle>,
+ ctx: &mut ModelContext<Self>,
+ ) -> Self {
+ Self::build(replica_id, history, file, ctx)
}
- fn build(replica_id: ReplicaId, history: History) -> Self {
+ fn build(
+ replica_id: ReplicaId,
+ history: History,
+ file: Option<FileHandle>,
+ ctx: &mut ModelContext<Self>,
+ ) -> Self {
+ if let Some(file) = file.as_ref() {
+ file.observe_from_model(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged));
+ }
+
let mut insertion_splits = HashMap::default();
let mut fragments = SumTree::new();
@@ -373,39 +393,48 @@ impl Buffer {
insertion_splits.insert(
base_insertion.id,
- SumTree::from_item(InsertionSplit {
- fragment_id: FragmentId::min_value().clone(),
- extent: 0,
- }),
+ SumTree::from_item(
+ InsertionSplit {
+ fragment_id: FragmentId::min_value().clone(),
+ extent: 0,
+ },
+ &(),
+ ),
+ );
+ fragments.push(
+ Fragment {
+ id: FragmentId::min_value().clone(),
+ insertion: base_insertion.clone(),
+ text: base_insertion.text.slice(0..0),
+ deletions: Default::default(),
+ max_undos: Default::default(),
+ visible: true,
+ },
+ &(),
);
- fragments.push(Fragment {
- id: FragmentId::min_value().clone(),
- insertion: base_insertion.clone(),
- text: base_insertion.text.slice(0..0),
- deletions: Default::default(),
- max_undos: Default::default(),
- visible: true,
- });
if base_insertion.text.len() > 0 {
let base_fragment_id =
FragmentId::between(&FragmentId::min_value(), &FragmentId::max_value());
- insertion_splits
- .get_mut(&base_insertion.id)
- .unwrap()
- .push(InsertionSplit {
+ insertion_splits.get_mut(&base_insertion.id).unwrap().push(
+ InsertionSplit {
fragment_id: base_fragment_id.clone(),
extent: base_insertion.text.len(),
- });
- fragments.push(Fragment {
- id: base_fragment_id,
- text: base_insertion.text.clone(),
- insertion: base_insertion,
- deletions: Default::default(),
- max_undos: Default::default(),
- visible: true,
- });
+ },
+ &(),
+ );
+ fragments.push(
+ Fragment {
+ id: base_fragment_id,
+ text: base_insertion.text.clone(),
+ insertion: base_insertion,
+ deletions: Default::default(),
+ max_undos: Default::default(),
+ visible: true,
+ },
+ &(),
+ );
}
Self {
@@ -416,6 +445,7 @@ impl Buffer {
last_edit: time::Local::default(),
undo_map: Default::default(),
history,
+ file,
selections: HashMap::default(),
selections_last_update: 0,
deferred_ops: OperationQueue::new(),
@@ -432,24 +462,40 @@ impl Buffer {
}
}
+ pub fn file(&self) -> Option<&FileHandle> {
+ self.file.as_ref()
+ }
+
pub fn save(
&mut self,
- file: &FileHandle,
+ new_file: Option<FileHandle>,
ctx: &mut ModelContext<Self>,
) -> LocalBoxFuture<'static, Result<()>> {
let snapshot = self.snapshot();
let version = self.version.clone();
- let save_task = file.save(snapshot, ctx.as_ref());
- let task = ctx.spawn(save_task, |me, save_result, ctx| {
- if save_result.is_ok() {
- me.did_save(version, ctx);
- }
- save_result
- });
- Box::pin(task)
+ if let Some(file) = new_file.as_ref().or(self.file.as_ref()) {
+ let save_task = file.save(snapshot, ctx.as_ref());
+ ctx.spawn(save_task, |me, save_result, ctx| {
+ if save_result.is_ok() {
+ me.did_save(version, new_file, ctx);
+ }
+ save_result
+ })
+ .boxed_local()
+ } else {
+ async { Ok(()) }.boxed_local()
+ }
}
- fn did_save(&mut self, version: time::Global, ctx: &mut ModelContext<Buffer>) {
+ fn did_save(
+ &mut self,
+ version: time::Global,
+ file: Option<FileHandle>,
+ ctx: &mut ModelContext<Buffer>,
+ ) {
+ if file.is_some() {
+ self.file = file;
+ }
self.saved_version = version;
ctx.emit(Event::Saved);
}
@@ -470,22 +516,22 @@ impl Buffer {
let mut summary = TextSummary::default();
let mut cursor = self.fragments.cursor::<usize, usize>();
- cursor.seek(&range.start, SeekBias::Right);
+ cursor.seek(&range.start, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += &fragment.text.slice(summary_start..summary_end).summary();
+ summary += fragment.text.slice(summary_start..summary_end).summary();
cursor.next();
}
if range.end > *cursor.start() {
- summary += &cursor.summary::<TextSummary>(&range.end, SeekBias::Right);
+ summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += &fragment.text.slice(summary_start..summary_end).summary();
+ summary += fragment.text.slice(summary_start..summary_end).summary();
}
}
@@ -515,22 +561,22 @@ impl Buffer {
let mut summary = TextSummary::default();
let mut cursor = self.fragments.cursor::<usize, usize>();
- cursor.seek(&range.start, SeekBias::Right);
+ cursor.seek(&range.start, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += &fragment.text.slice(summary_start..summary_end).summary();
+ summary += fragment.text.slice(summary_start..summary_end).summary();
cursor.next();
}
if range.end > *cursor.start() {
- summary += &cursor.summary::<TextSummary>(&range.end, SeekBias::Right);
+ summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
if let Some(fragment) = cursor.item() {
let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += &fragment.text.slice(summary_start..summary_end).summary();
+ summary += fragment.text.slice(summary_start..summary_end).summary();
}
}
@@ -933,10 +979,10 @@ impl Buffer {
let mut cursor = old_fragments.cursor::<FragmentIdRef, ()>();
let mut new_fragments =
- cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left);
+ cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
if start_offset == cursor.item().unwrap().end_offset() {
- new_fragments.push(cursor.item().unwrap().clone());
+ new_fragments.push(cursor.item().unwrap().clone(), &());
cursor.next();
}
@@ -975,30 +1021,33 @@ impl Buffer {
None
};
if let Some(fragment) = before_range {
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
}
if let Some(fragment) = insertion {
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
}
if let Some(mut fragment) = within_range {
if fragment.was_visible(&version_in_range, &self.undo_map) {
fragment.deletions.insert(local_timestamp);
fragment.visible = false;
}
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
}
if let Some(fragment) = after_range {
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
}
} else {
if new_text.is_some() && lamport_timestamp > fragment.insertion.lamport_timestamp {
- new_fragments.push(self.build_fragment_to_insert(
- cursor.prev_item().as_ref().unwrap(),
- Some(&fragment),
- new_text.take().unwrap(),
- local_timestamp,
- lamport_timestamp,
- ));
+ new_fragments.push(
+ self.build_fragment_to_insert(
+ cursor.prev_item().as_ref().unwrap(),
+ Some(&fragment),
+ new_text.take().unwrap(),
+ local_timestamp,
+ lamport_timestamp,
+ ),
+ &(),
+ );
}
if fragment.id < end_fragment_id
@@ -1007,23 +1056,26 @@ impl Buffer {
fragment.deletions.insert(local_timestamp);
fragment.visible = false;
}
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
}
cursor.next();
}
if let Some(new_text) = new_text {
- new_fragments.push(self.build_fragment_to_insert(
- cursor.prev_item().as_ref().unwrap(),
- None,
- new_text,
- local_timestamp,
- lamport_timestamp,
- ));
+ new_fragments.push(
+ self.build_fragment_to_insert(
+ cursor.prev_item().as_ref().unwrap(),
+ None,
+ new_text,
+ local_timestamp,
+ lamport_timestamp,
+ ),
+ &(),
+ );
}
- new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right));
+ new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right, &()), &());
self.fragments = new_fragments;
self.local_clock.observe(local_timestamp);
self.lamport_clock.observe(lamport_timestamp);
@@ -1111,23 +1163,26 @@ impl Buffer {
let mut insertion_splits = splits.cursor::<(), ()>().map(|s| &s.fragment_id).peekable();
let first_split_id = insertion_splits.next().unwrap();
- new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left);
+ new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left, &());
loop {
let mut fragment = cursor.item().unwrap().clone();
fragment.visible = fragment.is_visible(&self.undo_map);
fragment.max_undos.observe(undo.id);
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
cursor.next();
if let Some(split_id) = insertion_splits.next() {
- new_fragments
- .push_tree(cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left));
+ new_fragments.push_tree(
+ cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left, &()),
+ &(),
+ );
} else {
break;
}
}
} else {
- new_fragments = cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left);
+ new_fragments =
+ cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
while let Some(fragment) = cursor.item() {
if fragment.id > end_fragment_id {
break;
@@ -1139,13 +1194,13 @@ impl Buffer {
fragment.visible = fragment.is_visible(&self.undo_map);
fragment.max_undos.observe(undo.id);
}
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
cursor.next();
}
}
}
- new_fragments.push_tree(cursor.suffix());
+ new_fragments.push_tree(cursor.suffix(&()), &());
drop(cursor);
self.fragments = new_fragments;
@@ -1209,7 +1264,7 @@ impl Buffer {
.get(&edit_id)
.ok_or_else(|| anyhow!("invalid operation"))?;
let mut cursor = split_tree.cursor::<usize, ()>();
- cursor.seek(&offset, SeekBias::Left);
+ cursor.seek(&offset, SeekBias::Left, &());
Ok(cursor
.item()
.ok_or_else(|| anyhow!("invalid operation"))?
@@ -1231,7 +1286,10 @@ impl Buffer {
let old_fragments = self.fragments.clone();
let mut cursor = old_fragments.cursor::<usize, usize>();
let mut new_fragments = SumTree::new();
- new_fragments.push_tree(cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right));
+ new_fragments.push_tree(
+ cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
+ &(),
+ );
let mut start_id = None;
let mut start_offset = None;
@@ -1253,7 +1311,8 @@ impl Buffer {
.remove(&fragment.insertion.id)
.unwrap();
let mut splits_cursor = old_split_tree.cursor::<usize, ()>();
- let mut new_split_tree = splits_cursor.slice(&fragment.start_offset(), SeekBias::Right);
+ let mut new_split_tree =
+ splits_cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
// Find all splices that start or end within the current fragment. Then, split the
// fragment and reassemble it in both trees accounting for the deleted and the newly
@@ -1266,11 +1325,14 @@ impl Buffer {
prefix.id =
FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
fragment.set_start_offset(prefix.end_offset());
- new_fragments.push(prefix.clone());
- new_split_tree.push(InsertionSplit {
- extent: prefix.end_offset() - prefix.start_offset(),
- fragment_id: prefix.id,
- });
+ new_fragments.push(prefix.clone(), &());
+ new_split_tree.push(
+ InsertionSplit {
+ extent: prefix.end_offset() - prefix.start_offset(),
+ fragment_id: prefix.id,
+ },
+ &(),
+ );
fragment_start = range.start;
}
@@ -1294,7 +1356,7 @@ impl Buffer {
local_timestamp,
lamport_timestamp,
);
- new_fragments.push(new_fragment);
+ new_fragments.push(new_fragment, &());
}
}
@@ -1310,11 +1372,14 @@ impl Buffer {
prefix.visible = false;
}
fragment.set_start_offset(prefix.end_offset());
- new_fragments.push(prefix.clone());
- new_split_tree.push(InsertionSplit {
- extent: prefix.end_offset() - prefix.start_offset(),
- fragment_id: prefix.id,
- });
+ new_fragments.push(prefix.clone(), &());
+ new_split_tree.push(
+ InsertionSplit {
+ extent: prefix.end_offset() - prefix.start_offset(),
+ fragment_id: prefix.id,
+ },
+ &(),
+ );
fragment_start = range.end;
end_id = Some(fragment.insertion.id);
end_offset = Some(fragment.start_offset());
@@ -1358,16 +1423,21 @@ impl Buffer {
break;
}
}
- new_split_tree.push(InsertionSplit {
- extent: fragment.end_offset() - fragment.start_offset(),
- fragment_id: fragment.id.clone(),
- });
+ new_split_tree.push(
+ InsertionSplit {
+ extent: fragment.end_offset() - fragment.start_offset(),
+ fragment_id: fragment.id.clone(),
+ },
+ &(),
+ );
splits_cursor.next();
- new_split_tree
- .push_tree(splits_cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right));
+ new_split_tree.push_tree(
+ splits_cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right, &()),
+ &(),
+ );
self.insertion_splits
.insert(fragment.insertion.id, new_split_tree);
- new_fragments.push(fragment);
+ new_fragments.push(fragment, &());
// Scan forward until we find a fragment that is not fully contained by the current splice.
cursor.next();
@@ -1383,7 +1453,7 @@ impl Buffer {
new_fragment.deletions.insert(local_timestamp);
new_fragment.visible = false;
}
- new_fragments.push(new_fragment);
+ new_fragments.push(new_fragment, &());
cursor.next();
if range.end == fragment_end {
@@ -1425,7 +1495,8 @@ impl Buffer {
// and push all the fragments in between into the new tree.
if cur_range.as_ref().map_or(false, |r| r.start > fragment_end) {
new_fragments.push_tree(
- cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right),
+ cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
+ &(),
);
}
}
@@ -1457,11 +1528,13 @@ impl Buffer {
local_timestamp,
lamport_timestamp,
);
- new_fragments.push(new_fragment);
+ new_fragments.push(new_fragment, &());
}
} else {
- new_fragments
- .push_tree(cursor.slice(&old_fragments.extent::<usize>(), SeekBias::Right));
+ new_fragments.push_tree(
+ cursor.slice(&old_fragments.extent::<usize>(), SeekBias::Right, &()),
+ &(),
+ );
}
self.fragments = new_fragments;
@@ -1519,32 +1592,43 @@ impl Buffer {
.remove(&fragment.insertion.id)
.unwrap();
let mut cursor = old_split_tree.cursor::<usize, ()>();
- let mut new_split_tree = cursor.slice(&fragment.start_offset(), SeekBias::Right);
+ let mut new_split_tree = cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
if let Some(ref fragment) = before_range {
- new_split_tree.push(InsertionSplit {
- extent: range.start - fragment.start_offset(),
- fragment_id: fragment.id.clone(),
- });
+ new_split_tree.push(
+ InsertionSplit {
+ extent: range.start - fragment.start_offset(),
+ fragment_id: fragment.id.clone(),
+ },
+ &(),
+ );
}
if let Some(ref fragment) = within_range {
- new_split_tree.push(InsertionSplit {
- extent: range.end - range.start,
- fragment_id: fragment.id.clone(),
- });
+ new_split_tree.push(
+ InsertionSplit {
+ extent: range.end - range.start,
+ fragment_id: fragment.id.clone(),
+ },
+ &(),
+ );
}
if let Some(ref fragment) = after_range {
- new_split_tree.push(InsertionSplit {
- extent: fragment.end_offset() - range.end,
- fragment_id: fragment.id.clone(),
- });
+ new_split_tree.push(
+ InsertionSplit {
+ extent: fragment.end_offset() - range.end,
+ fragment_id: fragment.id.clone(),
+ },
+ &(),
+ );
}
cursor.next();
- new_split_tree
- .push_tree(cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right));
+ new_split_tree.push_tree(
+ cursor.slice(&old_split_tree.extent::<usize>(), SeekBias::Right, &()),
+ &(),
+ );
self.insertion_splits
.insert(fragment.insertion.id, new_split_tree);
@@ -1569,10 +1653,13 @@ impl Buffer {
);
let mut split_tree = SumTree::new();
- split_tree.push(InsertionSplit {
- extent: text.len(),
- fragment_id: new_fragment_id.clone(),
- });
+ split_tree.push(
+ InsertionSplit {
+ extent: text.len(),
+ fragment_id: new_fragment_id.clone(),
+ },
+ &(),
+ );
self.insertion_splits.insert(local_timestamp, split_tree);
Fragment::new(
@@ -1621,7 +1708,7 @@ impl Buffer {
};
let mut cursor = self.fragments.cursor::<usize, usize>();
- cursor.seek(&offset, seek_bias);
+ cursor.seek(&offset, seek_bias, &());
let fragment = cursor.item().unwrap();
let offset_in_fragment = offset - cursor.start();
let offset_in_insertion = fragment.start_offset() + offset_in_fragment;
@@ -1653,7 +1740,7 @@ impl Buffer {
.get(&insertion_id)
.ok_or_else(|| anyhow!("split does not exist for insertion id"))?;
let mut splits_cursor = splits.cursor::<usize, ()>();
- splits_cursor.seek(offset, seek_bias);
+ splits_cursor.seek(offset, seek_bias, &());
splits_cursor
.item()
.ok_or_else(|| anyhow!("split offset is out of range"))
@@ -1681,13 +1768,13 @@ impl Buffer {
.get(&insertion_id)
.ok_or_else(|| anyhow!("split does not exist for insertion id"))?;
let mut splits_cursor = splits.cursor::<usize, ()>();
- splits_cursor.seek(offset, seek_bias);
+ splits_cursor.seek(offset, seek_bias, &());
let split = splits_cursor
.item()
.ok_or_else(|| anyhow!("split offset is out of range"))?;
let mut fragments_cursor = self.fragments.cursor::<FragmentIdRef, TextSummary>();
- fragments_cursor.seek(&FragmentIdRef::new(&split.fragment_id), SeekBias::Left);
+ fragments_cursor.seek(&FragmentIdRef::new(&split.fragment_id), SeekBias::Left, &());
let fragment = fragments_cursor
.item()
.ok_or_else(|| anyhow!("fragment id does not exist"))?;
@@ -1707,7 +1794,7 @@ impl Buffer {
#[allow(dead_code)]
pub fn point_for_offset(&self, offset: usize) -> Result<Point> {
let mut fragments_cursor = self.fragments.cursor::<usize, TextSummary>();
- fragments_cursor.seek(&offset, SeekBias::Left);
+ fragments_cursor.seek(&offset, SeekBias::Left, &());
fragments_cursor
.item()
.ok_or_else(|| anyhow!("offset is out of range"))
@@ -1733,6 +1820,7 @@ impl Clone for Buffer {
selections: self.selections.clone(),
selections_last_update: self.selections_last_update.clone(),
deferred_ops: self.deferred_ops.clone(),
+ file: self.file.clone(),
deferred_replicas: self.deferred_replicas.clone(),
replica_id: self.replica_id,
local_clock: self.local_clock.clone(),
@@ -1772,7 +1860,7 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Point {
impl<'a> CharIter<'a> {
fn new(fragments: &'a SumTree<Fragment>, offset: usize) -> Self {
let mut fragments_cursor = fragments.cursor::<usize, usize>();
- fragments_cursor.seek(&offset, SeekBias::Right);
+ fragments_cursor.seek(&offset, SeekBias::Right, &());
let fragment_chars = fragments_cursor.item().map_or("".chars(), |fragment| {
let offset_in_fragment = offset - fragments_cursor.start();
fragment.text[offset_in_fragment..].chars()
@@ -1809,7 +1897,7 @@ impl<'a> Iterator for CharIter<'a> {
impl<'a> FragmentIter<'a> {
fn new(fragments: &'a SumTree<Fragment>) -> Self {
let mut cursor = fragments.cursor::<usize, usize>();
- cursor.seek(&0, SeekBias::Right);
+ cursor.seek(&0, SeekBias::Right, &());
Self {
cursor,
started: false,
@@ -2102,8 +2190,10 @@ impl sum_tree::Item for Fragment {
}
}
-impl<'a> AddAssign<&'a FragmentSummary> for FragmentSummary {
- fn add_assign(&mut self, other: &Self) {
+impl sum_tree::Summary for FragmentSummary {
+ type Context = ();
+
+ fn add_summary(&mut self, other: &Self, _: &()) {
self.text_summary += &other.text_summary;
debug_assert!(self.max_fragment_id <= other.max_fragment_id);
self.max_fragment_id = other.max_fragment_id.clone();
@@ -2166,8 +2256,10 @@ impl sum_tree::Item for InsertionSplit {
}
}
-impl<'a> AddAssign<&'a InsertionSplitSummary> for InsertionSplitSummary {
- fn add_assign(&mut self, other: &Self) {
+impl sum_tree::Summary for InsertionSplitSummary {
+ type Context = ();
+
+ fn add_summary(&mut self, other: &Self, _: &()) {
self.extent += other.extent;
}
}
@@ -2224,7 +2316,7 @@ pub trait ToOffset {
impl ToOffset for Point {
fn to_offset(&self, buffer: &Buffer) -> Result<usize> {
let mut fragments_cursor = buffer.fragments.cursor::<Point, TextSummary>();
- fragments_cursor.seek(self, SeekBias::Left);
+ fragments_cursor.seek(self, SeekBias::Left, &());
fragments_cursor
.item()
.ok_or_else(|| anyhow!("point is out of range"))
@@ -2268,7 +2360,7 @@ impl ToPoint for Anchor {
impl ToPoint for usize {
fn to_point(&self, buffer: &Buffer) -> Result<Point> {
let mut fragments_cursor = buffer.fragments.cursor::<usize, TextSummary>();
- fragments_cursor.seek(&self, SeekBias::Left);
+ fragments_cursor.seek(&self, SeekBias::Left, &());
fragments_cursor
.item()
.ok_or_else(|| anyhow!("offset is out of range"))
@@ -2292,8 +2384,8 @@ mod tests {
#[test]
fn test_edit() {
App::test((), |ctx| {
- ctx.add_model(|_| {
- let mut buffer = Buffer::new(0, "abc");
+ ctx.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, "abc", ctx);
assert_eq!(buffer.text(), "abc");
buffer.edit(vec![3..3], "def", None).unwrap();
assert_eq!(buffer.text(), "abcdef");
@@ -2317,8 +2409,8 @@ mod tests {
let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
- let buffer1 = app.add_model(|_| Buffer::new(0, "abcdef"));
- let buffer2 = app.add_model(|_| Buffer::new(1, "abcdef"));
+ let buffer1 = app.add_model(|ctx| Buffer::new(0, "abcdef", ctx));
+ let buffer2 = app.add_model(|ctx| Buffer::new(1, "abcdef", ctx));
let mut buffer_ops = Vec::new();
buffer1.update(app, |buffer, ctx| {
let buffer_1_events = buffer_1_events.clone();
@@ -2381,8 +2473,8 @@ mod tests {
let mut reference_string = RandomCharIter::new(&mut rng)
.take(reference_string_len)
.collect::<String>();
- ctx.add_model(|_| {
- let mut buffer = Buffer::new(0, reference_string.as_str());
+ ctx.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, reference_string.as_str(), ctx);
let mut buffer_versions = Vec::new();
for _i in 0..10 {
let (old_ranges, new_text, _) = buffer.randomly_mutate(rng, None);
@@ -2467,8 +2559,8 @@ mod tests {
#[test]
fn test_line_len() {
App::test((), |ctx| {
- ctx.add_model(|_| {
- let mut buffer = Buffer::new(0, "");
+ ctx.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap();
buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
buffer.edit(vec![18..18], "\npqrs\n", None).unwrap();
@@ -2489,8 +2581,8 @@ mod tests {
#[test]
fn test_rightmost_point() {
App::test((), |ctx| {
- ctx.add_model(|_| {
- let mut buffer = Buffer::new(0, "");
+ ctx.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, "", ctx);
assert_eq!(buffer.rightmost_point().row, 0);
buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap();
assert_eq!(buffer.rightmost_point().row, 0);
@@ -2510,8 +2602,8 @@ mod tests {
#[test]
fn test_text_summary_for_range() {
App::test((), |ctx| {
- ctx.add_model(|_| {
- let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz");
+ ctx.add_model(|ctx| {
+ let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx);
let text = Text::from(buffer.text());
assert_eq!(
buffer.text_summary_for_range(1..3),
@@ -2541,8 +2633,8 @@ mod tests {
#[test]
fn test_chars_at() {
App::test((), |ctx| {
- ctx.add_model(|_| {
- let mut buffer = Buffer::new(0, "");
+ ctx.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap();
buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
buffer.edit(vec![18..18], "\npqrs", None).unwrap();
@@ -2564,7 +2656,7 @@ mod tests {
assert_eq!(chars.collect::<String>(), "PQrs");
// Regression test:
- let mut buffer = Buffer::new(0, "");
+ let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", None).unwrap();
buffer.edit(vec![60..60], "\n", None).unwrap();
@@ -2693,8 +2785,8 @@ mod tests {
#[test]
fn test_anchors() {
App::test((), |ctx| {
- ctx.add_model(|_| {
- let mut buffer = Buffer::new(0, "");
+ ctx.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abc", None).unwrap();
let left_anchor = buffer.anchor_before(2).unwrap();
let right_anchor = buffer.anchor_after(2).unwrap();
@@ -2858,8 +2950,8 @@ mod tests {
#[test]
fn test_anchors_at_start_and_end() {
App::test((), |ctx| {
- ctx.add_model(|_| {
- let mut buffer = Buffer::new(0, "");
+ ctx.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, "", ctx);
let before_start_anchor = buffer.anchor_before(0).unwrap();
let after_end_anchor = buffer.anchor_after(0).unwrap();
@@ -2886,7 +2978,7 @@ mod tests {
#[test]
fn test_is_modified() {
App::test((), |app| {
- let model = app.add_model(|_| Buffer::new(0, "abc"));
+ let model = app.add_model(|ctx| Buffer::new(0, "abc", ctx));
let events = Rc::new(RefCell::new(Vec::new()));
// initially, the buffer isn't dirty.
@@ -2909,7 +3001,7 @@ mod tests {
assert_eq!(*events.borrow(), &[Event::Edited, Event::Dirtied]);
events.borrow_mut().clear();
- buffer.did_save(buffer.version(), ctx);
+ buffer.did_save(buffer.version(), None, ctx);
});
// after saving, the buffer is not dirty, and emits a saved event.
@@ -2948,8 +3040,8 @@ mod tests {
#[test]
fn test_undo_redo() {
App::test((), |app| {
- app.add_model(|_| {
- let mut buffer = Buffer::new(0, "1234");
+ app.add_model(|ctx| {
+ let mut buffer = Buffer::new(0, "1234", ctx);
let edit1 = buffer.edit(vec![1..1], "abx", None).unwrap();
let edit2 = buffer.edit(vec![3..4], "yzef", None).unwrap();
@@ -2985,9 +3077,9 @@ mod tests {
#[test]
fn test_history() {
App::test((), |app| {
- app.add_model(|_| {
+ app.add_model(|ctx| {
let mut now = Instant::now();
- let mut buffer = Buffer::new(0, "123456");
+ let mut buffer = Buffer::new(0, "123456", ctx);
let (set_id, _) = buffer
.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None);
@@ -3072,7 +3164,7 @@ mod tests {
let mut network = Network::new();
for i in 0..PEERS {
let buffer =
- ctx.add_model(|_| Buffer::new(i as ReplicaId, base_text.as_str()));
+ ctx.add_model(|ctx| Buffer::new(i as ReplicaId, base_text.as_str(), ctx));
buffers.push(buffer);
replica_ids.push(i as u16);
network.add_peer(i as u16);
@@ -75,6 +75,7 @@ impl Selection {
pub fn buffer_rows_for_display_rows(
&self,
+ include_end_if_at_line_start: bool,
map: &DisplayMap,
ctx: &AppContext,
) -> (Range<u32>, Range<u32>) {
@@ -84,7 +85,8 @@ impl Selection {
.unwrap();
let mut display_end = self.end.to_display_point(map, ctx).unwrap();
- if display_end.row() != map.max_point(ctx).row()
+ if !include_end_if_at_line_start
+ && display_end.row() != map.max_point(ctx).row()
&& display_start.row() != display_end.row()
&& display_end.column() == 0
{
@@ -58,6 +58,14 @@ pub struct TextSummary {
pub rightmost_point: Point,
}
+impl sum_tree::Summary for TextSummary {
+ type Context = ();
+
+ fn add_summary(&mut self, other: &Self, _: &()) {
+ *self += other;
+ }
+}
+
impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
fn add_assign(&mut self, other: &'a Self) {
let joined_line_len = self.lines.column + other.first_line_len;
@@ -85,8 +93,8 @@ impl std::ops::AddAssign<Self> for TextSummary {
}
impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary {
- fn add_summary(&mut self, summary: &TextSummary) {
- *self += summary;
+ fn add_summary(&mut self, other: &TextSummary) {
+ *self += other;
}
}
@@ -157,7 +165,7 @@ impl From<Arc<str>> for Text {
}
let mut tree = SumTree::new();
- tree.extend(runs);
+ tree.extend(runs, &());
Text {
text,
runs: tree,
@@ -231,13 +239,14 @@ impl Text {
pub fn line_len(&self, row: u32) -> u32 {
let mut cursor = self.runs.cursor::<usize, Point>();
- cursor.seek(&self.range.start, SeekBias::Right);
+ cursor.seek(&self.range.start, SeekBias::Right, &());
let absolute_row = cursor.start().row + row;
let mut cursor = self.runs.cursor::<Point, usize>();
- cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right);
+ cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right, &());
let prefix_len = self.range.start.saturating_sub(*cursor.start());
- let line_len = cursor.summary::<usize>(&Point::new(absolute_row + 1, 0), SeekBias::Left);
+ let line_len =
+ cursor.summary::<usize>(&Point::new(absolute_row + 1, 0), SeekBias::Left, &());
let suffix_len = cursor.start().saturating_sub(self.range.end);
line_len
@@ -262,14 +271,15 @@ impl Text {
candidates.push(Point::new(0, self.line_len(0)));
if lines.row > 1 {
let mut cursor = self.runs.cursor::<usize, Point>();
- cursor.seek(&self.range.start, SeekBias::Right);
+ cursor.seek(&self.range.start, SeekBias::Right, &());
let absolute_start_row = cursor.start().row;
let mut cursor = self.runs.cursor::<Point, usize>();
- cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right);
+ cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right, &());
let summary = cursor.summary::<TextSummary>(
&Point::new(absolute_start_row + lines.row, 0),
SeekBias::Left,
+ &(),
);
candidates.push(Point::new(1, 0) + &summary.rightmost_point);
@@ -287,7 +297,7 @@ impl Text {
pub fn offset_for_point(&self, point: Point) -> usize {
let mut cursor = self.runs.cursor::<Point, TextSummary>();
let abs_point = self.abs_point_for_offset(self.range.start) + &point;
- cursor.seek(&abs_point, SeekBias::Right);
+ cursor.seek(&abs_point, SeekBias::Right, &());
let overshoot = abs_point - &cursor.start().lines;
let abs_offset = cursor.start().chars + overshoot.column as usize;
abs_offset - self.range.start
@@ -307,14 +317,14 @@ impl Text {
fn abs_point_for_offset(&self, offset: usize) -> Point {
let mut cursor = self.runs.cursor::<usize, TextSummary>();
- cursor.seek(&offset, SeekBias::Right);
+ cursor.seek(&offset, SeekBias::Right, &());
let overshoot = (offset - cursor.start().chars) as u32;
cursor.start().lines + &Point::new(0, overshoot)
}
fn abs_byte_offset_for_offset(&self, offset: usize) -> usize {
let mut cursor = self.runs.cursor::<usize, TextSummary>();
- cursor.seek(&offset, SeekBias::Right);
+ cursor.seek(&offset, SeekBias::Right, &());
let overshoot = offset - cursor.start().chars;
cursor.start().bytes + overshoot * cursor.item().map_or(0, |run| run.char_size()) as usize
}
@@ -6,11 +6,10 @@ use crate::{settings::Settings, workspace, worktree::FileHandle};
use anyhow::Result;
use futures_core::future::LocalBoxFuture;
use gpui::{
- fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, ClipboardItem,
- Element, ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
- WeakViewHandle,
+ fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout,
+ AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle,
+ MutableAppContext, TextLayoutCache, View, ViewContext, WeakViewHandle,
};
-use gpui::{geometry::vector::Vector2F, TextLayoutCache};
use parking_lot::Mutex;
use postage::watch;
use serde::{Deserialize, Serialize};
@@ -69,6 +68,10 @@ pub fn init(app: &mut MutableAppContext) {
Binding::new("down", "buffer:move_down", Some("BufferView")),
Binding::new("left", "buffer:move_left", Some("BufferView")),
Binding::new("right", "buffer:move_right", Some("BufferView")),
+ Binding::new("ctrl-p", "buffer:move_up", Some("BufferView")),
+ Binding::new("ctrl-n", "buffer:move_down", Some("BufferView")),
+ Binding::new("ctrl-b", "buffer:move_left", Some("BufferView")),
+ Binding::new("ctrl-f", "buffer:move_right", Some("BufferView")),
Binding::new(
"alt-left",
"buffer:move_to_previous_word_boundary",
@@ -140,6 +143,12 @@ pub fn init(app: &mut MutableAppContext) {
),
Binding::new("cmd-shift-down", "buffer:select_to_end", Some("BufferView")),
Binding::new("cmd-a", "buffer:select_all", Some("BufferView")),
+ Binding::new("cmd-l", "buffer:select_line", Some("BufferView")),
+ Binding::new(
+ "cmd-shift-L",
+ "buffer:split_selection_into_lines",
+ Some("BufferView"),
+ ),
Binding::new("pageup", "buffer:page_up", Some("BufferView")),
Binding::new("pagedown", "buffer:page_down", Some("BufferView")),
Binding::new("alt-cmd-[", "buffer:fold", Some("BufferView")),
@@ -230,6 +239,11 @@ pub fn init(app: &mut MutableAppContext) {
);
app.add_action("buffer:select_to_end", BufferView::select_to_end);
app.add_action("buffer:select_all", BufferView::select_all);
+ app.add_action("buffer:select_line", BufferView::select_line);
+ app.add_action(
+ "buffer:split_selection_into_lines",
+ BufferView::split_selection_into_lines,
+ );
app.add_action("buffer:page_up", BufferView::page_up);
app.add_action("buffer:page_down", BufferView::page_down);
app.add_action("buffer:fold", BufferView::fold);
@@ -255,7 +269,6 @@ pub enum SelectAction {
pub struct BufferView {
handle: WeakViewHandle<Self>,
buffer: ModelHandle<Buffer>,
- file: Option<FileHandle>,
display_map: DisplayMap,
selection_set_id: SelectionSetId,
pending_selection: Option<Selection>,
@@ -277,22 +290,17 @@ struct ClipboardSelection {
impl BufferView {
pub fn single_line(settings: watch::Receiver<Settings>, ctx: &mut ViewContext<Self>) -> Self {
- let buffer = ctx.add_model(|_| Buffer::new(0, String::new()));
- let mut view = Self::for_buffer(buffer, None, settings, ctx);
+ let buffer = ctx.add_model(|ctx| Buffer::new(0, String::new(), ctx));
+ let mut view = Self::for_buffer(buffer, settings, ctx);
view.single_line = true;
view
}
pub fn for_buffer(
buffer: ModelHandle<Buffer>,
- file: Option<FileHandle>,
settings: watch::Receiver<Settings>,
ctx: &mut ViewContext<Self>,
) -> Self {
- if let Some(file) = file.as_ref() {
- file.observe_from_view(ctx, |_, _, ctx| ctx.emit(Event::FileHandleChanged));
- }
-
ctx.observe_model(&buffer, Self::on_buffer_changed);
ctx.subscribe_to_model(&buffer, Self::on_buffer_event);
let display_map = DisplayMap::new(buffer.clone(), settings.borrow().tab_size, ctx.as_ref());
@@ -311,7 +319,6 @@ impl BufferView {
Self {
handle: ctx.handle().downgrade(),
buffer,
- file,
display_map,
selection_set_id,
pending_selection: None,
@@ -707,7 +714,8 @@ impl BufferView {
let mut selections = self.selections(app).iter().peekable();
while let Some(selection) = selections.next() {
- let (mut rows, _) = selection.buffer_rows_for_display_rows(&self.display_map, app);
+ let (mut rows, _) =
+ selection.buffer_rows_for_display_rows(false, &self.display_map, app);
let goal_display_column = selection
.head()
.to_display_point(&self.display_map, app)
@@ -717,7 +725,7 @@ impl BufferView {
// Accumulate contiguous regions of rows that we want to delete.
while let Some(next_selection) = selections.peek() {
let (next_rows, _) =
- next_selection.buffer_rows_for_display_rows(&self.display_map, app);
+ next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_rows.start <= rows.end {
rows.end = next_rows.end;
selections.next().unwrap();
@@ -798,10 +806,11 @@ impl BufferView {
let mut selections_iter = selections.iter_mut().peekable();
while let Some(selection) = selections_iter.next() {
// Avoid duplicating the same lines twice.
- let (mut rows, _) = selection.buffer_rows_for_display_rows(&self.display_map, app);
+ let (mut rows, _) =
+ selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections_iter.peek() {
let (next_rows, _) =
- next_selection.buffer_rows_for_display_rows(&self.display_map, app);
+ next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_rows.start <= rows.end - 1 {
rows.end = next_rows.end;
selections_iter.next().unwrap();
@@ -855,10 +864,10 @@ impl BufferView {
// Accumulate contiguous regions of rows that we want to move.
contiguous_selections.push(selection.range(buffer));
let (mut buffer_rows, mut display_rows) =
- selection.buffer_rows_for_display_rows(&self.display_map, app);
+ selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections.peek() {
let (next_buffer_rows, next_display_rows) =
- next_selection.buffer_rows_for_display_rows(&self.display_map, app);
+ next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_buffer_rows.start <= buffer_rows.end {
buffer_rows.end = next_buffer_rows.end;
display_rows.end = next_display_rows.end;
@@ -945,10 +954,10 @@ impl BufferView {
// Accumulate contiguous regions of rows that we want to move.
contiguous_selections.push(selection.range(buffer));
let (mut buffer_rows, mut display_rows) =
- selection.buffer_rows_for_display_rows(&self.display_map, app);
+ selection.buffer_rows_for_display_rows(false, &self.display_map, app);
while let Some(next_selection) = selections.peek() {
let (next_buffer_rows, next_display_rows) =
- next_selection.buffer_rows_for_display_rows(&self.display_map, app);
+ next_selection.buffer_rows_for_display_rows(false, &self.display_map, app);
if next_buffer_rows.start <= buffer_rows.end {
buffer_rows.end = next_buffer_rows.end;
display_rows.end = next_display_rows.end;
@@ -1657,6 +1666,63 @@ impl BufferView {
self.update_selections(vec![selection], false, ctx);
}
+ pub fn select_line(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+ let app = ctx.as_ref();
+ let buffer = self.buffer.read(app);
+ let mut selections = self.selections(app).to_vec();
+ let max_point = buffer.max_point();
+ for selection in &mut selections {
+ let (rows, _) = selection.buffer_rows_for_display_rows(true, &self.display_map, app);
+ selection.start = buffer.anchor_before(Point::new(rows.start, 0)).unwrap();
+ selection.end = buffer
+ .anchor_before(cmp::min(max_point, Point::new(rows.end, 0)))
+ .unwrap();
+ selection.reversed = false;
+ }
+ self.update_selections(selections, true, ctx);
+ }
+
+ pub fn split_selection_into_lines(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+ use super::RangeExt;
+
+ let app = ctx.as_ref();
+ let buffer = self.buffer.read(app);
+
+ let mut to_unfold = Vec::new();
+ let mut new_selections = Vec::new();
+ for selection in self.selections(app) {
+ let range = selection.range(buffer).sorted();
+ if range.start.row != range.end.row {
+ new_selections.push(Selection {
+ start: selection.start.clone(),
+ end: selection.start.clone(),
+ reversed: false,
+ goal_column: None,
+ });
+ }
+ for row in range.start.row + 1..range.end.row {
+ let cursor = buffer
+ .anchor_before(Point::new(row, buffer.line_len(row).unwrap()))
+ .unwrap();
+ new_selections.push(Selection {
+ start: cursor.clone(),
+ end: cursor,
+ reversed: false,
+ goal_column: None,
+ });
+ }
+ new_selections.push(Selection {
+ start: selection.end.clone(),
+ end: selection.end.clone(),
+ reversed: false,
+ goal_column: None,
+ });
+ to_unfold.push(range);
+ }
+ self.unfold_ranges(to_unfold, ctx);
+ self.update_selections(new_selections, true, ctx);
+ }
+
pub fn selections_in_range<'a>(
&'a self,
range: Range<DisplayPoint>,
@@ -1877,9 +1943,8 @@ impl BufferView {
.selections(ctx.as_ref())
.iter()
.map(|s| s.range(buffer).sorted())
- .collect::<Vec<_>>();
- self.display_map.fold(ranges, ctx.as_ref()).unwrap();
- ctx.notify();
+ .collect();
+ self.fold_ranges(ranges, ctx);
}
fn fold_ranges<T: ToOffset>(&mut self, ranges: Vec<Range<T>>, ctx: &mut ViewContext<Self>) {
@@ -2177,6 +2242,22 @@ impl View for BufferView {
}
}
+impl workspace::Item for Buffer {
+ type View = BufferView;
+
+ fn file(&self) -> Option<&FileHandle> {
+ self.file()
+ }
+
+ fn build_view(
+ handle: ModelHandle<Self>,
+ settings: watch::Receiver<Settings>,
+ ctx: &mut ViewContext<Self::View>,
+ ) -> Self::View {
+ BufferView::for_buffer(handle, settings, ctx)
+ }
+}
+
impl workspace::ItemView for BufferView {
fn should_activate_item_on_event(event: &Self::Event) -> bool {
matches!(event, Event::Activate)
@@ -2190,7 +2271,11 @@ impl workspace::ItemView for BufferView {
}
fn title(&self, app: &AppContext) -> std::string::String {
- let filename = self.file.as_ref().and_then(|file| file.file_name(app));
+ let filename = self
+ .buffer
+ .read(app)
+ .file()
+ .and_then(|file| file.file_name(app));
if let Some(name) = filename {
name.to_string_lossy().into()
} else {
@@ -2198,31 +2283,25 @@ impl workspace::ItemView for BufferView {
}
}
- fn entry_id(&self, _: &AppContext) -> Option<(usize, Arc<Path>)> {
- self.file.as_ref().map(|file| file.entry_id())
+ fn entry_id(&self, ctx: &AppContext) -> Option<(usize, Arc<Path>)> {
+ self.buffer.read(ctx).file().map(|file| file.entry_id())
}
fn clone_on_split(&self, ctx: &mut ViewContext<Self>) -> Option<Self>
where
Self: Sized,
{
- let clone = BufferView::for_buffer(
- self.buffer.clone(),
- self.file.clone(),
- self.settings.clone(),
- ctx,
- );
+ let clone = BufferView::for_buffer(self.buffer.clone(), self.settings.clone(), ctx);
*clone.scroll_position.lock() = *self.scroll_position.lock();
Some(clone)
}
- fn save(&self, ctx: &mut ViewContext<Self>) -> LocalBoxFuture<'static, Result<()>> {
- if let Some(file) = self.file.as_ref() {
- self.buffer
- .update(ctx, |buffer, ctx| buffer.save(file, ctx))
- } else {
- Box::pin(async { Ok(()) })
- }
+ fn save(
+ &mut self,
+ new_file: Option<FileHandle>,
+ ctx: &mut ViewContext<Self>,
+ ) -> LocalBoxFuture<'static, Result<()>> {
+ self.buffer.update(ctx, |b, ctx| b.save(new_file, ctx))
}
fn is_dirty(&self, ctx: &AppContext) -> bool {
@@ -2240,10 +2319,11 @@ mod tests {
#[test]
fn test_selection_with_mouse() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n"));
+ let buffer =
+ app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, buffer_view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
buffer_view.update(app, |view, ctx| {
view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
@@ -2354,11 +2434,11 @@ mod tests {
let layout_cache = TextLayoutCache::new(app.platform().fonts());
let font_cache = app.font_cache().clone();
- let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx));
let settings = settings::channel(&font_cache).unwrap().1;
let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
+ app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
let layouts = view
.read(app)
@@ -2371,7 +2451,7 @@ mod tests {
#[test]
fn test_fold() {
App::test((), |app| {
- let buffer = app.add_model(|_| {
+ let buffer = app.add_model(|ctx| {
Buffer::new(
0,
"
@@ -2392,11 +2472,12 @@ mod tests {
}
"
.unindent(),
+ ctx,
)
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
+ app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
@@ -2465,10 +2546,10 @@ mod tests {
#[test]
fn test_move_cursor() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, sample_text(6, 6)));
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
+ app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
buffer.update(app, |buffer, ctx| {
buffer
@@ -2543,10 +2624,9 @@ mod tests {
#[test]
fn test_beginning_end_of_line() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, "abc\n def"));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abc\n def", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -2672,11 +2752,10 @@ mod tests {
#[test]
fn test_prev_next_word_boundary() {
App::test((), |app| {
- let buffer =
- app.add_model(|_| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}"));
+ let buffer = app
+ .add_model(|ctx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -2855,12 +2934,16 @@ mod tests {
#[test]
fn test_backspace() {
App::test((), |app| {
- let buffer = app.add_model(|_| {
- Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
+ let buffer = app.add_model(|ctx| {
+ Buffer::new(
+ 0,
+ "one two three\nfour five six\nseven eight nine\nten\n",
+ ctx,
+ )
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
+ app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
@@ -2888,12 +2971,16 @@ mod tests {
#[test]
fn test_delete() {
App::test((), |app| {
- let buffer = app.add_model(|_| {
- Buffer::new(0, "one two three\nfour five six\nseven eight nine\nten\n")
+ let buffer = app.add_model(|ctx| {
+ Buffer::new(
+ 0,
+ "one two three\nfour five six\nseven eight nine\nten\n",
+ ctx,
+ )
});
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx));
+ app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
@@ -2922,9 +3009,8 @@ mod tests {
fn test_delete_line() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -2947,9 +3033,8 @@ mod tests {
);
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)],
@@ -2970,9 +3055,8 @@ mod tests {
fn test_duplicate_line() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -3001,9 +3085,8 @@ mod tests {
);
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let buffer = app.add_model(|_| Buffer::new(0, "abc\ndef\nghi\n"));
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.select_display_ranges(
&[
@@ -3033,9 +3116,8 @@ mod tests {
fn test_move_line_up_down() {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let buffer = app.add_model(|_| Buffer::new(0, sample_text(10, 5)));
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(10, 5), ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| {
view.fold_ranges(
vec![
@@ -3126,10 +3208,10 @@ mod tests {
#[test]
fn test_clipboard() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, "one two three four five six "));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "one two three four five six ", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
let view = app
- .add_window(|ctx| BufferView::for_buffer(buffer.clone(), None, settings, ctx))
+ .add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx))
.1;
// Cut with three selections. Clipboard text is divided into three slices.
@@ -3267,10 +3349,9 @@ mod tests {
#[test]
fn test_select_all() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, "abc\nde\nfgh"));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abc\nde\nfgh", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer, None, settings, ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |b, ctx| b.select_all(&(), ctx));
assert_eq!(
view.read(app).selection_ranges(app.as_ref()),
@@ -3279,6 +3360,124 @@ mod tests {
});
}
+ #[test]
+ fn test_select_line() {
+ App::test((), |app| {
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 5), ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.select_line(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 0)..DisplayPoint::new(2, 0),
+ DisplayPoint::new(4, 0)..DisplayPoint::new(5, 0),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.select_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 0)..DisplayPoint::new(3, 0),
+ DisplayPoint::new(4, 0)..DisplayPoint::new(5, 5),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.select_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
+ );
+ });
+ }
+
+ #[test]
+ fn test_split_selection_into_lines() {
+ App::test((), |app| {
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(9, 5), ctx));
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
+ view.update(app, |view, ctx| {
+ view.fold_ranges(
+ vec![
+ Point::new(0, 2)..Point::new(1, 2),
+ Point::new(2, 3)..Point::new(4, 1),
+ Point::new(7, 0)..Point::new(8, 4),
+ ],
+ ctx,
+ );
+ view.select_display_ranges(
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2),
+ ],
+ ctx,
+ )
+ .unwrap();
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "aa…bbb\nccc…eeee\nfffff\nggggg\n…i"
+ );
+
+ view.update(app, |view, ctx| view.split_selection_into_lines(&(), ctx));
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "aa…bbb\nccc…eeee\nfffff\nggggg\n…i"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ DisplayPoint::new(4, 2)..DisplayPoint::new(4, 2)
+ ]
+ );
+
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
+ &[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)],
+ ctx,
+ )
+ .unwrap();
+ view.split_selection_into_lines(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\n…i"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+ DisplayPoint::new(2, 5)..DisplayPoint::new(2, 5),
+ DisplayPoint::new(3, 5)..DisplayPoint::new(3, 5),
+ DisplayPoint::new(4, 5)..DisplayPoint::new(4, 5),
+ DisplayPoint::new(5, 5)..DisplayPoint::new(5, 5),
+ DisplayPoint::new(6, 5)..DisplayPoint::new(6, 5),
+ DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
+ ]
+ );
+ });
+ }
+
impl BufferView {
fn selection_ranges(&self, app: &AppContext) -> Vec<Range<DisplayPoint>> {
self.selections_in_range(DisplayPoint::zero()..self.max_point(app), app)
@@ -1,10 +1,10 @@
use super::{
- buffer, Anchor, AnchorRangeExt, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset,
+ buffer::{self, AnchorRangeExt},
+ Anchor, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset,
};
use crate::{
- sum_tree::{self, Cursor, SumTree},
+ sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
time,
- util::find_insertion_index,
};
use anyhow::{anyhow, Result};
use gpui::{AppContext, ModelHandle};
@@ -14,12 +14,11 @@ use std::{
iter::Take,
ops::Range,
};
-use sum_tree::{Dimension, SeekBias};
pub struct FoldMap {
buffer: ModelHandle<Buffer>,
transforms: Mutex<SumTree<Transform>>,
- folds: Vec<Range<Anchor>>,
+ folds: SumTree<Fold>,
last_sync: Mutex<time::Global>,
}
@@ -29,14 +28,17 @@ impl FoldMap {
let text_summary = buffer.text_summary();
Self {
buffer: buffer_handle,
- folds: Vec::new(),
- transforms: Mutex::new(SumTree::from_item(Transform {
- summary: TransformSummary {
- buffer: text_summary.clone(),
- display: text_summary,
+ folds: Default::default(),
+ transforms: Mutex::new(SumTree::from_item(
+ Transform {
+ summary: TransformSummary {
+ buffer: text_summary.clone(),
+ display: text_summary,
+ },
+ display_text: None,
},
- display_text: None,
- })),
+ &(),
+ )),
last_sync: Mutex::new(buffer.version()),
}
}
@@ -76,17 +78,12 @@ impl FoldMap {
pub fn folds_in_range<'a, T>(
&'a self,
range: Range<T>,
- app: &'a AppContext,
+ ctx: &'a AppContext,
) -> Result<impl Iterator<Item = &'a Range<Anchor>>>
where
T: ToOffset,
{
- let buffer = self.buffer.read(app);
- let range = buffer.anchor_before(range.start)?..buffer.anchor_before(range.end)?;
- Ok(self.folds.iter().filter(move |fold| {
- range.start.cmp(&fold.end, buffer).unwrap() == Ordering::Less
- && range.end.cmp(&fold.start, buffer).unwrap() == Ordering::Greater
- }))
+ Ok(self.intersecting_folds(range, ctx)?.map(|f| &f.0))
}
pub fn fold<T: ToOffset>(
@@ -97,19 +94,22 @@ impl FoldMap {
let _ = self.sync(ctx);
let mut edits = Vec::new();
+ let mut folds = Vec::new();
let buffer = self.buffer.read(ctx);
for range in ranges.into_iter() {
- let start = range.start.to_offset(buffer)?;
- let end = range.end.to_offset(buffer)?;
- edits.push(Edit {
- old_range: start..end,
- new_range: start..end,
- });
-
- let fold = buffer.anchor_after(start)?..buffer.anchor_before(end)?;
- let ix = find_insertion_index(&self.folds, |probe| probe.cmp(&fold, buffer))?;
- self.folds.insert(ix, fold);
+ let range = range.start.to_offset(buffer)?..range.end.to_offset(buffer)?;
+ if range.start != range.end {
+ let fold =
+ Fold(buffer.anchor_after(range.start)?..buffer.anchor_before(range.end)?);
+ folds.push(fold);
+ edits.push(Edit {
+ old_range: range.clone(),
+ new_range: range.clone(),
+ });
+ }
}
+
+ folds.sort_unstable_by(|a, b| sum_tree::SeekDimension::cmp(a, b, buffer));
edits.sort_unstable_by(|a, b| {
a.old_range
.start
@@ -117,6 +117,16 @@ impl FoldMap {
.then_with(|| b.old_range.end.cmp(&a.old_range.end))
});
+ self.folds = {
+ let mut new_tree = SumTree::new();
+ let mut cursor = self.folds.cursor::<_, ()>();
+ for fold in folds {
+ new_tree.push_tree(cursor.slice(&fold, SeekBias::Right, buffer), buffer);
+ new_tree.push(fold, buffer);
+ }
+ new_tree.push_tree(cursor.suffix(buffer), buffer);
+ new_tree
+ };
self.apply_edits(edits, ctx);
Ok(())
}
@@ -131,36 +141,66 @@ impl FoldMap {
let buffer = self.buffer.read(ctx);
let mut edits = Vec::new();
+ let mut fold_ixs_to_delete = Vec::new();
for range in ranges.into_iter() {
- let start = buffer.anchor_before(range.start.to_offset(buffer)?)?;
- let end = buffer.anchor_after(range.end.to_offset(buffer)?)?;
-
- // Remove intersecting folds and add their ranges to edits that are passed to apply_edits
- self.folds.retain(|fold| {
- if fold.start.cmp(&end, buffer).unwrap() > Ordering::Equal
- || fold.end.cmp(&start, buffer).unwrap() < Ordering::Equal
- {
- true
- } else {
- let offset_range =
- fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap();
- edits.push(Edit {
- old_range: offset_range.clone(),
- new_range: offset_range,
- });
- false
- }
- });
+ // Remove intersecting folds and add their ranges to edits that are passed to apply_edits.
+ let mut folds_cursor = self.intersecting_folds(range, ctx)?;
+ while let Some(fold) = folds_cursor.item() {
+ let offset_range =
+ fold.0.start.to_offset(buffer).unwrap()..fold.0.end.to_offset(buffer).unwrap();
+ edits.push(Edit {
+ old_range: offset_range.clone(),
+ new_range: offset_range,
+ });
+ fold_ixs_to_delete.push(*folds_cursor.start());
+ folds_cursor.next();
+ }
}
+ fold_ixs_to_delete.sort_unstable();
+ fold_ixs_to_delete.dedup();
+ edits.sort_unstable_by(|a, b| {
+ a.old_range
+ .start
+ .cmp(&b.old_range.start)
+ .then_with(|| b.old_range.end.cmp(&a.old_range.end))
+ });
+
+ self.folds = {
+ let mut cursor = self.folds.cursor::<_, ()>();
+ let mut folds = SumTree::new();
+ for fold_ix in fold_ixs_to_delete {
+ folds.push_tree(cursor.slice(&fold_ix, SeekBias::Right, buffer), buffer);
+ cursor.next();
+ }
+ folds.push_tree(cursor.suffix(buffer), buffer);
+ folds
+ };
self.apply_edits(edits, ctx);
Ok(())
}
+ fn intersecting_folds<'a, T>(
+ &self,
+ range: Range<T>,
+ ctx: &'a AppContext,
+ ) -> Result<FilterCursor<impl 'a + Fn(&FoldSummary) -> bool, Fold, usize>>
+ where
+ T: ToOffset,
+ {
+ let buffer = self.buffer.read(ctx);
+ let start = buffer.anchor_before(range.start.to_offset(buffer)?)?;
+ let end = buffer.anchor_after(range.end.to_offset(buffer)?)?;
+ Ok(self.folds.filter::<_, usize>(move |summary| {
+ start.cmp(&summary.max_end, buffer).unwrap() == Ordering::Less
+ && end.cmp(&summary.min_start, buffer).unwrap() == Ordering::Greater
+ }))
+ }
+
pub fn is_line_folded(&self, display_row: u32, ctx: &AppContext) -> bool {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, DisplayPoint>();
- cursor.seek(&DisplayPoint::new(display_row, 0), SeekBias::Right);
+ cursor.seek(&DisplayPoint::new(display_row, 0), SeekBias::Right, &());
while let Some(transform) = cursor.item() {
if transform.display_text.is_some() {
return true;
@@ -177,7 +217,7 @@ impl FoldMap {
pub fn to_buffer_offset(&self, point: DisplayPoint, ctx: &AppContext) -> Result<usize> {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, TransformSummary>();
- cursor.seek(&point, SeekBias::Right);
+ cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
(cursor.start().buffer.lines + overshoot).to_offset(self.buffer.read(ctx))
}
@@ -193,7 +233,7 @@ impl FoldMap {
pub fn to_buffer_point(&self, display_point: DisplayPoint, ctx: &AppContext) -> Point {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<DisplayPoint, TransformSummary>();
- cursor.seek(&display_point, SeekBias::Right);
+ cursor.seek(&display_point, SeekBias::Right, &());
let overshoot = display_point.0 - cursor.start().display.lines;
cursor.start().buffer.lines + overshoot
}
@@ -201,7 +241,7 @@ impl FoldMap {
pub fn to_display_point(&self, point: Point, ctx: &AppContext) -> DisplayPoint {
let transforms = self.sync(ctx);
let mut cursor = transforms.cursor::<Point, TransformSummary>();
- cursor.seek(&point, SeekBias::Right);
+ cursor.seek(&point, SeekBias::Right, &());
let overshoot = point - cursor.start().buffer.lines;
DisplayPoint(cmp::min(
cursor.start().display.lines + overshoot,
@@ -226,14 +266,17 @@ impl FoldMap {
let mut new_transforms = SumTree::new();
let mut transforms = self.transforms.lock();
let mut cursor = transforms.cursor::<usize, usize>();
- cursor.seek(&0, SeekBias::Right);
+ cursor.seek(&0, SeekBias::Right, &());
while let Some(mut edit) = edits.next() {
- new_transforms.push_tree(cursor.slice(&edit.old_range.start, SeekBias::Left));
+ new_transforms.push_tree(
+ cursor.slice(&edit.old_range.start, SeekBias::Left, &()),
+ &(),
+ );
edit.new_range.start -= edit.old_range.start - cursor.start();
edit.old_range.start = *cursor.start();
- cursor.seek(&edit.old_range.end, SeekBias::Right);
+ cursor.seek(&edit.old_range.end, SeekBias::Right, &());
cursor.next();
let mut delta = edit.delta();
@@ -250,7 +293,7 @@ impl FoldMap {
if next_edit.old_range.end >= edit.old_range.end {
edit.old_range.end = next_edit.old_range.end;
- cursor.seek(&edit.old_range.end, SeekBias::Right);
+ cursor.seek(&edit.old_range.end, SeekBias::Right, &());
cursor.next();
}
} else {
@@ -262,14 +305,10 @@ impl FoldMap {
((edit.new_range.start + edit.old_extent()) as isize + delta) as usize;
let anchor = buffer.anchor_before(edit.new_range.start).unwrap();
- let folds_start =
- find_insertion_index(&self.folds, |probe| probe.start.cmp(&anchor, buffer))
- .unwrap();
- let mut folds = self.folds[folds_start..]
- .iter()
- .map(|fold| {
- fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap()
- })
+ let mut folds_cursor = self.folds.cursor::<_, ()>();
+ folds_cursor.seek(&Fold(anchor..Anchor::End), SeekBias::Left, buffer);
+ let mut folds = folds_cursor
+ .map(|f| f.0.start.to_offset(buffer).unwrap()..f.0.end.to_offset(buffer).unwrap())
.peekable();
while folds
@@ -293,29 +332,35 @@ impl FoldMap {
if fold.start > sum.buffer.chars {
let text_summary = buffer.text_summary_for_range(sum.buffer.chars..fold.start);
- new_transforms.push(Transform {
- summary: TransformSummary {
- display: text_summary.clone(),
- buffer: text_summary,
+ new_transforms.push(
+ Transform {
+ summary: TransformSummary {
+ display: text_summary.clone(),
+ buffer: text_summary,
+ },
+ display_text: None,
},
- display_text: None,
- });
+ &(),
+ );
}
if fold.end > fold.start {
- new_transforms.push(Transform {
- summary: TransformSummary {
- display: TextSummary {
- chars: 1,
- bytes: '…'.len_utf8(),
- lines: Point::new(0, 1),
- first_line_len: 1,
- rightmost_point: Point::new(0, 1),
+ new_transforms.push(
+ Transform {
+ summary: TransformSummary {
+ display: TextSummary {
+ chars: 1,
+ bytes: '…'.len_utf8(),
+ lines: Point::new(0, 1),
+ first_line_len: 1,
+ rightmost_point: Point::new(0, 1),
+ },
+ buffer: buffer.text_summary_for_range(fold.start..fold.end),
},
- buffer: buffer.text_summary_for_range(fold.start..fold.end),
+ display_text: Some('…'),
},
- display_text: Some('…'),
- });
+ &(),
+ );
}
}
@@ -323,26 +368,32 @@ impl FoldMap {
if sum.buffer.chars < edit.new_range.end {
let text_summary =
buffer.text_summary_for_range(sum.buffer.chars..edit.new_range.end);
- new_transforms.push(Transform {
- summary: TransformSummary {
- display: text_summary.clone(),
- buffer: text_summary,
+ new_transforms.push(
+ Transform {
+ summary: TransformSummary {
+ display: text_summary.clone(),
+ buffer: text_summary,
+ },
+ display_text: None,
},
- display_text: None,
- });
+ &(),
+ );
}
}
- new_transforms.push_tree(cursor.suffix());
+ new_transforms.push_tree(cursor.suffix(&()), &());
if new_transforms.is_empty() {
let text_summary = buffer.text_summary();
- new_transforms.push(Transform {
- summary: TransformSummary {
- display: text_summary.clone(),
- buffer: text_summary,
+ new_transforms.push(
+ Transform {
+ summary: TransformSummary {
+ display: text_summary.clone(),
+ buffer: text_summary,
+ },
+ display_text: None,
},
- display_text: None,
- });
+ &(),
+ );
}
drop(cursor);
@@ -363,7 +414,7 @@ impl FoldMapSnapshot {
let display_point = Point::new(start_row, 0);
let mut cursor = self.transforms.cursor();
- cursor.seek(&DisplayPoint(display_point), SeekBias::Left);
+ cursor.seek(&DisplayPoint(display_point), SeekBias::Left, &());
Ok(BufferRows {
display_point,
@@ -374,7 +425,7 @@ impl FoldMapSnapshot {
pub fn chars_at<'a>(&'a self, point: DisplayPoint, ctx: &'a AppContext) -> Result<Chars<'a>> {
let offset = self.to_display_offset(point, ctx)?;
let mut cursor = self.transforms.cursor();
- cursor.seek(&offset, SeekBias::Right);
+ cursor.seek(&offset, SeekBias::Right, &());
Ok(Chars {
cursor,
offset: offset.0,
@@ -385,7 +436,7 @@ impl FoldMapSnapshot {
fn to_display_offset(&self, point: DisplayPoint, ctx: &AppContext) -> Result<DisplayOffset> {
let mut cursor = self.transforms.cursor::<DisplayPoint, TransformSummary>();
- cursor.seek(&point, SeekBias::Right);
+ cursor.seek(&point, SeekBias::Right, &());
let overshoot = point.0 - cursor.start().display.lines;
let mut offset = cursor.start().display.chars;
if !overshoot.is_zero() {
@@ -421,16 +472,106 @@ impl sum_tree::Item for Transform {
}
}
-impl<'a> std::ops::AddAssign<&'a Self> for TransformSummary {
- fn add_assign(&mut self, other: &'a Self) {
+impl sum_tree::Summary for TransformSummary {
+ type Context = ();
+
+ fn add_summary(&mut self, other: &Self, _: &()) {
self.buffer += &other.buffer;
self.display += &other.display;
}
}
-impl<'a> Dimension<'a, TransformSummary> for TransformSummary {
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for TransformSummary {
fn add_summary(&mut self, summary: &'a TransformSummary) {
- *self += summary;
+ sum_tree::Summary::add_summary(self, summary, &());
+ }
+}
+
+#[derive(Clone, Debug)]
+struct Fold(Range<Anchor>);
+
+impl Default for Fold {
+ fn default() -> Self {
+ Self(Anchor::Start..Anchor::End)
+ }
+}
+
+impl sum_tree::Item for Fold {
+ type Summary = FoldSummary;
+
+ fn summary(&self) -> Self::Summary {
+ FoldSummary {
+ start: self.0.start.clone(),
+ end: self.0.end.clone(),
+ min_start: self.0.start.clone(),
+ max_end: self.0.end.clone(),
+ count: 1,
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+struct FoldSummary {
+ start: Anchor,
+ end: Anchor,
+ min_start: Anchor,
+ max_end: Anchor,
+ count: usize,
+}
+
+impl Default for FoldSummary {
+ fn default() -> Self {
+ Self {
+ start: Anchor::Start,
+ end: Anchor::End,
+ min_start: Anchor::End,
+ max_end: Anchor::Start,
+ count: 0,
+ }
+ }
+}
+
+impl sum_tree::Summary for FoldSummary {
+ type Context = Buffer;
+
+ fn add_summary(&mut self, other: &Self, buffer: &Buffer) {
+ if other.min_start.cmp(&self.min_start, buffer).unwrap() == Ordering::Less {
+ self.min_start = other.min_start.clone();
+ }
+ if other.max_end.cmp(&self.max_end, buffer).unwrap() == Ordering::Greater {
+ self.max_end = other.max_end.clone();
+ }
+
+ #[cfg(debug_assertions)]
+ {
+ let start_comparison = self.start.cmp(&other.start, buffer).unwrap();
+ assert!(start_comparison <= Ordering::Equal);
+ if start_comparison == Ordering::Equal {
+ assert!(self.end.cmp(&other.end, buffer).unwrap() >= Ordering::Equal);
+ }
+ }
+ self.start = other.start.clone();
+ self.end = other.end.clone();
+ self.count += other.count;
+ }
+}
+
+impl<'a> sum_tree::Dimension<'a, FoldSummary> for Fold {
+ fn add_summary(&mut self, summary: &'a FoldSummary) {
+ self.0.start = summary.start.clone();
+ self.0.end = summary.end.clone();
+ }
+}
+
+impl<'a> sum_tree::SeekDimension<'a, FoldSummary> for Fold {
+ fn cmp(&self, other: &Self, buffer: &Buffer) -> Ordering {
+ self.0.cmp(&other.0, buffer).unwrap()
+ }
+}
+
+impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize {
+ fn add_summary(&mut self, summary: &'a FoldSummary) {
+ *self += summary.count;
}
}
@@ -498,7 +639,7 @@ impl<'a> Iterator for Chars<'a> {
}
}
-impl<'a> Dimension<'a, TransformSummary> for DisplayPoint {
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayPoint {
fn add_summary(&mut self, summary: &'a TransformSummary) {
self.0 += &summary.display.lines;
}
@@ -507,19 +648,19 @@ impl<'a> Dimension<'a, TransformSummary> for DisplayPoint {
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
pub struct DisplayOffset(usize);
-impl<'a> Dimension<'a, TransformSummary> for DisplayOffset {
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for DisplayOffset {
fn add_summary(&mut self, summary: &'a TransformSummary) {
self.0 += &summary.display.chars;
}
}
-impl<'a> Dimension<'a, TransformSummary> for Point {
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
fn add_summary(&mut self, summary: &'a TransformSummary) {
*self += &summary.buffer.lines;
}
}
-impl<'a> Dimension<'a, TransformSummary> for usize {
+impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
fn add_summary(&mut self, summary: &'a TransformSummary) {
*self += &summary.buffer.chars;
}
@@ -535,7 +676,7 @@ mod tests {
#[test]
fn test_basic_folds() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold(
@@ -571,7 +712,7 @@ mod tests {
});
assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
- map.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), app.as_ref())
+ map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), app.as_ref())
.unwrap();
assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
});
@@ -580,7 +721,7 @@ mod tests {
#[test]
fn test_adjacent_folds() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, "abcdefghijkl"));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
{
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
@@ -623,7 +764,7 @@ mod tests {
#[test]
fn test_overlapping_folds() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold(
vec![
@@ -642,7 +783,7 @@ mod tests {
#[test]
fn test_merging_folds_via_edit() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold(
@@ -667,7 +808,7 @@ mod tests {
#[test]
fn test_folds_in_range() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, sample_text(5, 6)));
+ let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
let buffer = buffer.read(app);
@@ -719,39 +860,51 @@ mod tests {
};
for seed in seed_range {
- println!("{:?}", seed);
+ dbg!(seed);
let mut rng = StdRng::seed_from_u64(seed);
App::test((), |app| {
- let buffer = app.add_model(|_| {
+ let buffer = app.add_model(|ctx| {
let len = rng.gen_range(0..10);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
- Buffer::new(0, text)
+ Buffer::new(0, text, ctx)
});
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
for _ in 0..operations {
log::info!("text: {:?}", buffer.read(app).text());
- if rng.gen() {
- let buffer = buffer.read(app);
-
- let fold_count = rng.gen_range(1..=5);
- let mut fold_ranges: Vec<Range<usize>> = Vec::new();
- for _ in 0..fold_count {
- let end = rng.gen_range(0..buffer.len() + 1);
- let start = rng.gen_range(0..end + 1);
- fold_ranges.push(start..end);
+ match rng.gen_range(0..=100) {
+ 0..=34 => {
+ let buffer = buffer.read(app);
+ let mut to_fold = Vec::new();
+ for _ in 0..rng.gen_range(1..=5) {
+ let end = rng.gen_range(0..=buffer.len());
+ let start = rng.gen_range(0..=end);
+ to_fold.push(start..end);
+ }
+ log::info!("folding {:?}", to_fold);
+ map.fold(to_fold, app.as_ref()).unwrap();
+ }
+ 35..=59 if !map.folds.is_empty() => {
+ let buffer = buffer.read(app);
+ let mut to_unfold = Vec::new();
+ for _ in 0..rng.gen_range(1..=3) {
+ let end = rng.gen_range(0..=buffer.len());
+ let start = rng.gen_range(0..=end);
+ to_unfold.push(start..end);
+ }
+ log::info!("unfolding {:?}", to_unfold);
+ map.unfold(to_unfold, app.as_ref()).unwrap();
+ }
+ _ => {
+ let edits = buffer.update(app, |buffer, ctx| {
+ let start_version = buffer.version.clone();
+ let edit_count = rng.gen_range(1..=5);
+ buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
+ buffer.edits_since(start_version).collect::<Vec<_>>()
+ });
+ log::info!("editing {:?}", edits);
}
- log::info!("folding {:?}", fold_ranges);
- map.fold(fold_ranges.clone(), app.as_ref()).unwrap();
- } else {
- let edits = buffer.update(app, |buffer, ctx| {
- let start_version = buffer.version.clone();
- let edit_count = rng.gen_range(1..=5);
- buffer.randomly_edit(&mut rng, edit_count, Some(ctx));
- buffer.edits_since(start_version).collect::<Vec<_>>()
- });
- log::info!("editing {:?}", edits);
}
map.check_invariants(app.as_ref());
@@ -844,6 +997,31 @@ mod tests {
);
assert!(map.is_line_folded(display_point.row(), app.as_ref()));
}
+
+ for _ in 0..5 {
+ let end = rng.gen_range(0..=buffer.len());
+ let start = rng.gen_range(0..=end);
+ let expected_folds = map
+ .folds
+ .items()
+ .into_iter()
+ .filter(|fold| {
+ let start = buffer.anchor_before(start).unwrap();
+ let end = buffer.anchor_after(end).unwrap();
+ start.cmp(&fold.0.end, buffer).unwrap() == Ordering::Less
+ && end.cmp(&fold.0.start, buffer).unwrap() == Ordering::Greater
+ })
+ .map(|fold| fold.0)
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ map.folds_in_range(start..end, app.as_ref())
+ .unwrap()
+ .cloned()
+ .collect::<Vec<_>>(),
+ expected_folds
+ );
+ }
}
});
}
@@ -853,7 +1031,7 @@ mod tests {
fn test_buffer_rows() {
App::test((), |app| {
let text = sample_text(6, 6) + "\n";
- let buffer = app.add_model(|_| Buffer::new(0, text));
+ let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
let mut map = FoldMap::new(buffer.clone(), app.as_ref());
@@ -894,13 +1072,13 @@ mod tests {
fn merged_fold_ranges(&self, app: &AppContext) -> Vec<Range<usize>> {
let buffer = self.buffer.read(app);
- let mut folds = self.folds.clone();
+ let mut folds = self.folds.items();
// Ensure sorting doesn't change how folds get merged and displayed.
- folds.sort_by(|a, b| a.cmp(b, buffer).unwrap());
+ folds.sort_by(|a, b| a.0.cmp(&b.0, buffer).unwrap());
let mut fold_ranges = folds
.iter()
.map(|fold| {
- fold.start.to_offset(buffer).unwrap()..fold.end.to_offset(buffer).unwrap()
+ fold.0.start.to_offset(buffer).unwrap()..fold.0.end.to_offset(buffer).unwrap()
})
.peekable();
@@ -1,6 +1,6 @@
mod fold_map;
-use super::{buffer, Anchor, AnchorRangeExt, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint};
+use super::{buffer, Anchor, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint};
use anyhow::Result;
pub use fold_map::BufferRows;
use fold_map::{FoldMap, FoldMapSnapshot};
@@ -345,7 +345,7 @@ mod tests {
fn test_chars_at() {
App::test((), |app| {
let text = sample_text(6, 6);
- let buffer = app.add_model(|_| Buffer::new(0, text));
+ let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
buffer
.update(app, |buffer, ctx| {
@@ -414,7 +414,7 @@ mod tests {
#[test]
fn test_max_point() {
App::test((), |app| {
- let buffer = app.add_model(|_| Buffer::new(0, "aaa\n\t\tbbb"));
+ let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx));
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11))
});
@@ -25,12 +25,21 @@ pub fn menus(settings: watch::Receiver<Settings>) -> Vec<Menu<'static>> {
},
Menu {
name: "File",
- items: vec![MenuItem::Action {
- name: "Open…",
- keystroke: Some("cmd-o"),
- action: "workspace:open",
- arg: Some(Box::new(settings)),
- }],
+ items: vec![
+ MenuItem::Action {
+ name: "New",
+ keystroke: Some("cmd-n"),
+ action: "workspace:new_file",
+ arg: None,
+ },
+ MenuItem::Separator,
+ MenuItem::Action {
+ name: "Open…",
+ keystroke: Some("cmd-o"),
+ action: "workspace:open",
+ arg: Some(Box::new(settings)),
+ },
+ ],
},
Menu {
name: "Edit",
@@ -1,11 +1,8 @@
use crate::{
- sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree},
+ sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree, Summary},
time,
};
-use std::{
- fmt::Debug,
- ops::{Add, AddAssign},
-};
+use std::{fmt::Debug, ops::Add};
pub trait Operation: Clone + Debug + Eq {
fn timestamp(&self) -> time::Lamport;
@@ -35,7 +32,8 @@ impl<T: Operation> OperationQueue<T> {
pub fn insert(&mut self, mut ops: Vec<T>) {
ops.sort_by_key(|op| op.timestamp());
ops.dedup_by_key(|op| op.timestamp());
- self.0.edit(ops.into_iter().map(Edit::Insert).collect());
+ self.0
+ .edit(ops.into_iter().map(Edit::Insert).collect(), &());
}
pub fn drain(&mut self) -> Self {
@@ -68,8 +66,10 @@ impl<T: Operation> KeyedItem for T {
}
}
-impl<'a> AddAssign<&'a Self> for OperationSummary {
- fn add_assign(&mut self, other: &Self) {
+impl Summary for OperationSummary {
+ type Context = ();
+
+ fn add_summary(&mut self, other: &Self, _: &()) {
assert!(self.key < other.key);
self.key = other.key;
self.len += other.len;
@@ -199,9 +199,6 @@ where
}
pub fn next(&mut self) {
- if !self.did_seek {
- self.descend_to_first_item(self.tree, |_| true)
- }
self.next_internal(|_| true)
}
@@ -209,67 +206,88 @@ where
where
F: Fn(&T::Summary) -> bool,
{
- assert!(self.did_seek, "Must seek before calling this method");
+ let mut descend = false;
+
+ if self.stack.is_empty() && !self.at_end {
+ self.stack.push(StackEntry {
+ tree: self.tree,
+ index: 0,
+ seek_dimension: S::default(),
+ sum_dimension: U::default(),
+ });
+ descend = true;
+ self.did_seek = true;
+ }
- if self.stack.is_empty() {
- if !self.at_end {
- self.descend_to_first_item(self.tree, filter_node);
- }
- } else {
- while self.stack.len() > 0 {
- let new_subtree = {
- let entry = self.stack.last_mut().unwrap();
- match entry.tree.0.as_ref() {
- Node::Internal {
- child_trees,
- child_summaries,
- ..
- } => {
- while entry.index < child_summaries.len() {
- entry
- .seek_dimension
- .add_summary(&child_summaries[entry.index]);
- entry
- .sum_dimension
- .add_summary(&child_summaries[entry.index]);
-
- entry.index += 1;
- if let Some(next_summary) = child_summaries.get(entry.index) {
- if filter_node(next_summary) {
- break;
- } else {
- self.seek_dimension.add_summary(next_summary);
- self.sum_dimension.add_summary(next_summary);
- }
- }
- }
+ while self.stack.len() > 0 {
+ let new_subtree = {
+ let entry = self.stack.last_mut().unwrap();
+ match entry.tree.0.as_ref() {
+ Node::Internal {
+ child_trees,
+ child_summaries,
+ ..
+ } => {
+ if !descend {
+ let summary = &child_summaries[entry.index];
+ entry.seek_dimension.add_summary(summary);
+ entry.sum_dimension.add_summary(summary);
+ entry.index += 1;
+ }
- child_trees.get(entry.index)
+ while entry.index < child_summaries.len() {
+ let next_summary = &child_summaries[entry.index];
+ if filter_node(next_summary) {
+ break;
+ } else {
+ self.seek_dimension.add_summary(next_summary);
+ self.sum_dimension.add_summary(next_summary);
+ }
+ entry.index += 1;
}
- Node::Leaf { item_summaries, .. } => loop {
+
+ child_trees.get(entry.index)
+ }
+ Node::Leaf { item_summaries, .. } => {
+ if !descend {
let item_summary = &item_summaries[entry.index];
self.seek_dimension.add_summary(item_summary);
entry.seek_dimension.add_summary(item_summary);
self.sum_dimension.add_summary(item_summary);
entry.sum_dimension.add_summary(item_summary);
entry.index += 1;
+ }
+
+ loop {
if let Some(next_item_summary) = item_summaries.get(entry.index) {
if filter_node(next_item_summary) {
return;
+ } else {
+ self.seek_dimension.add_summary(next_item_summary);
+ entry.seek_dimension.add_summary(next_item_summary);
+ self.sum_dimension.add_summary(next_item_summary);
+ entry.sum_dimension.add_summary(next_item_summary);
+ entry.index += 1;
}
} else {
break None;
}
- },
+ }
}
- };
-
- if let Some(subtree) = new_subtree {
- self.descend_to_first_item(subtree, filter_node);
- break;
- } else {
- self.stack.pop();
}
+ };
+
+ if let Some(subtree) = new_subtree {
+ descend = true;
+ self.stack.push(StackEntry {
+ tree: subtree,
+ index: 0,
+ seek_dimension: self.seek_dimension.clone(),
+ sum_dimension: self.sum_dimension.clone(),
+ });
+ } else {
+ descend = false;
+ self.stack.pop();
}
}
@@ -277,67 +295,6 @@ where
debug_assert!(self.stack.is_empty() || self.stack.last().unwrap().tree.0.is_leaf());
}
- pub fn descend_to_first_item<F>(&mut self, mut subtree: &'a SumTree<T>, filter_node: F)
- where
- F: Fn(&T::Summary) -> bool,
- {
- self.did_seek = true;
- loop {
- subtree = match *subtree.0 {
- Node::Internal {
- ref child_trees,
- ref child_summaries,
- ..
- } => {
- let mut new_index = None;
- for (index, summary) in child_summaries.iter().enumerate() {
- if filter_node(summary) {
- new_index = Some(index);
- break;
- }
- self.seek_dimension.add_summary(summary);
- self.sum_dimension.add_summary(summary);
- }
-
- if let Some(new_index) = new_index {
- self.stack.push(StackEntry {
- tree: subtree,
- index: new_index,
- seek_dimension: self.seek_dimension.clone(),
- sum_dimension: self.sum_dimension.clone(),
- });
- &child_trees[new_index]
- } else {
- break;
- }
- }
- Node::Leaf {
- ref item_summaries, ..
- } => {
- let mut new_index = None;
- for (index, item_summary) in item_summaries.iter().enumerate() {
- if filter_node(item_summary) {
- new_index = Some(index);
- break;
- }
- self.seek_dimension.add_summary(item_summary);
- self.sum_dimension.add_summary(item_summary);
- }
-
- if let Some(new_index) = new_index {
- self.stack.push(StackEntry {
- tree: subtree,
- index: new_index,
- seek_dimension: self.seek_dimension.clone(),
- sum_dimension: self.sum_dimension.clone(),
- });
- }
- break;
- }
- }
- }
- }
-
fn descend_to_last_item(&mut self, mut subtree: &'a SumTree<T>) {
self.did_seek = true;
loop {
@@ -382,22 +339,36 @@ where
impl<'a, T, S, U> Cursor<'a, T, S, U>
where
T: Item,
- S: Dimension<'a, T::Summary> + Ord,
+ S: SeekDimension<'a, T::Summary>,
U: Dimension<'a, T::Summary>,
{
- pub fn seek(&mut self, pos: &S, bias: SeekBias) -> bool {
+ pub fn seek(
+ &mut self,
+ pos: &S,
+ bias: SeekBias,
+ ctx: &<T::Summary as Summary>::Context,
+ ) -> bool {
self.reset();
- self.seek_internal::<()>(pos, bias, &mut SeekAggregate::None)
+ self.seek_internal::<()>(pos, bias, &mut SeekAggregate::None, ctx)
}
- #[allow(unused)]
- pub fn seek_forward(&mut self, pos: &S, bias: SeekBias) -> bool {
- self.seek_internal::<()>(pos, bias, &mut SeekAggregate::None)
+ pub fn seek_forward(
+ &mut self,
+ pos: &S,
+ bias: SeekBias,
+ ctx: &<T::Summary as Summary>::Context,
+ ) -> bool {
+ self.seek_internal::<()>(pos, bias, &mut SeekAggregate::None, ctx)
}
- pub fn slice(&mut self, end: &S, bias: SeekBias) -> SumTree<T> {
+ pub fn slice(
+ &mut self,
+ end: &S,
+ bias: SeekBias,
+ ctx: &<T::Summary as Summary>::Context,
+ ) -> SumTree<T> {
let mut slice = SeekAggregate::Slice(SumTree::new());
- self.seek_internal::<()>(end, bias, &mut slice);
+ self.seek_internal::<()>(end, bias, &mut slice, ctx);
if let SeekAggregate::Slice(slice) = slice {
slice
} else {
@@ -405,10 +376,10 @@ where
}
}
- pub fn suffix(&mut self) -> SumTree<T> {
+ pub fn suffix(&mut self, ctx: &<T::Summary as Summary>::Context) -> SumTree<T> {
let extent = self.tree.extent::<S>();
let mut slice = SeekAggregate::Slice(SumTree::new());
- self.seek_internal::<()>(&extent, SeekBias::Right, &mut slice);
+ self.seek_internal::<()>(&extent, SeekBias::Right, &mut slice, ctx);
if let SeekAggregate::Slice(slice) = slice {
slice
} else {
@@ -416,12 +387,17 @@ where
}
}
- pub fn summary<D>(&mut self, end: &S, bias: SeekBias) -> D
+ pub fn summary<D>(
+ &mut self,
+ end: &S,
+ bias: SeekBias,
+ ctx: &<T::Summary as Summary>::Context,
+ ) -> D
where
D: Dimension<'a, T::Summary>,
{
let mut summary = SeekAggregate::Summary(D::default());
- self.seek_internal(end, bias, &mut summary);
+ self.seek_internal(end, bias, &mut summary, ctx);
if let SeekAggregate::Summary(summary) = summary {
summary
} else {
@@ -434,11 +410,12 @@ where
target: &S,
bias: SeekBias,
aggregate: &mut SeekAggregate<T, D>,
+ ctx: &<T::Summary as Summary>::Context,
) -> bool
where
D: Dimension<'a, T::Summary>,
{
- debug_assert!(target >= &self.seek_dimension);
+ debug_assert!(target.cmp(&self.seek_dimension, ctx) >= Ordering::Equal);
let mut containing_subtree = None;
if self.did_seek {
@@ -458,7 +435,7 @@ where
let mut child_end = self.seek_dimension.clone();
child_end.add_summary(&child_summary);
- let comparison = target.cmp(&child_end);
+ let comparison = target.cmp(&child_end, ctx);
if comparison == Ordering::Greater
|| (comparison == Ordering::Equal && bias == SeekBias::Right)
{
@@ -467,7 +444,7 @@ where
match aggregate {
SeekAggregate::None => {}
SeekAggregate::Slice(slice) => {
- slice.push_tree(child_tree.clone());
+ slice.push_tree(child_tree.clone(), ctx);
}
SeekAggregate::Summary(summary) => {
summary.add_summary(child_summary);
@@ -500,7 +477,7 @@ where
let mut item_end = self.seek_dimension.clone();
item_end.add_summary(item_summary);
- let comparison = target.cmp(&item_end);
+ let comparison = target.cmp(&item_end, ctx);
if comparison == Ordering::Greater
|| (comparison == Ordering::Equal && bias == SeekBias::Right)
{
@@ -511,7 +488,10 @@ where
SeekAggregate::Slice(_) => {
slice_items.push(item.clone());
slice_item_summaries.push(item_summary.clone());
- *slice_items_summary.as_mut().unwrap() += item_summary;
+ slice_items_summary
+ .as_mut()
+ .unwrap()
+ .add_summary(item_summary, ctx);
}
SeekAggregate::Summary(summary) => {
summary.add_summary(item_summary);
@@ -520,11 +500,14 @@ where
entry.index += 1;
} else {
if let SeekAggregate::Slice(slice) = aggregate {
- slice.push_tree(SumTree(Arc::new(Node::Leaf {
- summary: slice_items_summary.unwrap(),
- items: slice_items,
- item_summaries: slice_item_summaries,
- })));
+ slice.push_tree(
+ SumTree(Arc::new(Node::Leaf {
+ summary: slice_items_summary.unwrap(),
+ items: slice_items,
+ item_summaries: slice_item_summaries,
+ })),
+ ctx,
+ );
}
break 'outer;
}
@@ -532,11 +515,14 @@ where
if let SeekAggregate::Slice(slice) = aggregate {
if !slice_items.is_empty() {
- slice.push_tree(SumTree(Arc::new(Node::Leaf {
- summary: slice_items_summary.unwrap(),
- items: slice_items,
- item_summaries: slice_item_summaries,
- })));
+ slice.push_tree(
+ SumTree(Arc::new(Node::Leaf {
+ summary: slice_items_summary.unwrap(),
+ items: slice_items,
+ item_summaries: slice_item_summaries,
+ })),
+ ctx,
+ );
}
}
}
@@ -565,7 +551,7 @@ where
let mut child_end = self.seek_dimension.clone();
child_end.add_summary(child_summary);
- let comparison = target.cmp(&child_end);
+ let comparison = target.cmp(&child_end, ctx);
if comparison == Ordering::Greater
|| (comparison == Ordering::Equal && bias == SeekBias::Right)
{
@@ -574,7 +560,7 @@ where
match aggregate {
SeekAggregate::None => {}
SeekAggregate::Slice(slice) => {
- slice.push_tree(child_trees[index].clone());
+ slice.push_tree(child_trees[index].clone(), ctx);
}
SeekAggregate::Summary(summary) => {
summary.add_summary(child_summary);
@@ -611,7 +597,7 @@ where
let mut child_end = self.seek_dimension.clone();
child_end.add_summary(item_summary);
- let comparison = target.cmp(&child_end);
+ let comparison = target.cmp(&child_end, ctx);
if comparison == Ordering::Greater
|| (comparison == Ordering::Equal && bias == SeekBias::Right)
{
@@ -621,7 +607,10 @@ where
SeekAggregate::None => {}
SeekAggregate::Slice(_) => {
slice_items.push(item.clone());
- *slice_items_summary.as_mut().unwrap() += item_summary;
+ slice_items_summary
+ .as_mut()
+ .unwrap()
+ .add_summary(item_summary, ctx);
slice_item_summaries.push(item_summary.clone());
}
SeekAggregate::Summary(summary) => {
@@ -641,11 +630,14 @@ where
if let SeekAggregate::Slice(slice) = aggregate {
if !slice_items.is_empty() {
- slice.push_tree(SumTree(Arc::new(Node::Leaf {
- summary: slice_items_summary.unwrap(),
- items: slice_items,
- item_summaries: slice_item_summaries,
- })));
+ slice.push_tree(
+ SumTree(Arc::new(Node::Leaf {
+ summary: slice_items_summary.unwrap(),
+ items: slice_items,
+ item_summaries: slice_item_summaries,
+ })),
+ ctx,
+ );
}
}
}
@@ -666,9 +658,9 @@ where
if let Some(summary) = self.item_summary() {
end.add_summary(summary);
}
- *target == end
+ target.cmp(&end, ctx) == Ordering::Equal
} else {
- *target == self.seek_dimension
+ target.cmp(&self.seek_dimension, ctx) == Ordering::Equal
}
}
}
@@ -683,7 +675,7 @@ where
fn next(&mut self) -> Option<Self::Item> {
if !self.did_seek {
- self.descend_to_first_item(self.tree, |_| true);
+ self.next();
}
if let Some(item) = self.item() {
@@ -708,13 +700,7 @@ where
{
pub fn new(tree: &'a SumTree<T>, filter_node: F) -> Self {
let mut cursor = tree.cursor::<(), U>();
- if filter_node(&tree.summary()) {
- cursor.descend_to_first_item(tree, &filter_node);
- } else {
- cursor.did_seek = true;
- cursor.at_end = true;
- }
-
+ cursor.next_internal(&filter_node);
Self {
cursor,
filter_node,
@@ -3,7 +3,7 @@ mod cursor;
use arrayvec::ArrayVec;
pub use cursor::Cursor;
pub use cursor::FilterCursor;
-use std::{fmt, iter::FromIterator, ops::AddAssign, sync::Arc};
+use std::{cmp::Ordering, fmt, iter::FromIterator, sync::Arc};
#[cfg(test)]
const TREE_BASE: usize = 2;
@@ -11,7 +11,7 @@ const TREE_BASE: usize = 2;
const TREE_BASE: usize = 6;
pub trait Item: Clone + fmt::Debug {
- type Summary: for<'a> AddAssign<&'a Self::Summary> + Default + Clone + fmt::Debug;
+ type Summary: Summary;
fn summary(&self) -> Self::Summary;
}
@@ -22,14 +22,30 @@ pub trait KeyedItem: Item {
fn key(&self) -> Self::Key;
}
-pub trait Dimension<'a, Summary: Default>: Clone + fmt::Debug + Default {
- fn add_summary(&mut self, summary: &'a Summary);
+pub trait Summary: Default + Clone + fmt::Debug {
+ type Context;
+
+ fn add_summary(&mut self, summary: &Self, ctx: &Self::Context);
+}
+
+pub trait Dimension<'a, S: Summary>: Clone + fmt::Debug + Default {
+ fn add_summary(&mut self, _summary: &'a S);
}
-impl<'a, T: Default> Dimension<'a, T> for () {
+impl<'a, T: Summary> Dimension<'a, T> for () {
fn add_summary(&mut self, _: &'a T) {}
}
+pub trait SeekDimension<'a, T: Summary>: Dimension<'a, T> {
+ fn cmp(&self, other: &Self, ctx: &T::Context) -> Ordering;
+}
+
+impl<'a, S: Summary, T: Dimension<'a, S> + Ord> SeekDimension<'a, S> for T {
+ fn cmp(&self, other: &Self, _ctx: &S::Context) -> Ordering {
+ Ord::cmp(self, other)
+ }
+}
+
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum SeekBias {
Left,
@@ -48,16 +64,15 @@ impl<T: Item> SumTree<T> {
}))
}
- pub fn from_item(item: T) -> Self {
+ pub fn from_item(item: T, ctx: &<T::Summary as Summary>::Context) -> Self {
let mut tree = Self::new();
- tree.push(item);
+ tree.push(item, ctx);
tree
}
#[allow(unused)]
pub fn items(&self) -> Vec<T> {
let mut cursor = self.cursor::<(), ()>();
- cursor.descend_to_first_item(self, |_| true);
cursor.cloned().collect()
}
@@ -90,7 +105,7 @@ impl<T: Item> SumTree<T> {
let mut extent = D::default();
match self.0.as_ref() {
Node::Internal { summary, .. } | Node::Leaf { summary, .. } => {
- extent.add_summary(summary)
+ extent.add_summary(summary);
}
}
extent
@@ -110,7 +125,7 @@ impl<T: Item> SumTree<T> {
}
}
- pub fn extend<I>(&mut self, iter: I)
+ pub fn extend<I>(&mut self, iter: I, ctx: &<T::Summary as Summary>::Context)
where
I: IntoIterator<Item = T>,
{
@@ -118,7 +133,7 @@ impl<T: Item> SumTree<T> {
for item in iter {
if leaf.is_some() && leaf.as_ref().unwrap().items().len() == 2 * TREE_BASE {
- self.push_tree(SumTree(Arc::new(leaf.take().unwrap())));
+ self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), ctx);
}
if leaf.is_none() {
@@ -136,7 +151,7 @@ impl<T: Item> SumTree<T> {
}) = leaf.as_mut()
{
let item_summary = item.summary();
- *summary += &item_summary;
+ summary.add_summary(&item_summary, ctx);
items.push(item);
item_summaries.push(item_summary);
} else {
@@ -145,35 +160,43 @@ impl<T: Item> SumTree<T> {
}
if leaf.is_some() {
- self.push_tree(SumTree(Arc::new(leaf.take().unwrap())));
+ self.push_tree(SumTree(Arc::new(leaf.take().unwrap())), ctx);
}
}
- pub fn push(&mut self, item: T) {
+ pub fn push(&mut self, item: T, ctx: &<T::Summary as Summary>::Context) {
let summary = item.summary();
- self.push_tree(SumTree::from_child_trees(vec![SumTree(Arc::new(
- Node::Leaf {
- summary: summary.clone(),
- items: ArrayVec::from_iter(Some(item)),
- item_summaries: ArrayVec::from_iter(Some(summary)),
- },
- ))]))
- }
-
- pub fn push_tree(&mut self, other: Self) {
+ self.push_tree(
+ SumTree::from_child_trees(
+ vec![SumTree(Arc::new(Node::Leaf {
+ summary: summary.clone(),
+ items: ArrayVec::from_iter(Some(item)),
+ item_summaries: ArrayVec::from_iter(Some(summary)),
+ }))],
+ ctx,
+ ),
+ ctx,
+ )
+ }
+
+ pub fn push_tree(&mut self, other: Self, ctx: &<T::Summary as Summary>::Context) {
let other_node = other.0.clone();
if !other_node.is_leaf() || other_node.items().len() > 0 {
if self.0.height() < other_node.height() {
for tree in other_node.child_trees() {
- self.push_tree(tree.clone());
+ self.push_tree(tree.clone(), ctx);
}
- } else if let Some(split_tree) = self.push_tree_recursive(other) {
- *self = Self::from_child_trees(vec![self.clone(), split_tree]);
+ } else if let Some(split_tree) = self.push_tree_recursive(other, ctx) {
+ *self = Self::from_child_trees(vec![self.clone(), split_tree], ctx);
}
}
}
- fn push_tree_recursive(&mut self, other: SumTree<T>) -> Option<SumTree<T>> {
+ fn push_tree_recursive(
+ &mut self,
+ other: SumTree<T>,
+ ctx: &<T::Summary as Summary>::Context,
+ ) -> Option<SumTree<T>> {
match Arc::make_mut(&mut self.0) {
Node::Internal {
height,
@@ -183,7 +206,7 @@ impl<T: Item> SumTree<T> {
..
} => {
let other_node = other.0.clone();
- *summary += other_node.summary();
+ summary.add_summary(other_node.summary(), ctx);
let height_delta = *height - other_node.height();
let mut summaries_to_append = ArrayVec::<[T::Summary; 2 * TREE_BASE]>::new();
@@ -195,7 +218,10 @@ impl<T: Item> SumTree<T> {
summaries_to_append.push(other_node.summary().clone());
trees_to_append.push(other)
} else {
- let tree_to_append = child_trees.last_mut().unwrap().push_tree_recursive(other);
+ let tree_to_append = child_trees
+ .last_mut()
+ .unwrap()
+ .push_tree_recursive(other, ctx);
*child_summaries.last_mut().unwrap() =
child_trees.last().unwrap().0.summary().clone();
@@ -225,13 +251,13 @@ impl<T: Item> SumTree<T> {
left_trees = all_trees.by_ref().take(midpoint).collect();
right_trees = all_trees.collect();
}
- *summary = sum(left_summaries.iter());
+ *summary = sum(left_summaries.iter(), ctx);
*child_summaries = left_summaries;
*child_trees = left_trees;
Some(SumTree(Arc::new(Node::Internal {
height: *height,
- summary: sum(right_summaries.iter()),
+ summary: sum(right_summaries.iter(), ctx),
child_summaries: right_summaries,
child_trees: right_trees,
})))
@@ -270,14 +296,14 @@ impl<T: Item> SumTree<T> {
}
*items = left_items;
*item_summaries = left_summaries;
- *summary = sum(item_summaries.iter());
+ *summary = sum(item_summaries.iter(), ctx);
Some(SumTree(Arc::new(Node::Leaf {
items: right_items,
- summary: sum(right_summaries.iter()),
+ summary: sum(right_summaries.iter(), ctx),
item_summaries: right_summaries,
})))
} else {
- *summary += other_node.summary();
+ summary.add_summary(other_node.summary(), ctx);
items.extend(other_node.items().iter().cloned());
item_summaries.extend(other_node.child_summaries().iter().cloned());
None
@@ -286,13 +312,16 @@ impl<T: Item> SumTree<T> {
}
}
- fn from_child_trees(child_trees: Vec<SumTree<T>>) -> Self {
+ fn from_child_trees(
+ child_trees: Vec<SumTree<T>>,
+ ctx: &<T::Summary as Summary>::Context,
+ ) -> Self {
let height = child_trees[0].0.height() + 1;
let mut child_summaries = ArrayVec::new();
for child in &child_trees {
child_summaries.push(child.0.summary().clone());
}
- let summary = sum(child_summaries.iter());
+ let summary = sum(child_summaries.iter(), ctx);
SumTree(Arc::new(Node::Internal {
height,
summary,
@@ -322,17 +351,21 @@ impl<T: Item> SumTree<T> {
impl<T: KeyedItem> SumTree<T> {
#[allow(unused)]
- pub fn insert(&mut self, item: T) {
+ pub fn insert(&mut self, item: T, ctx: &<T::Summary as Summary>::Context) {
*self = {
let mut cursor = self.cursor::<T::Key, ()>();
- let mut new_tree = cursor.slice(&item.key(), SeekBias::Left);
- new_tree.push(item);
- new_tree.push_tree(cursor.suffix());
+ let mut new_tree = cursor.slice(&item.key(), SeekBias::Left, ctx);
+ new_tree.push(item, ctx);
+ new_tree.push_tree(cursor.suffix(ctx), ctx);
new_tree
};
}
- pub fn edit(&mut self, mut edits: Vec<Edit<T>>) -> Vec<T> {
+ pub fn edit(
+ &mut self,
+ mut edits: Vec<Edit<T>>,
+ ctx: &<T::Summary as Summary>::Context,
+ ) -> Vec<T> {
if edits.is_empty() {
return Vec::new();
}
@@ -345,7 +378,7 @@ impl<T: KeyedItem> SumTree<T> {
let mut new_tree = SumTree::new();
let mut buffered_items = Vec::new();
- cursor.seek(&T::Key::default(), SeekBias::Left);
+ cursor.seek(&T::Key::default(), SeekBias::Left, ctx);
for edit in edits {
let new_key = edit.key();
let mut old_item = cursor.item();
@@ -354,9 +387,9 @@ impl<T: KeyedItem> SumTree<T> {
.as_ref()
.map_or(false, |old_item| old_item.key() < new_key)
{
- new_tree.extend(buffered_items.drain(..));
- let slice = cursor.slice(&new_key, SeekBias::Left);
- new_tree.push_tree(slice);
+ new_tree.extend(buffered_items.drain(..), ctx);
+ let slice = cursor.slice(&new_key, SeekBias::Left, ctx);
+ new_tree.push_tree(slice, ctx);
old_item = cursor.item();
}
@@ -375,17 +408,17 @@ impl<T: KeyedItem> SumTree<T> {
}
}
- new_tree.extend(buffered_items);
- new_tree.push_tree(cursor.suffix());
+ new_tree.extend(buffered_items, ctx);
+ new_tree.push_tree(cursor.suffix(ctx), ctx);
new_tree
};
removed
}
- pub fn get(&self, key: &T::Key) -> Option<&T> {
+ pub fn get(&self, key: &T::Key, ctx: &<T::Summary as Summary>::Context) -> Option<&T> {
let mut cursor = self.cursor::<T::Key, ()>();
- if cursor.seek(key, SeekBias::Left) {
+ if cursor.seek(key, SeekBias::Left, ctx) {
cursor.item()
} else {
None
@@ -482,14 +515,14 @@ impl<T: KeyedItem> Edit<T> {
}
}
-fn sum<'a, T, I>(iter: I) -> T
+fn sum<'a, T, I>(iter: I, ctx: &T::Context) -> T
where
- T: 'a + Default + AddAssign<&'a T>,
+ T: 'a + Summary,
I: Iterator<Item = &'a T>,
{
let mut sum = T::default();
for value in iter {
- sum += value;
+ sum.add_summary(value, ctx);
}
sum
}
@@ -503,12 +536,12 @@ mod tests {
#[test]
fn test_extend_and_push_tree() {
let mut tree1 = SumTree::new();
- tree1.extend(0..20);
+ tree1.extend(0..20, &());
let mut tree2 = SumTree::new();
- tree2.extend(50..100);
+ tree2.extend(50..100, &());
- tree1.push_tree(tree2);
+ tree1.push_tree(tree2, &());
assert_eq!(tree1.items(), (0..20).chain(50..100).collect::<Vec<u8>>());
}
@@ -517,11 +550,12 @@ mod tests {
for seed in 0..100 {
use rand::{distributions, prelude::*};
+ dbg!(seed);
let rng = &mut StdRng::seed_from_u64(seed);
let mut tree = SumTree::<u8>::new();
let count = rng.gen_range(0..10);
- tree.extend(rng.sample_iter(distributions::Standard).take(count));
+ tree.extend(rng.sample_iter(distributions::Standard).take(count), &());
for _ in 0..5 {
let splice_end = rng.gen_range(0..tree.extent::<Count>().0 + 1);
@@ -538,10 +572,10 @@ mod tests {
tree = {
let mut cursor = tree.cursor::<Count, ()>();
- let mut new_tree = cursor.slice(&Count(splice_start), SeekBias::Right);
- new_tree.extend(new_items);
- cursor.seek(&Count(splice_end), SeekBias::Right);
- new_tree.push_tree(cursor.slice(&tree_end, SeekBias::Right));
+ let mut new_tree = cursor.slice(&Count(splice_start), SeekBias::Right, &());
+ new_tree.extend(new_items, &());
+ cursor.seek(&Count(splice_end), SeekBias::Right, &());
+ new_tree.push_tree(cursor.slice(&tree_end, SeekBias::Right, &()), &());
new_tree
};
@@ -564,7 +598,7 @@ mod tests {
let mut pos = rng.gen_range(0..tree.extent::<Count>().0 + 1);
let mut before_start = false;
let mut cursor = tree.cursor::<Count, Count>();
- cursor.seek(&Count(pos), SeekBias::Right);
+ cursor.seek(&Count(pos), SeekBias::Right, &());
for i in 0..10 {
assert_eq!(cursor.start().0, pos);
@@ -612,11 +646,11 @@ mod tests {
};
let mut cursor = tree.cursor::<Count, ()>();
- cursor.seek(&Count(start), start_bias);
- let slice = cursor.slice(&Count(end), end_bias);
+ cursor.seek(&Count(start), start_bias, &());
+ let slice = cursor.slice(&Count(end), end_bias, &());
- cursor.seek(&Count(start), start_bias);
- let summary = cursor.summary::<Sum>(&Count(end), end_bias);
+ cursor.seek(&Count(start), start_bias, &());
+ let summary = cursor.summary::<Sum>(&Count(end), end_bias, &());
assert_eq!(summary, slice.summary().sum);
}
@@ -629,7 +663,7 @@ mod tests {
let tree = SumTree::<u8>::new();
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(
- cursor.slice(&Count(0), SeekBias::Right).items(),
+ cursor.slice(&Count(0), SeekBias::Right, &()).items(),
Vec::<u8>::new()
);
assert_eq!(cursor.item(), None);
@@ -638,10 +672,10 @@ mod tests {
// Single-element tree
let mut tree = SumTree::<u8>::new();
- tree.extend(vec![1]);
+ tree.extend(vec![1], &());
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(
- cursor.slice(&Count(0), SeekBias::Right).items(),
+ cursor.slice(&Count(0), SeekBias::Right, &()).items(),
Vec::<u8>::new()
);
assert_eq!(cursor.item(), Some(&1));
@@ -659,15 +693,15 @@ mod tests {
assert_eq!(cursor.start(), &Sum(0));
let mut cursor = tree.cursor::<Count, Sum>();
- assert_eq!(cursor.slice(&Count(1), SeekBias::Right).items(), [1]);
+ assert_eq!(cursor.slice(&Count(1), SeekBias::Right, &()).items(), [1]);
assert_eq!(cursor.item(), None);
assert_eq!(cursor.prev_item(), Some(&1));
assert_eq!(cursor.start(), &Sum(1));
- cursor.seek(&Count(0), SeekBias::Right);
+ cursor.seek(&Count(0), SeekBias::Right, &());
assert_eq!(
cursor
- .slice(&tree.extent::<Count>(), SeekBias::Right)
+ .slice(&tree.extent::<Count>(), SeekBias::Right, &())
.items(),
[1]
);
@@ -677,10 +711,13 @@ mod tests {
// Multiple-element tree
let mut tree = SumTree::new();
- tree.extend(vec![1, 2, 3, 4, 5, 6]);
+ tree.extend(vec![1, 2, 3, 4, 5, 6], &());
let mut cursor = tree.cursor::<Count, Sum>();
- assert_eq!(cursor.slice(&Count(2), SeekBias::Right).items(), [1, 2]);
+ assert_eq!(
+ cursor.slice(&Count(2), SeekBias::Right, &()).items(),
+ [1, 2]
+ );
assert_eq!(cursor.item(), Some(&3));
assert_eq!(cursor.prev_item(), Some(&2));
assert_eq!(cursor.start(), &Sum(3));
@@ -749,7 +786,7 @@ mod tests {
let mut cursor = tree.cursor::<Count, Sum>();
assert_eq!(
cursor
- .slice(&tree.extent::<Count>(), SeekBias::Right)
+ .slice(&tree.extent::<Count>(), SeekBias::Right, &())
.items(),
tree.items()
);
@@ -757,10 +794,10 @@ mod tests {
assert_eq!(cursor.prev_item(), Some(&6));
assert_eq!(cursor.start(), &Sum(21));
- cursor.seek(&Count(3), SeekBias::Right);
+ cursor.seek(&Count(3), SeekBias::Right, &());
assert_eq!(
cursor
- .slice(&tree.extent::<Count>(), SeekBias::Right)
+ .slice(&tree.extent::<Count>(), SeekBias::Right, &())
.items(),
[4, 5, 6]
);
@@ -769,37 +806,46 @@ mod tests {
assert_eq!(cursor.start(), &Sum(21));
// Seeking can bias left or right
- cursor.seek(&Count(1), SeekBias::Left);
+ cursor.seek(&Count(1), SeekBias::Left, &());
assert_eq!(cursor.item(), Some(&1));
- cursor.seek(&Count(1), SeekBias::Right);
+ cursor.seek(&Count(1), SeekBias::Right, &());
assert_eq!(cursor.item(), Some(&2));
// Slicing without resetting starts from where the cursor is parked at.
- cursor.seek(&Count(1), SeekBias::Right);
- assert_eq!(cursor.slice(&Count(3), SeekBias::Right).items(), vec![2, 3]);
- assert_eq!(cursor.slice(&Count(6), SeekBias::Left).items(), vec![4, 5]);
- assert_eq!(cursor.slice(&Count(6), SeekBias::Right).items(), vec![6]);
+ cursor.seek(&Count(1), SeekBias::Right, &());
+ assert_eq!(
+ cursor.slice(&Count(3), SeekBias::Right, &()).items(),
+ vec![2, 3]
+ );
+ assert_eq!(
+ cursor.slice(&Count(6), SeekBias::Left, &()).items(),
+ vec![4, 5]
+ );
+ assert_eq!(
+ cursor.slice(&Count(6), SeekBias::Right, &()).items(),
+ vec![6]
+ );
}
#[test]
fn test_edit() {
let mut tree = SumTree::<u8>::new();
- let removed = tree.edit(vec![Edit::Insert(1), Edit::Insert(2), Edit::Insert(0)]);
+ let removed = tree.edit(vec![Edit::Insert(1), Edit::Insert(2), Edit::Insert(0)], &());
assert_eq!(tree.items(), vec![0, 1, 2]);
assert_eq!(removed, Vec::<u8>::new());
- assert_eq!(tree.get(&0), Some(&0));
- assert_eq!(tree.get(&1), Some(&1));
- assert_eq!(tree.get(&2), Some(&2));
- assert_eq!(tree.get(&4), None);
+ assert_eq!(tree.get(&0, &()), Some(&0));
+ assert_eq!(tree.get(&1, &()), Some(&1));
+ assert_eq!(tree.get(&2, &()), Some(&2));
+ assert_eq!(tree.get(&4, &()), None);
- let removed = tree.edit(vec![Edit::Insert(2), Edit::Insert(4), Edit::Remove(0)]);
+ let removed = tree.edit(vec![Edit::Insert(2), Edit::Insert(4), Edit::Remove(0)], &());
assert_eq!(tree.items(), vec![1, 2, 4]);
assert_eq!(removed, vec![0, 2]);
- assert_eq!(tree.get(&0), None);
- assert_eq!(tree.get(&1), Some(&1));
- assert_eq!(tree.get(&2), Some(&2));
- assert_eq!(tree.get(&4), Some(&4));
+ assert_eq!(tree.get(&0, &()), None);
+ assert_eq!(tree.get(&1, &()), Some(&1));
+ assert_eq!(tree.get(&2, &()), Some(&2));
+ assert_eq!(tree.get(&4, &()), Some(&4));
}
#[derive(Clone, Default, Debug)]
@@ -837,14 +883,10 @@ mod tests {
}
}
- impl<'a> Dimension<'a, IntegersSummary> for u8 {
- fn add_summary(&mut self, summary: &IntegersSummary) {
- *self = summary.max;
- }
- }
+ impl Summary for IntegersSummary {
+ type Context = ();
- impl<'a> AddAssign<&'a Self> for IntegersSummary {
- fn add_assign(&mut self, other: &Self) {
+ fn add_summary(&mut self, other: &Self, _: &()) {
self.count.0 += &other.count.0;
self.sum.0 += &other.sum.0;
self.contains_even |= other.contains_even;
@@ -852,6 +894,12 @@ mod tests {
}
}
+ impl<'a> Dimension<'a, IntegersSummary> for u8 {
+ fn add_summary(&mut self, summary: &IntegersSummary) {
+ *self = summary.max;
+ }
+ }
+
impl<'a> Dimension<'a, IntegersSummary> for Count {
fn add_summary(&mut self, summary: &IntegersSummary) {
self.0 += summary.count.0;
@@ -30,37 +30,6 @@ where
}
}
-pub fn find_insertion_index<'a, F, T, E>(slice: &'a [T], mut f: F) -> Result<usize, E>
-where
- F: FnMut(&'a T) -> Result<Ordering, E>,
-{
- 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: Rng>(T);
impl<T: Rng> RandomCharIter<T> {
@@ -85,14 +54,6 @@ impl<T: Rng> Iterator for RandomCharIter<T> {
mod tests {
use super::*;
- #[test]
- fn test_find_insertion_index() {
- assert_eq!(
- find_insertion_index(&[0, 4, 8], |probe| Ok::<Ordering, ()>(probe.cmp(&2))),
- Ok(1)
- );
- }
-
#[test]
fn test_extend_sorted() {
let mut vec = vec![];
@@ -1,41 +1,42 @@
pub mod pane;
pub mod pane_group;
+use crate::{
+ editor::{Buffer, BufferView},
+ settings::Settings,
+ time::ReplicaId,
+ worktree::{FileHandle, Worktree, WorktreeHandle},
+};
+use futures_core::{future::LocalBoxFuture, Future};
+use gpui::{
+ color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
+ ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, PathPromptOptions, View,
+ ViewContext, ViewHandle, WeakModelHandle,
+};
+use log::error;
pub use pane::*;
pub use pane_group::*;
-
-use crate::settings::Settings;
-use gpui::{MutableAppContext, PathPromptOptions};
use postage::watch;
-use std::path::PathBuf;
+use smol::prelude::*;
+use std::{collections::HashMap, path::PathBuf};
+use std::{
+ collections::{hash_map::Entry, HashSet},
+ path::Path,
+ sync::Arc,
+};
+
pub fn init(app: &mut MutableAppContext) {
app.add_global_action("workspace:open", open);
app.add_global_action("workspace:open_paths", open_paths);
app.add_global_action("app:quit", quit);
app.add_action("workspace:save", Workspace::save_active_item);
app.add_action("workspace:debug_elements", Workspace::debug_elements);
+ app.add_action("workspace:new_file", Workspace::open_new_file);
app.add_bindings(vec![
Binding::new("cmd-s", "workspace:save", None),
Binding::new("cmd-alt-i", "workspace:debug_elements", None),
]);
pane::init(app);
}
-use crate::{
- editor::{Buffer, BufferView},
- time::ReplicaId,
- worktree::{Worktree, WorktreeHandle},
-};
-use futures_core::{future::LocalBoxFuture, Future};
-use gpui::{
- color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
- ClipboardItem, Entity, EntityTask, ModelHandle, View, ViewContext, ViewHandle,
-};
-use log::error;
-use smol::prelude::*;
-use std::{
- collections::{hash_map::Entry, HashMap, HashSet},
- path::Path,
- sync::Arc,
-};
pub struct OpenParams {
pub paths: Vec<PathBuf>,
@@ -94,6 +95,18 @@ fn quit(_: &(), app: &mut MutableAppContext) {
app.platform().quit();
}
+pub trait Item: Entity + Sized {
+ type View: ItemView;
+
+ fn build_view(
+ handle: ModelHandle<Self>,
+ settings: watch::Receiver<Settings>,
+ ctx: &mut ViewContext<Self::View>,
+ ) -> Self::View;
+
+ fn file(&self) -> Option<&FileHandle>;
+}
+
pub trait ItemView: View {
fn title(&self, app: &AppContext) -> String;
fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)>;
@@ -106,9 +119,11 @@ pub trait ItemView: View {
fn is_dirty(&self, _: &AppContext) -> bool {
false
}
- fn save(&self, _: &mut ViewContext<Self>) -> LocalBoxFuture<'static, anyhow::Result<()>> {
- Box::pin(async { Ok(()) })
- }
+ fn save(
+ &mut self,
+ _: Option<FileHandle>,
+ _: &mut ViewContext<Self>,
+ ) -> LocalBoxFuture<'static, anyhow::Result<()>>;
fn should_activate_item_on_event(_: &Self::Event) -> bool {
false
}
@@ -117,6 +132,22 @@ pub trait ItemView: View {
}
}
+pub trait ItemHandle: Send + Sync {
+ fn boxed_clone(&self) -> Box<dyn ItemHandle>;
+ fn downgrade(&self) -> Box<dyn WeakItemHandle>;
+}
+
+pub trait WeakItemHandle: Send + Sync {
+ fn file<'a>(&'a self, ctx: &'a AppContext) -> Option<&'a FileHandle>;
+ fn add_view(
+ &self,
+ window_id: usize,
+ settings: watch::Receiver<Settings>,
+ app: &mut MutableAppContext,
+ ) -> Option<Box<dyn ItemViewHandle>>;
+ fn alive(&self, ctx: &AppContext) -> bool;
+}
+
pub trait ItemViewHandle: Send + Sync {
fn title(&self, app: &AppContext) -> String;
fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)>;
@@ -126,7 +157,46 @@ pub trait ItemViewHandle: Send + Sync {
fn id(&self) -> usize;
fn to_any(&self) -> AnyViewHandle;
fn is_dirty(&self, ctx: &AppContext) -> bool;
- fn save(&self, ctx: &mut MutableAppContext) -> LocalBoxFuture<'static, anyhow::Result<()>>;
+ fn save(
+ &self,
+ file: Option<FileHandle>,
+ ctx: &mut MutableAppContext,
+ ) -> LocalBoxFuture<'static, anyhow::Result<()>>;
+}
+
+impl<T: Item> ItemHandle for ModelHandle<T> {
+ fn boxed_clone(&self) -> Box<dyn ItemHandle> {
+ Box::new(self.clone())
+ }
+
+ fn downgrade(&self) -> Box<dyn WeakItemHandle> {
+ Box::new(self.downgrade())
+ }
+}
+
+impl<T: Item> WeakItemHandle for WeakModelHandle<T> {
+ fn file<'a>(&'a self, ctx: &'a AppContext) -> Option<&'a FileHandle> {
+ self.upgrade(ctx).and_then(|h| h.read(ctx).file())
+ }
+
+ fn add_view(
+ &self,
+ window_id: usize,
+ settings: watch::Receiver<Settings>,
+ ctx: &mut MutableAppContext,
+ ) -> Option<Box<dyn ItemViewHandle>> {
+ if let Some(handle) = self.upgrade(ctx.as_ref()) {
+ Some(Box::new(ctx.add_view(window_id, |ctx| {
+ T::build_view(handle, settings, ctx)
+ })))
+ } else {
+ None
+ }
+ }
+
+ fn alive(&self, ctx: &AppContext) -> bool {
+ self.upgrade(ctx).is_some()
+ }
}
impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
@@ -165,8 +235,12 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
})
}
- fn save(&self, ctx: &mut MutableAppContext) -> LocalBoxFuture<'static, anyhow::Result<()>> {
- self.update(ctx, |item, ctx| item.save(ctx))
+ fn save(
+ &self,
+ file: Option<FileHandle>,
+ ctx: &mut MutableAppContext,
+ ) -> LocalBoxFuture<'static, anyhow::Result<()>> {
+ self.update(ctx, |item, ctx| item.save(file, ctx))
}
fn is_dirty(&self, ctx: &AppContext) -> bool {
@@ -188,6 +262,12 @@ impl Clone for Box<dyn ItemViewHandle> {
}
}
+impl Clone for Box<dyn ItemHandle> {
+ fn clone(&self) -> Box<dyn ItemHandle> {
+ self.boxed_clone()
+ }
+}
+
#[derive(Debug)]
pub struct State {
pub modal: Option<usize>,
@@ -200,12 +280,12 @@ pub struct Workspace {
center: PaneGroup,
panes: Vec<ViewHandle<Pane>>,
active_pane: ViewHandle<Pane>,
- loading_entries: HashSet<(usize, Arc<Path>)>,
replica_id: ReplicaId,
worktrees: HashSet<ModelHandle<Worktree>>,
- buffers: HashMap<
- (usize, u64),
- postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
+ items: Vec<Box<dyn WeakItemHandle>>,
+ loading_items: HashMap<
+ (usize, Arc<Path>),
+ postage::watch::Receiver<Option<Result<Box<dyn ItemHandle>, Arc<anyhow::Error>>>>,
>,
}
@@ -227,11 +307,11 @@ impl Workspace {
center: PaneGroup::new(pane.id()),
panes: vec![pane.clone()],
active_pane: pane.clone(),
- loading_entries: HashSet::new(),
settings,
replica_id,
worktrees: Default::default(),
- buffers: Default::default(),
+ items: Default::default(),
+ loading_items: Default::default(),
}
}
@@ -270,15 +350,7 @@ impl Workspace {
let entries = paths
.iter()
.cloned()
- .map(|path| {
- for tree in self.worktrees.iter() {
- if let Ok(relative_path) = path.strip_prefix(tree.read(ctx).abs_path()) {
- return (tree.id(), relative_path.into());
- }
- }
- let worktree_id = self.add_worktree(&path, ctx);
- (worktree_id, Path::new("").into())
- })
+ .map(|path| self.file_for_path(&path, ctx))
.collect::<Vec<_>>();
let bg = ctx.background_executor().clone();
@@ -286,12 +358,12 @@ impl Workspace {
.iter()
.cloned()
.zip(entries.into_iter())
- .map(|(path, entry)| {
+ .map(|(abs_path, file)| {
ctx.spawn(
- bg.spawn(async move { path.is_file() }),
- |me, is_file, ctx| {
+ bg.spawn(async move { abs_path.is_file() }),
+ move |me, is_file, ctx| {
if is_file {
- me.open_entry(entry, ctx)
+ me.open_entry(file.entry_id(), ctx)
} else {
None
}
@@ -308,13 +380,26 @@ impl Workspace {
}
}
- pub fn add_worktree(&mut self, path: &Path, ctx: &mut ViewContext<Self>) -> usize {
+ fn file_for_path(&mut self, abs_path: &Path, ctx: &mut ViewContext<Self>) -> FileHandle {
+ for tree in self.worktrees.iter() {
+ if let Ok(relative_path) = abs_path.strip_prefix(tree.read(ctx).abs_path()) {
+ return tree.file(relative_path, ctx.as_ref());
+ }
+ }
+ let worktree = self.add_worktree(&abs_path, ctx);
+ worktree.file(Path::new(""), ctx.as_ref())
+ }
+
+ pub fn add_worktree(
+ &mut self,
+ path: &Path,
+ ctx: &mut ViewContext<Self>,
+ ) -> ModelHandle<Worktree> {
let worktree = ctx.add_model(|ctx| Worktree::new(path, ctx));
- let worktree_id = worktree.id();
ctx.observe_model(&worktree, |_, _, ctx| ctx.notify());
- self.worktrees.insert(worktree);
+ self.worktrees.insert(worktree.clone());
ctx.notify();
- worktree_id
+ worktree
}
pub fn toggle_modal<V, F>(&mut self, ctx: &mut ViewContext<Self>, add_view: F)
@@ -344,16 +429,22 @@ impl Workspace {
}
}
+ pub fn open_new_file(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+ let buffer = ctx.add_model(|ctx| Buffer::new(self.replica_id, "", ctx));
+ let buffer_view =
+ ctx.add_view(|ctx| BufferView::for_buffer(buffer.clone(), self.settings.clone(), ctx));
+ self.items.push(ItemHandle::downgrade(&buffer));
+ self.add_item_view(Box::new(buffer_view), ctx);
+ }
+
#[must_use]
pub fn open_entry(
&mut self,
entry: (usize, Arc<Path>),
ctx: &mut ViewContext<Self>,
) -> Option<EntityTask<()>> {
- if self.loading_entries.contains(&entry) {
- return None;
- }
-
+ // If the active pane contains a view for this file, then activate
+ // that item view.
if self
.active_pane()
.update(ctx, |pane, ctx| pane.activate_entry(entry.clone(), ctx))
@@ -361,6 +452,32 @@ impl Workspace {
return None;
}
+ // Otherwise, if this file is already open somewhere in the workspace,
+ // then add another view for it.
+ let settings = self.settings.clone();
+ let mut view_for_existing_item = None;
+ self.items.retain(|item| {
+ if item.alive(ctx.as_ref()) {
+ if view_for_existing_item.is_none()
+ && item
+ .file(ctx.as_ref())
+ .map_or(false, |f| f.entry_id() == entry)
+ {
+ view_for_existing_item = Some(
+ item.add_view(ctx.window_id(), settings.clone(), ctx.as_mut())
+ .unwrap(),
+ );
+ }
+ true
+ } else {
+ false
+ }
+ });
+ if let Some(view) = view_for_existing_item {
+ self.add_item_view(view, ctx);
+ return None;
+ }
+
let (worktree_id, path) = entry.clone();
let worktree = match self.worktrees.get(&worktree_id).cloned() {
@@ -371,42 +488,31 @@ impl Workspace {
}
};
- let inode = match worktree.read(ctx).inode_for_path(&path) {
- Some(inode) => inode,
- None => {
- log::error!("path {:?} does not exist", path);
- return None;
- }
- };
-
- let file = match worktree.file(path.clone(), ctx.as_ref()) {
- Some(file) => file,
- None => {
- log::error!("path {:?} does not exist", path);
- return None;
- }
- };
-
- self.loading_entries.insert(entry.clone());
+ let file = worktree.file(path.clone(), ctx.as_ref());
+ if file.is_deleted() {
+ log::error!("path {:?} does not exist", path);
+ return None;
+ }
- if let Entry::Vacant(entry) = self.buffers.entry((worktree_id, inode)) {
+ if let Entry::Vacant(entry) = self.loading_items.entry(entry.clone()) {
let (mut tx, rx) = postage::watch::channel();
entry.insert(rx);
- let history = file.load_history(ctx.as_ref());
let replica_id = self.replica_id;
- let buffer = ctx
+ let history = ctx
.background_executor()
- .spawn(async move { Ok(Buffer::from_history(replica_id, history.await?)) });
- ctx.spawn(buffer, move |_, from_history_result, ctx| {
- *tx.borrow_mut() = Some(match from_history_result {
- Ok(buffer) => Ok(ctx.add_model(|_| buffer)),
+ .spawn(file.load_history(ctx.as_ref()));
+ ctx.spawn(history, move |_, history, ctx| {
+ *tx.borrow_mut() = Some(match history {
+ Ok(history) => Ok(Box::new(ctx.add_model(|ctx| {
+ Buffer::from_history(replica_id, history, Some(file), ctx)
+ }))),
Err(error) => Err(Arc::new(error)),
})
})
.detach()
}
- let mut watch = self.buffers.get(&(worktree_id, inode)).unwrap().clone();
+ let mut watch = self.loading_items.get(&entry).unwrap().clone();
Some(ctx.spawn(
async move {
loop {
@@ -417,18 +523,15 @@ impl Workspace {
}
},
move |me, load_result, ctx| {
- me.loading_entries.remove(&entry);
+ me.loading_items.remove(&entry);
match load_result {
- Ok(buffer_handle) => {
- let buffer_view = Box::new(ctx.add_view(|ctx| {
- BufferView::for_buffer(
- buffer_handle,
- Some(file),
- me.settings.clone(),
- ctx,
- )
- }));
- me.add_item(buffer_view, ctx);
+ Ok(item) => {
+ let weak_item = item.downgrade();
+ let view = weak_item
+ .add_view(ctx.window_id(), settings, ctx.as_mut())
+ .unwrap();
+ me.items.push(weak_item);
+ me.add_item_view(view, ctx);
}
Err(error) => {
log::error!("error opening item: {}", error);
@@ -438,19 +541,45 @@ impl Workspace {
))
}
+ pub fn active_item(&self, ctx: &ViewContext<Self>) -> Option<Box<dyn ItemViewHandle>> {
+ self.active_pane().read(ctx).active_item()
+ }
+
pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
- self.active_pane.update(ctx, |pane, ctx| {
- if let Some(item) = pane.active_item() {
- let task = item.save(ctx.as_mut());
- ctx.spawn(task, |_, result, _| {
- if let Err(e) = result {
- // TODO - present this error to the user
- error!("failed to save item: {:?}, ", e);
+ if let Some(item) = self.active_item(ctx) {
+ if item.entry_id(ctx.as_ref()).is_none() {
+ let handle = ctx.handle();
+ let start_path = self
+ .worktrees
+ .iter()
+ .next()
+ .map_or(Path::new(""), |h| h.read(ctx).abs_path())
+ .to_path_buf();
+ ctx.prompt_for_new_path(&start_path, move |path, ctx| {
+ if let Some(path) = path {
+ handle.update(ctx, move |this, ctx| {
+ let file = this.file_for_path(&path, ctx);
+ let task = item.save(Some(file), ctx.as_mut());
+ ctx.spawn(task, move |_, result, _| {
+ if let Err(e) = result {
+ error!("failed to save item: {:?}, ", e);
+ }
+ })
+ .detach()
+ })
}
- })
- .detach()
+ });
+ return;
}
- });
+
+ let task = item.save(None, ctx.as_mut());
+ ctx.spawn(task, |_, result, _| {
+ if let Err(e) = result {
+ error!("failed to save item: {:?}, ", e);
+ }
+ })
+ .detach()
+ }
}
pub fn debug_elements(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
@@ -519,7 +648,7 @@ impl Workspace {
self.activate_pane(new_pane.clone(), ctx);
if let Some(item) = pane.read(ctx).active_item() {
if let Some(clone) = item.clone_on_split(ctx.as_mut()) {
- self.add_item(clone, ctx);
+ self.add_item_view(clone, ctx);
}
}
self.center
@@ -544,7 +673,7 @@ impl Workspace {
&self.active_pane
}
- fn add_item(&self, item: Box<dyn ItemViewHandle>, ctx: &mut ViewContext<Self>) {
+ fn add_item_view(&self, item: Box<dyn ItemViewHandle>, ctx: &mut ViewContext<Self>) {
let active_pane = self.active_pane();
item.set_parent_pane(&active_pane, ctx.as_mut());
active_pane.update(ctx, |pane, ctx| {
@@ -607,7 +736,9 @@ mod tests {
use crate::{editor::BufferView, settings, test::temp_tree};
use gpui::App;
use serde_json::json;
- use std::{collections::HashSet, os::unix};
+ use std::collections::HashSet;
+ use std::time;
+ use tempdir::TempDir;
#[test]
fn test_open_paths_action() {
@@ -696,15 +827,13 @@ mod tests {
let file2 = entries[1].clone();
let file3 = entries[2].clone();
- let pane = app.read(|ctx| workspace.read(ctx).active_pane().clone());
-
// Open the first entry
workspace
.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
.unwrap()
.await;
app.read(|ctx| {
- let pane = pane.read(ctx);
+ let pane = workspace.read(ctx).active_pane().read(ctx);
assert_eq!(
pane.active_item().unwrap().entry_id(ctx),
Some(file1.clone())
@@ -718,7 +847,7 @@ mod tests {
.unwrap()
.await;
app.read(|ctx| {
- let pane = pane.read(ctx);
+ let pane = workspace.read(ctx).active_pane().read(ctx);
assert_eq!(
pane.active_item().unwrap().entry_id(ctx),
Some(file2.clone())
@@ -731,7 +860,7 @@ mod tests {
assert!(w.open_entry(file1.clone(), ctx).is_none())
});
app.read(|ctx| {
- let pane = pane.read(ctx);
+ let pane = workspace.read(ctx).active_pane().read(ctx);
assert_eq!(
pane.active_item().unwrap().entry_id(ctx),
Some(file1.clone())
@@ -739,21 +868,42 @@ mod tests {
assert_eq!(pane.items().len(), 2);
});
- // Open the third entry twice concurrently. Only one pane item is added.
- workspace
- .update(&mut app, |w, ctx| {
- let task = w.open_entry(file3.clone(), ctx).unwrap();
- assert!(w.open_entry(file3.clone(), ctx).is_none());
- task
- })
- .await;
+ // Split the pane with the first entry, then open the second entry again.
+ workspace.update(&mut app, |w, ctx| {
+ w.split_pane(w.active_pane().clone(), SplitDirection::Right, ctx);
+ assert!(w.open_entry(file2.clone(), ctx).is_none());
+ assert_eq!(
+ w.active_pane()
+ .read(ctx)
+ .active_item()
+ .unwrap()
+ .entry_id(ctx.as_ref()),
+ Some(file2.clone())
+ );
+ });
+
+ // Open the third entry twice concurrently. Two pane items
+ // are added.
+ let (t1, t2) = workspace.update(&mut app, |w, ctx| {
+ (
+ w.open_entry(file3.clone(), ctx).unwrap(),
+ w.open_entry(file3.clone(), ctx).unwrap(),
+ )
+ });
+ t1.await;
+ t2.await;
app.read(|ctx| {
- let pane = pane.read(ctx);
+ let pane = workspace.read(ctx).active_pane().read(ctx);
assert_eq!(
pane.active_item().unwrap().entry_id(ctx),
Some(file3.clone())
);
- assert_eq!(pane.items().len(), 3);
+ let pane_entries = pane
+ .items()
+ .iter()
+ .map(|i| i.entry_id(ctx).unwrap())
+ .collect::<Vec<_>>();
+ assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]);
});
});
}
@@ -830,63 +980,100 @@ mod tests {
}
#[test]
- fn test_open_two_paths_to_the_same_file() {
- use crate::workspace::ItemViewHandle;
-
+ fn test_open_and_save_new_file() {
App::test_async((), |mut app| async move {
- // Create a worktree with a symlink:
- // dir
- // ├── hello.txt
- // └── hola.txt -> hello.txt
- let temp_dir = temp_tree(json!({ "hello.txt": "hi" }));
- let dir = temp_dir.path();
- unix::fs::symlink(dir.join("hello.txt"), dir.join("hola.txt")).unwrap();
-
+ let dir = TempDir::new("test-new-file").unwrap();
let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, workspace) = app.add_window(|ctx| {
let mut workspace = Workspace::new(0, settings, ctx);
- workspace.add_worktree(dir, ctx);
+ workspace.add_worktree(dir.path(), ctx);
workspace
});
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
-
- // Simultaneously open both the original file and the symlink to the same file.
- app.update(|ctx| {
- workspace.update(ctx, |view, ctx| {
- view.open_paths(&[dir.join("hello.txt"), dir.join("hola.txt")], ctx)
- })
- })
- .await;
-
- // The same content shows up with two different editors.
- let buffer_views = app.read(|ctx| {
+ let worktree = app.read(|ctx| {
workspace
.read(ctx)
- .active_pane()
- .read(ctx)
- .items()
+ .worktrees()
.iter()
- .map(|i| i.to_any().downcast::<BufferView>().unwrap())
- .collect::<Vec<_>>()
+ .next()
+ .unwrap()
+ .clone()
});
- app.read(|ctx| {
- assert_eq!(buffer_views[0].title(ctx), "hello.txt");
- assert_eq!(buffer_views[1].title(ctx), "hola.txt");
- assert_eq!(buffer_views[0].read(ctx).text(ctx), "hi");
- assert_eq!(buffer_views[1].read(ctx).text(ctx), "hi");
+
+ // Create a new untitled buffer
+ let editor = workspace.update(&mut app, |workspace, ctx| {
+ workspace.open_new_file(&(), ctx);
+ workspace
+ .active_item(ctx)
+ .unwrap()
+ .to_any()
+ .downcast::<BufferView>()
+ .unwrap()
+ });
+ editor.update(&mut app, |editor, ctx| {
+ assert!(!editor.is_dirty(ctx.as_ref()));
+ assert_eq!(editor.title(ctx.as_ref()), "untitled");
+ editor.insert(&"hi".to_string(), ctx);
+ assert!(editor.is_dirty(ctx.as_ref()));
});
- // When modifying one buffer, the changes appear in both editors.
- app.update(|ctx| {
- buffer_views[0].update(ctx, |buf, ctx| {
- buf.insert(&"oh, ".to_string(), ctx);
- });
+ // Save the buffer. This prompts for a filename.
+ workspace.update(&mut app, |workspace, ctx| {
+ workspace.save_active_item(&(), ctx)
+ });
+ app.simulate_new_path_selection(|parent_dir| {
+ assert_eq!(parent_dir, dir.path());
+ Some(parent_dir.join("the-new-name"))
});
app.read(|ctx| {
- assert_eq!(buffer_views[0].read(ctx).text(ctx), "oh, hi");
- assert_eq!(buffer_views[1].read(ctx).text(ctx), "oh, hi");
+ assert!(editor.is_dirty(ctx));
+ assert_eq!(editor.title(ctx), "untitled");
+ });
+
+ // When the save completes, the buffer's title is updated.
+ editor
+ .condition(&app, |editor, ctx| !editor.is_dirty(ctx))
+ .await;
+ worktree
+ .condition_with_duration(time::Duration::from_millis(500), &app, |worktree, _| {
+ worktree.inode_for_path("the-new-name").is_some()
+ })
+ .await;
+ app.read(|ctx| assert_eq!(editor.title(ctx), "the-new-name"));
+
+ // Edit the file and save it again. This time, there is no filename prompt.
+ editor.update(&mut app, |editor, ctx| {
+ editor.insert(&" there".to_string(), ctx);
+ assert_eq!(editor.is_dirty(ctx.as_ref()), true);
+ });
+ workspace.update(&mut app, |workspace, ctx| {
+ workspace.save_active_item(&(), ctx)
+ });
+ assert!(!app.did_prompt_for_new_path());
+ editor
+ .condition(&app, |editor, ctx| !editor.is_dirty(ctx))
+ .await;
+ app.read(|ctx| assert_eq!(editor.title(ctx), "the-new-name"));
+
+ // Open the same newly-created file in another pane item. The new editor should reuse
+ // the same buffer.
+ workspace.update(&mut app, |workspace, ctx| {
+ workspace.open_new_file(&(), ctx);
+ workspace.split_pane(workspace.active_pane().clone(), SplitDirection::Right, ctx);
+ assert!(workspace
+ .open_entry((worktree.id(), Path::new("the-new-name").into()), ctx)
+ .is_none());
+ });
+ let editor2 = workspace.update(&mut app, |workspace, ctx| {
+ workspace
+ .active_item(ctx)
+ .unwrap()
+ .to_any()
+ .downcast::<BufferView>()
+ .unwrap()
});
+ app.read(|ctx| {
+ assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer());
+ })
});
}
@@ -9,7 +9,7 @@ use crate::{
use ::ignore::gitignore::Gitignore;
use anyhow::{Context, Result};
pub use fuzzy::{match_paths, PathMatch};
-use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task, View, ViewContext};
+use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use postage::{
@@ -24,7 +24,7 @@ use std::{
fmt, fs,
future::Future,
io::{self, Read, Write},
- ops::{AddAssign, Deref},
+ ops::Deref,
os::unix::{ffi::OsStrExt, fs::MetadataExt},
path::{Path, PathBuf},
sync::{Arc, Weak},
@@ -53,7 +53,7 @@ pub struct Worktree {
poll_scheduled: bool,
}
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub struct FileHandle {
worktree: ModelHandle<Worktree>,
state: Arc<Mutex<FileHandleState>>,
@@ -249,9 +249,10 @@ impl Snapshot {
#[cfg(test)]
pub fn paths(&self) -> impl Iterator<Item = &Arc<Path>> {
- let mut cursor = self.entries.cursor::<(), ()>();
- cursor.next();
- cursor.map(|entry| entry.path())
+ self.entries
+ .cursor::<(), ()>()
+ .skip(1)
+ .map(|entry| entry.path())
}
pub fn visible_files(&self, start: usize) -> FileIter {
@@ -274,7 +275,7 @@ impl Snapshot {
fn entry_for_path(&self, path: impl AsRef<Path>) -> Option<&Entry> {
let mut cursor = self.entries.cursor::<_, ()>();
- if cursor.seek(&PathSearch::Exact(path.as_ref()), SeekBias::Left) {
+ if cursor.seek(&PathSearch::Exact(path.as_ref()), SeekBias::Left, &()) {
cursor.item()
} else {
None
@@ -296,7 +297,7 @@ impl Snapshot {
self.ignores
.insert(ignore_dir_path.into(), (Arc::new(ignore), self.scan_id));
}
- self.entries.insert(entry);
+ self.entries.insert(entry, &());
}
fn populate_dir(
@@ -309,7 +310,7 @@ impl Snapshot {
let mut parent_entry = self
.entries
- .get(&PathKey(parent_path.clone()))
+ .get(&PathKey(parent_path.clone()), &())
.unwrap()
.clone();
if let Some(ignore) = ignore {
@@ -325,15 +326,15 @@ impl Snapshot {
for entry in entries {
edits.push(Edit::Insert(entry));
}
- self.entries.edit(edits);
+ self.entries.edit(edits, &());
}
fn remove_path(&mut self, path: &Path) {
let new_entries = {
let mut cursor = self.entries.cursor::<_, ()>();
- let mut new_entries = cursor.slice(&PathSearch::Exact(path), SeekBias::Left);
- cursor.seek_forward(&PathSearch::Successor(path), SeekBias::Left);
- new_entries.push_tree(cursor.suffix());
+ let mut new_entries = cursor.slice(&PathSearch::Exact(path), SeekBias::Left, &());
+ cursor.seek_forward(&PathSearch::Successor(path), SeekBias::Left, &());
+ new_entries.push_tree(cursor.suffix(&()), &());
new_entries
};
self.entries = new_entries;
@@ -406,6 +407,10 @@ impl FileHandle {
self.state.lock().is_deleted
}
+ pub fn exists(&self) -> bool {
+ !self.is_deleted()
+ }
+
pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
self.worktree.read(ctx).load_history(&self.path(), ctx)
}
@@ -415,18 +420,22 @@ impl FileHandle {
worktree.save(&self.path(), content, ctx)
}
+ pub fn worktree_id(&self) -> usize {
+ self.worktree.id()
+ }
+
pub fn entry_id(&self) -> (usize, Arc<Path>) {
(self.worktree.id(), self.path())
}
- pub fn observe_from_view<T: View>(
+ pub fn observe_from_model<T: Entity>(
&self,
- ctx: &mut ViewContext<T>,
- mut callback: impl FnMut(&mut T, FileHandle, &mut ViewContext<T>) + 'static,
+ ctx: &mut ModelContext<T>,
+ mut callback: impl FnMut(&mut T, FileHandle, &mut ModelContext<T>) + 'static,
) {
let mut prev_state = self.state.lock().clone();
let cur_state = Arc::downgrade(&self.state);
- ctx.observe_model(&self.worktree, move |observer, worktree, ctx| {
+ ctx.observe(&self.worktree, move |observer, worktree, ctx| {
if let Some(cur_state) = cur_state.upgrade() {
let cur_state_unlocked = cur_state.lock();
if *cur_state_unlocked != prev_state {
@@ -535,8 +544,10 @@ impl Default for EntrySummary {
}
}
-impl<'a> AddAssign<&'a EntrySummary> for EntrySummary {
- fn add_assign(&mut self, rhs: &'a EntrySummary) {
+impl sum_tree::Summary for EntrySummary {
+ type Context = ();
+
+ fn add_summary(&mut self, rhs: &Self, _: &()) {
self.max_path = rhs.max_path.clone();
self.file_count += rhs.file_count;
self.visible_file_count += rhs.visible_file_count;
@@ -971,9 +982,8 @@ impl BackgroundScanner {
let snapshot = self.snapshot.lock();
handles.retain(|path, handle_state| {
if let Some(handle_state) = Weak::upgrade(&handle_state) {
- if snapshot.entry_for_path(&path).is_none() {
- handle_state.lock().is_deleted = true;
- }
+ let mut handle_state = handle_state.lock();
+ handle_state.is_deleted = snapshot.entry_for_path(&path).is_none();
true
} else {
false
@@ -1066,7 +1076,7 @@ impl BackgroundScanner {
edits.push(Edit::Insert(entry));
}
}
- self.snapshot.lock().entries.edit(edits);
+ self.snapshot.lock().entries.edit(edits, &());
}
fn fs_entry_for_path(&self, path: Arc<Path>, abs_path: &Path) -> Result<Option<Entry>> {
@@ -1126,31 +1136,38 @@ struct UpdateIgnoreStatusJob {
}
pub trait WorktreeHandle {
- fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Option<FileHandle>;
+ fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> FileHandle;
}
impl WorktreeHandle for ModelHandle<Worktree> {
- fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Option<FileHandle> {
+ fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> FileHandle {
+ let path = path.as_ref();
let tree = self.read(app);
- let entry = tree.entry_for_path(&path)?;
-
- let path = entry.path().clone();
let mut handles = tree.handles.lock();
- let state = if let Some(state) = handles.get(&path).and_then(Weak::upgrade) {
+ let state = if let Some(state) = handles.get(path).and_then(Weak::upgrade) {
state
} else {
- let state = Arc::new(Mutex::new(FileHandleState {
- path: path.clone(),
- is_deleted: false,
- }));
- handles.insert(path, Arc::downgrade(&state));
+ let handle_state = if let Some(entry) = tree.entry_for_path(path) {
+ FileHandleState {
+ path: entry.path().clone(),
+ is_deleted: false,
+ }
+ } else {
+ FileHandleState {
+ path: path.into(),
+ is_deleted: true,
+ }
+ };
+
+ let state = Arc::new(Mutex::new(handle_state.clone()));
+ handles.insert(handle_state.path, Arc::downgrade(&state));
state
};
- Some(FileHandle {
+ FileHandle {
worktree: self.clone(),
state,
- })
+ }
}
}
@@ -1162,13 +1179,13 @@ pub enum FileIter<'a> {
impl<'a> FileIter<'a> {
fn all(snapshot: &'a Snapshot, start: usize) -> Self {
let mut cursor = snapshot.entries.cursor();
- cursor.seek(&FileCount(start), SeekBias::Right);
+ cursor.seek(&FileCount(start), SeekBias::Right, &());
Self::All(cursor)
}
fn visible(snapshot: &'a Snapshot, start: usize) -> Self {
let mut cursor = snapshot.entries.cursor();
- cursor.seek(&VisibleFileCount(start), SeekBias::Right);
+ cursor.seek(&VisibleFileCount(start), SeekBias::Right, &());
Self::Visible(cursor)
}
@@ -1176,11 +1193,11 @@ impl<'a> FileIter<'a> {
match self {
Self::All(cursor) => {
let ix = *cursor.start();
- cursor.seek_forward(&FileCount(ix.0 + 1), SeekBias::Right);
+ cursor.seek_forward(&FileCount(ix.0 + 1), SeekBias::Right, &());
}
Self::Visible(cursor) => {
let ix = *cursor.start();
- cursor.seek_forward(&VisibleFileCount(ix.0 + 1), SeekBias::Right);
+ cursor.seek_forward(&VisibleFileCount(ix.0 + 1), SeekBias::Right, &());
}
}
}
@@ -1214,7 +1231,7 @@ struct ChildEntriesIter<'a> {
impl<'a> ChildEntriesIter<'a> {
fn new(parent_path: &'a Path, snapshot: &'a Snapshot) -> Self {
let mut cursor = snapshot.entries.cursor();
- cursor.seek(&PathSearch::Exact(parent_path), SeekBias::Right);
+ cursor.seek(&PathSearch::Exact(parent_path), SeekBias::Right, &());
Self {
parent_path,
cursor,
@@ -1229,7 +1246,7 @@ impl<'a> Iterator for ChildEntriesIter<'a> {
if let Some(item) = self.cursor.item() {
if item.path().starts_with(self.parent_path) {
self.cursor
- .seek_forward(&PathSearch::Successor(item.path()), SeekBias::Left);
+ .seek_forward(&PathSearch::Successor(item.path()), SeekBias::Left, &());
Some(item)
} else {
None
@@ -1346,7 +1363,8 @@ mod tests {
app.read(|ctx| tree.read(ctx).scan_complete()).await;
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
- let buffer = app.add_model(|_| Buffer::new(1, "a line of text.\n".repeat(10 * 1024)));
+ let buffer =
+ app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
let path = tree.update(&mut app, |tree, ctx| {
let path = tree.files(0).next().unwrap().path().clone();
@@ -1389,10 +1407,10 @@ mod tests {
let (file2, file3, file4, file5) = app.read(|ctx| {
(
- tree.file("a/file2", ctx).unwrap(),
- tree.file("a/file3", ctx).unwrap(),
- tree.file("b/c/file4", ctx).unwrap(),
- tree.file("b/c/file5", ctx).unwrap(),
+ tree.file("a/file2", ctx),
+ tree.file("a/file3", ctx),
+ tree.file("b/c/file4", ctx),
+ tree.file("b/c/file5", ctx),
)
});