Detailed changes
@@ -1180,6 +1180,7 @@ dependencies = [
"etagere",
"font-kit",
"foreign-types",
+ "gpui_macros",
"log",
"metal",
"num_cpus",
@@ -1205,6 +1206,14 @@ dependencies = [
"usvg",
]
+[[package]]
+name = "gpui_macros"
+version = "0.1.0"
+dependencies = [
+ "quote",
+ "syn",
+]
+
[[package]]
name = "hashbrown"
version = "0.9.1"
@@ -1,5 +1,5 @@
[workspace]
-members = ["zed", "gpui", "fsevent", "scoped_pool"]
+members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"]
[patch.crates-io]
async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
@@ -8,6 +8,7 @@ version = "0.1.0"
async-task = "4.0.3"
ctor = "0.1"
etagere = "0.2"
+gpui_macros = {path = "../gpui_macros"}
log = "0.4"
num_cpus = "1.13"
ordered-float = "2.1.1"
@@ -8,6 +8,7 @@ use crate::{
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
};
use anyhow::{anyhow, Result};
+use async_task::Task;
use keymap::MatchResult;
use parking_lot::{Mutex, RwLock};
use pathfinder_geometry::{rect::RectF, vector::vec2f};
@@ -50,6 +51,14 @@ pub trait ReadModel {
fn read_model<T: Entity>(&self, handle: &ModelHandle<T>) -> &T;
}
+pub trait ReadModelWith {
+ fn read_model_with<E: Entity, F: FnOnce(&E, &AppContext) -> T, T>(
+ &self,
+ handle: &ModelHandle<E>,
+ read: F,
+ ) -> T;
+}
+
pub trait UpdateModel {
fn update_model<T, F, S>(&mut self, handle: &ModelHandle<T>, update: F) -> S
where
@@ -61,6 +70,13 @@ pub trait ReadView {
fn read_view<T: View>(&self, handle: &ViewHandle<T>) -> &T;
}
+pub trait ReadViewWith {
+ fn read_view_with<V, F, T>(&self, handle: &ViewHandle<V>, read: F) -> T
+ where
+ V: View,
+ F: FnOnce(&V, &AppContext) -> T;
+}
+
pub trait UpdateView {
fn update_view<T, F, S>(&mut self, handle: &ViewHandle<T>, update: F) -> S
where
@@ -86,6 +102,8 @@ pub enum MenuItem<'a> {
#[derive(Clone)]
pub struct App(Rc<RefCell<MutableAppContext>>);
+pub struct AsyncAppContext(Rc<RefCell<MutableAppContext>>);
+
#[derive(Clone)]
pub struct TestAppContext(Rc<RefCell<MutableAppContext>>, Rc<platform::test::Platform>);
@@ -362,6 +380,84 @@ impl TestAppContext {
}
}
+impl AsyncAppContext {
+ pub fn read<T, F: FnOnce(&AppContext) -> T>(&mut self, callback: F) -> T {
+ callback(self.0.borrow().as_ref())
+ }
+
+ pub fn update<T, F: FnOnce(&mut MutableAppContext) -> T>(&mut self, callback: F) -> T {
+ let mut state = self.0.borrow_mut();
+ state.pending_flushes += 1;
+ let result = callback(&mut *state);
+ state.flush_effects();
+ result
+ }
+
+ pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
+ where
+ T: Entity,
+ F: FnOnce(&mut ModelContext<T>) -> T,
+ {
+ self.update(|ctx| ctx.add_model(build_model))
+ }
+
+ pub fn background_executor(&self) -> Arc<executor::Background> {
+ self.0.borrow().ctx.background.clone()
+ }
+}
+
+impl UpdateModel for AsyncAppContext {
+ fn update_model<T, F, S>(&mut self, handle: &ModelHandle<T>, update: F) -> S
+ where
+ T: Entity,
+ F: FnOnce(&mut T, &mut ModelContext<T>) -> S,
+ {
+ let mut state = self.0.borrow_mut();
+ state.pending_flushes += 1;
+ let result = state.update_model(handle, update);
+ state.flush_effects();
+ result
+ }
+}
+
+impl ReadModelWith for AsyncAppContext {
+ fn read_model_with<E: Entity, F: FnOnce(&E, &AppContext) -> T, T>(
+ &self,
+ handle: &ModelHandle<E>,
+ read: F,
+ ) -> T {
+ let ctx = self.0.borrow();
+ let ctx = ctx.as_ref();
+ read(handle.read(ctx), ctx)
+ }
+}
+
+impl UpdateView for AsyncAppContext {
+ fn update_view<T, F, S>(&mut self, handle: &ViewHandle<T>, update: F) -> S
+ where
+ T: View,
+ F: FnOnce(&mut T, &mut ViewContext<T>) -> S,
+ {
+ let mut state = self.0.borrow_mut();
+ state.pending_flushes += 1;
+ let result = state.update_view(handle, update);
+ state.flush_effects();
+ result
+ }
+}
+
+impl ReadViewWith for AsyncAppContext {
+ fn read_view_with<V, F, T>(&self, handle: &ViewHandle<V>, read: F) -> T
+ where
+ V: View,
+ F: FnOnce(&V, &AppContext) -> T,
+ {
+ let ctx = self.0.borrow();
+ let ctx = ctx.as_ref();
+ read(handle.read(ctx), ctx)
+ }
+}
+
impl UpdateModel for TestAppContext {
fn update_model<T, F, S>(&mut self, handle: &ModelHandle<T>, update: F) -> S
where
@@ -376,6 +472,18 @@ impl UpdateModel for TestAppContext {
}
}
+impl ReadModelWith for TestAppContext {
+ fn read_model_with<E: Entity, F: FnOnce(&E, &AppContext) -> T, T>(
+ &self,
+ handle: &ModelHandle<E>,
+ read: F,
+ ) -> T {
+ let ctx = self.0.borrow();
+ let ctx = ctx.as_ref();
+ read(handle.read(ctx), ctx)
+ }
+}
+
impl UpdateView for TestAppContext {
fn update_view<T, F, S>(&mut self, handle: &ViewHandle<T>, update: F) -> S
where
@@ -390,6 +498,18 @@ impl UpdateView for TestAppContext {
}
}
+impl ReadViewWith for TestAppContext {
+ fn read_view_with<V, F, T>(&self, handle: &ViewHandle<V>, read: F) -> T
+ where
+ V: View,
+ F: FnOnce(&V, &AppContext) -> T,
+ {
+ let ctx = self.0.borrow();
+ let ctx = ctx.as_ref();
+ read(handle.read(ctx), ctx)
+ }
+}
+
type ActionCallback =
dyn FnMut(&mut dyn AnyView, &dyn Any, &mut MutableAppContext, usize, usize) -> bool;
@@ -405,7 +525,6 @@ pub struct MutableAppContext {
keystroke_matcher: keymap::Matcher,
next_entity_id: usize,
next_window_id: usize,
- next_task_id: usize,
subscriptions: HashMap<usize, Vec<Subscription>>,
model_observations: HashMap<usize, Vec<ModelObservation>>,
view_observations: HashMap<usize, Vec<ViewObservation>>,
@@ -413,8 +532,6 @@ pub struct MutableAppContext {
HashMap<usize, (Rc<RefCell<Presenter>>, Box<dyn platform::Window>)>,
debug_elements_callbacks: HashMap<usize, Box<dyn Fn(&AppContext) -> crate::json::Value>>,
foreground: Rc<executor::Foreground>,
- future_handlers: Rc<RefCell<HashMap<usize, FutureHandler>>>,
- stream_handlers: Rc<RefCell<HashMap<usize, StreamHandler>>>,
pending_effects: VecDeque<Effect>,
pending_flushes: usize,
flushing_effects: bool,
@@ -446,15 +563,12 @@ impl MutableAppContext {
keystroke_matcher: keymap::Matcher::default(),
next_entity_id: 0,
next_window_id: 0,
- next_task_id: 0,
subscriptions: HashMap::new(),
model_observations: HashMap::new(),
view_observations: HashMap::new(),
presenters_and_platform_windows: HashMap::new(),
debug_elements_callbacks: HashMap::new(),
foreground,
- future_handlers: Default::default(),
- stream_handlers: Default::default(),
pending_effects: VecDeque::new(),
pending_flushes: 0,
flushing_effects: false,
@@ -1201,96 +1315,14 @@ impl MutableAppContext {
self.flush_effects();
}
- fn spawn<F, T>(&mut self, spawner: Spawner, future: F) -> EntityTask<T>
+ pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
where
- F: 'static + Future,
+ F: FnOnce(AsyncAppContext) -> Fut,
+ Fut: 'static + Future<Output = T>,
T: 'static,
{
- let task_id = post_inc(&mut self.next_task_id);
- let app = self.weak_self.as_ref().unwrap().upgrade().unwrap();
- let task = {
- let app = app.clone();
- self.foreground.spawn(async move {
- let output = future.await;
- app.borrow_mut()
- .handle_future_output(task_id, Box::new(output))
- .map(|output| *output.downcast::<T>().unwrap())
- })
- };
- EntityTask::new(
- task_id,
- task,
- spawner,
- TaskHandlerMap::Future(self.future_handlers.clone()),
- )
- }
-
- fn spawn_stream<F, T>(&mut self, spawner: Spawner, mut stream: F) -> EntityTask<T>
- where
- F: 'static + Stream + Unpin,
- T: 'static,
- {
- let task_id = post_inc(&mut self.next_task_id);
- let app = self.weak_self.as_ref().unwrap().upgrade().unwrap();
- let task = self.foreground.spawn(async move {
- loop {
- match stream.next().await {
- Some(item) => {
- let mut app = app.borrow_mut();
- if app.handle_stream_item(task_id, Box::new(item)) {
- break;
- }
- }
- None => {
- break;
- }
- }
- }
-
- app.borrow_mut()
- .stream_completed(task_id)
- .map(|output| *output.downcast::<T>().unwrap())
- });
-
- EntityTask::new(
- task_id,
- task,
- spawner,
- TaskHandlerMap::Stream(self.stream_handlers.clone()),
- )
- }
-
- fn handle_future_output(
- &mut self,
- task_id: usize,
- output: Box<dyn Any>,
- ) -> Option<Box<dyn Any>> {
- self.pending_flushes += 1;
- let future_callback = self.future_handlers.borrow_mut().remove(&task_id).unwrap();
- let result = future_callback(output, self);
- self.flush_effects();
- result
- }
-
- fn handle_stream_item(&mut self, task_id: usize, output: Box<dyn Any>) -> bool {
- self.pending_flushes += 1;
-
- let mut handler = self.stream_handlers.borrow_mut().remove(&task_id).unwrap();
- let halt = (handler.item_callback)(output, self);
- self.stream_handlers.borrow_mut().insert(task_id, handler);
-
- self.flush_effects();
- halt
- }
-
- fn stream_completed(&mut self, task_id: usize) -> Option<Box<dyn Any>> {
- self.pending_flushes += 1;
-
- let handler = self.stream_handlers.borrow_mut().remove(&task_id).unwrap();
- let result = (handler.done_callback)(self);
-
- self.flush_effects();
- result
+ let ctx = AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap());
+ self.foreground.spawn(f(ctx))
}
pub fn write_to_clipboard(&self, item: ClipboardItem) {
@@ -1666,76 +1698,14 @@ impl<'a, T: Entity> ModelContext<'a, T> {
ModelHandle::new(self.model_id, &self.app.ctx.ref_counts)
}
- pub fn spawn<S, F, U>(&mut self, future: S, callback: F) -> EntityTask<U>
- where
- S: 'static + Future,
- F: 'static + FnOnce(&mut T, S::Output, &mut ModelContext<T>) -> U,
- U: 'static,
- {
- let handle = self.handle();
- let weak_handle = handle.downgrade();
- let task = self
- .app
- .spawn::<S, U>(Spawner::Model(handle.into()), future);
-
- self.app.future_handlers.borrow_mut().insert(
- task.id,
- Box::new(move |output, ctx| {
- weak_handle.upgrade(ctx.as_ref()).map(|handle| {
- let output = *output.downcast().unwrap();
- handle.update(ctx, |model, ctx| {
- Box::new(callback(model, output, ctx)) as Box<dyn Any>
- })
- })
- }),
- );
-
- task
- }
-
- pub fn spawn_stream<S, F, G, U>(
- &mut self,
- stream: S,
- mut item_callback: F,
- done_callback: G,
- ) -> EntityTask<U>
+ pub fn spawn<F, Fut, S>(&self, f: F) -> Task<S>
where
- S: 'static + Stream + Unpin,
- F: 'static + FnMut(&mut T, S::Item, &mut ModelContext<T>),
- G: 'static + FnOnce(&mut T, &mut ModelContext<T>) -> U,
- U: 'static + Any,
+ F: FnOnce(ModelHandle<T>, AsyncAppContext) -> Fut,
+ Fut: 'static + Future<Output = S>,
+ S: 'static,
{
let handle = self.handle();
- let weak_handle = handle.downgrade();
- let task = self.app.spawn_stream(Spawner::Model(handle.into()), stream);
-
- self.app.stream_handlers.borrow_mut().insert(
- task.id,
- StreamHandler {
- item_callback: {
- let weak_handle = weak_handle.clone();
- Box::new(move |output, app| {
- if let Some(handle) = weak_handle.upgrade(app.as_ref()) {
- let output = *output.downcast().unwrap();
- handle.update(app, |model, ctx| {
- item_callback(model, output, ctx);
- ctx.halt_stream
- })
- } else {
- true
- }
- })
- },
- done_callback: Box::new(move |app| {
- weak_handle.upgrade(app.as_ref()).map(|handle| {
- handle.update(app, |model, ctx| Box::new(done_callback(model, ctx)))
- as Box<dyn Any>
- })
- }),
- },
- );
-
- task
+ self.app.spawn(|ctx| f(handle, ctx))
}
}
@@ -1773,7 +1743,6 @@ pub struct ViewContext<'a, T: ?Sized> {
view_id: usize,
view_type: PhantomData<T>,
halt_action_dispatch: bool,
- halt_stream: bool,
}
impl<'a, T: View> ViewContext<'a, T> {
@@ -1784,7 +1753,6 @@ impl<'a, T: View> ViewContext<'a, T> {
view_id,
view_type: PhantomData,
halt_action_dispatch: true,
- halt_stream: false,
}
}
@@ -1994,77 +1962,14 @@ impl<'a, T: View> ViewContext<'a, T> {
self.halt_action_dispatch = false;
}
- pub fn halt_stream(&mut self) {
- self.halt_stream = true;
- }
-
- pub fn spawn<S, F, U>(&mut self, future: S, callback: F) -> EntityTask<U>
+ pub fn spawn<F, Fut, S>(&self, f: F) -> Task<S>
where
- S: 'static + Future,
- F: 'static + FnOnce(&mut T, S::Output, &mut ViewContext<T>) -> U,
- U: 'static,
+ F: FnOnce(ViewHandle<T>, AsyncAppContext) -> Fut,
+ Fut: 'static + Future<Output = S>,
+ S: 'static,
{
let handle = self.handle();
- let weak_handle = handle.downgrade();
- let task = self.app.spawn(Spawner::View(handle.into()), future);
-
- self.app.future_handlers.borrow_mut().insert(
- task.id,
- Box::new(move |output, app| {
- weak_handle.upgrade(app.as_ref()).map(|handle| {
- let output = *output.downcast().unwrap();
- handle.update(app, |view, ctx| {
- Box::new(callback(view, output, ctx)) as Box<dyn Any>
- })
- })
- }),
- );
-
- task
- }
-
- pub fn spawn_stream<S, F, G, U>(
- &mut self,
- stream: S,
- mut item_callback: F,
- done_callback: G,
- ) -> EntityTask<U>
- where
- S: 'static + Stream + Unpin,
- F: 'static + FnMut(&mut T, S::Item, &mut ViewContext<T>),
- G: 'static + FnOnce(&mut T, &mut ViewContext<T>) -> U,
- U: 'static + Any,
- {
- let handle = self.handle();
- let weak_handle = handle.downgrade();
- let task = self.app.spawn_stream(Spawner::View(handle.into()), stream);
- self.app.stream_handlers.borrow_mut().insert(
- task.id,
- StreamHandler {
- item_callback: {
- let weak_handle = weak_handle.clone();
- Box::new(move |output, ctx| {
- if let Some(handle) = weak_handle.upgrade(ctx.as_ref()) {
- let output = *output.downcast().unwrap();
- handle.update(ctx, |view, ctx| {
- item_callback(view, output, ctx);
- ctx.halt_stream
- })
- } else {
- true
- }
- })
- },
- done_callback: Box::new(move |ctx| {
- weak_handle.upgrade(ctx.as_ref()).map(|handle| {
- handle.update(ctx, |view, ctx| {
- Box::new(done_callback(view, ctx)) as Box<dyn Any>
- })
- })
- }),
- },
- );
- task
+ self.app.spawn(|ctx| f(handle, ctx))
}
}
@@ -2157,6 +2062,14 @@ impl<T: Entity> ModelHandle<T> {
app.read_model(self)
}
+ pub fn read_with<'a, A, F, S>(&self, ctx: &A, read: F) -> S
+ where
+ A: ReadModelWith,
+ F: FnOnce(&T, &AppContext) -> S,
+ {
+ ctx.read_model_with(self, read)
+ }
+
pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
where
A: UpdateModel,
@@ -2299,9 +2212,10 @@ impl<T: Entity> WeakModelHandle<T> {
}
}
- pub fn upgrade(&self, app: &AppContext) -> Option<ModelHandle<T>> {
- if app.models.contains_key(&self.model_id) {
- Some(ModelHandle::new(self.model_id, &app.ref_counts))
+ pub fn upgrade(&self, ctx: impl AsRef<AppContext>) -> Option<ModelHandle<T>> {
+ let ctx = ctx.as_ref();
+ if ctx.models.contains_key(&self.model_id) {
+ Some(ModelHandle::new(self.model_id, &ctx.ref_counts))
} else {
None
}
@@ -2351,6 +2265,14 @@ impl<T: View> ViewHandle<T> {
app.read_view(self)
}
+ pub fn read_with<A, F, S>(&self, ctx: &A, read: F) -> S
+ where
+ A: ReadViewWith,
+ F: FnOnce(&T, &AppContext) -> S,
+ {
+ ctx.read_view_with(self, read)
+ }
+
pub fn update<A, F, S>(&self, app: &mut A, update: F) -> S
where
A: UpdateView,
@@ -2761,94 +2683,14 @@ struct ViewObservation {
callback: Box<dyn FnMut(&mut dyn Any, usize, usize, &mut MutableAppContext, usize, usize)>,
}
-type FutureHandler = Box<dyn FnOnce(Box<dyn Any>, &mut MutableAppContext) -> Option<Box<dyn Any>>>;
-
-struct StreamHandler {
- item_callback: Box<dyn FnMut(Box<dyn Any>, &mut MutableAppContext) -> bool>,
- done_callback: Box<dyn FnOnce(&mut MutableAppContext) -> Option<Box<dyn Any>>>,
-}
-
-#[must_use]
-pub struct EntityTask<T> {
- id: usize,
- task: Option<executor::Task<Option<T>>>,
- _spawner: Spawner, // Keeps the spawning entity alive for as long as the task exists
- handler_map: TaskHandlerMap,
-}
-
-pub enum Spawner {
- Model(AnyModelHandle),
- View(AnyViewHandle),
-}
-
-enum TaskHandlerMap {
- Detached,
- Future(Rc<RefCell<HashMap<usize, FutureHandler>>>),
- Stream(Rc<RefCell<HashMap<usize, StreamHandler>>>),
-}
-
-impl<T> EntityTask<T> {
- fn new(
- id: usize,
- task: executor::Task<Option<T>>,
- spawner: Spawner,
- handler_map: TaskHandlerMap,
- ) -> Self {
- Self {
- id,
- task: Some(task),
- _spawner: spawner,
- handler_map,
- }
- }
-
- pub fn detach(mut self) {
- self.handler_map = TaskHandlerMap::Detached;
- self.task.take().unwrap().detach();
- }
-
- pub async fn cancel(mut self) -> Option<T> {
- let task = self.task.take().unwrap();
- task.cancel().await.unwrap()
- }
-}
-
-impl<T> Future for EntityTask<T> {
- type Output = T;
-
- fn poll(
- self: std::pin::Pin<&mut Self>,
- ctx: &mut std::task::Context<'_>,
- ) -> std::task::Poll<Self::Output> {
- let task = unsafe { self.map_unchecked_mut(|task| task.task.as_mut().unwrap()) };
- task.poll(ctx).map(|output| output.unwrap())
- }
-}
-
-impl<T> Drop for EntityTask<T> {
- fn drop(self: &mut Self) {
- match &self.handler_map {
- TaskHandlerMap::Detached => {
- return;
- }
- TaskHandlerMap::Future(map) => {
- map.borrow_mut().remove(&self.id);
- }
- TaskHandlerMap::Stream(map) => {
- map.borrow_mut().remove(&self.id);
- }
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
use crate::elements::*;
use smol::future::poll_once;
- #[test]
- fn test_model_handles() {
+ #[crate::test(self)]
+ fn test_model_handles(app: &mut MutableAppContext) {
struct Model {
other: Option<ModelHandle<Model>>,
events: Vec<String>,
@@ -2876,40 +2718,38 @@ mod tests {
}
}
- App::test((), |app| {
- let handle_1 = app.add_model(|ctx| Model::new(None, ctx));
- let handle_2 = app.add_model(|ctx| Model::new(Some(handle_1.clone()), ctx));
- assert_eq!(app.ctx.models.len(), 2);
+ let handle_1 = app.add_model(|ctx| Model::new(None, ctx));
+ let handle_2 = app.add_model(|ctx| Model::new(Some(handle_1.clone()), ctx));
+ assert_eq!(app.ctx.models.len(), 2);
- handle_1.update(app, |model, ctx| {
- model.events.push("updated".into());
- ctx.emit(1);
- ctx.notify();
- ctx.emit(2);
- });
- assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
- assert_eq!(
- handle_2.read(app).events,
- vec![
- "observed event 1".to_string(),
- "notified".to_string(),
- "observed event 2".to_string(),
- ]
- );
-
- handle_2.update(app, |model, _| {
- drop(handle_1);
- model.other.take();
- });
+ handle_1.update(app, |model, ctx| {
+ model.events.push("updated".into());
+ ctx.emit(1);
+ ctx.notify();
+ ctx.emit(2);
+ });
+ assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
+ assert_eq!(
+ handle_2.read(app).events,
+ vec![
+ "observed event 1".to_string(),
+ "notified".to_string(),
+ "observed event 2".to_string(),
+ ]
+ );
- assert_eq!(app.ctx.models.len(), 1);
- assert!(app.subscriptions.is_empty());
- assert!(app.model_observations.is_empty());
+ handle_2.update(app, |model, _| {
+ drop(handle_1);
+ model.other.take();
});
+
+ assert_eq!(app.ctx.models.len(), 1);
+ assert!(app.subscriptions.is_empty());
+ assert!(app.model_observations.is_empty());
}
- #[test]
- fn test_subscribe_and_emit_from_model() {
+ #[crate::test(self)]
+ fn test_subscribe_and_emit_from_model(app: &mut MutableAppContext) {
#[derive(Default)]
struct Model {
events: Vec<usize>,
@@ -2919,31 +2759,29 @@ mod tests {
type Event = usize;
}
- App::test((), |app| {
- let handle_1 = app.add_model(|_| Model::default());
- let handle_2 = app.add_model(|_| Model::default());
- let handle_2b = handle_2.clone();
+ let handle_1 = app.add_model(|_| Model::default());
+ let handle_2 = app.add_model(|_| Model::default());
+ let handle_2b = handle_2.clone();
- handle_1.update(app, |_, c| {
- c.subscribe(&handle_2, move |model: &mut Model, event, c| {
- model.events.push(*event);
+ handle_1.update(app, |_, c| {
+ c.subscribe(&handle_2, move |model: &mut Model, event, c| {
+ model.events.push(*event);
- c.subscribe(&handle_2b, |model, event, _| {
- model.events.push(*event * 2);
- });
+ c.subscribe(&handle_2b, |model, event, _| {
+ model.events.push(*event * 2);
});
});
+ });
- handle_2.update(app, |_, c| c.emit(7));
- assert_eq!(handle_1.read(app).events, vec![7]);
+ handle_2.update(app, |_, c| c.emit(7));
+ assert_eq!(handle_1.read(app).events, vec![7]);
- handle_2.update(app, |_, c| c.emit(5));
- assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
- })
+ handle_2.update(app, |_, c| c.emit(5));
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
}
- #[test]
- fn test_observe_and_notify_from_model() {
+ #[crate::test(self)]
+ fn test_observe_and_notify_from_model(app: &mut MutableAppContext) {
#[derive(Default)]
struct Model {
count: usize,
@@ -2954,99 +2792,34 @@ mod tests {
type Event = ();
}
- App::test((), |app| {
- let handle_1 = app.add_model(|_| Model::default());
- let handle_2 = app.add_model(|_| Model::default());
- let handle_2b = handle_2.clone();
+ let handle_1 = app.add_model(|_| Model::default());
+ let handle_2 = app.add_model(|_| Model::default());
+ let handle_2b = handle_2.clone();
- handle_1.update(app, |_, c| {
- c.observe(&handle_2, move |model, observed, c| {
- model.events.push(observed.read(c).count);
- c.observe(&handle_2b, |model, observed, c| {
- model.events.push(observed.read(c).count * 2);
- });
+ handle_1.update(app, |_, c| {
+ c.observe(&handle_2, move |model, observed, c| {
+ model.events.push(observed.read(c).count);
+ c.observe(&handle_2b, |model, observed, c| {
+ model.events.push(observed.read(c).count * 2);
});
});
-
- handle_2.update(app, |model, c| {
- model.count = 7;
- c.notify()
- });
- assert_eq!(handle_1.read(app).events, vec![7]);
-
- handle_2.update(app, |model, c| {
- model.count = 5;
- c.notify()
- });
- assert_eq!(handle_1.read(app).events, vec![7, 10, 5])
- })
- }
-
- #[test]
- fn test_spawn_from_model() {
- #[derive(Default)]
- struct Model {
- count: usize,
- }
-
- impl Entity for Model {
- type Event = ();
- }
-
- App::test_async((), |mut app| async move {
- let handle = app.add_model(|_| Model::default());
- handle
- .update(&mut app, |_, c| {
- c.spawn(async { 7 }, |model, output, _| {
- model.count = output;
- })
- })
- .await;
- app.read(|ctx| assert_eq!(handle.read(ctx).count, 7));
-
- handle
- .update(&mut app, |_, c| {
- c.spawn(async { 14 }, |model, output, _| {
- model.count = output;
- })
- })
- .await;
- app.read(|ctx| assert_eq!(handle.read(ctx).count, 14));
});
- }
- #[test]
- fn test_spawn_stream_local_from_model() {
- #[derive(Default)]
- struct Model {
- events: Vec<Option<usize>>,
- }
-
- impl Entity for Model {
- type Event = ();
- }
+ handle_2.update(app, |model, c| {
+ model.count = 7;
+ c.notify()
+ });
+ assert_eq!(handle_1.read(app).events, vec![7]);
- App::test_async((), |mut app| async move {
- let handle = app.add_model(|_| Model::default());
- handle
- .update(&mut app, |_, c| {
- c.spawn_stream(
- smol::stream::iter(vec![1, 2, 3]),
- |model, output, _| {
- model.events.push(Some(output));
- },
- |model, _| {
- model.events.push(None);
- },
- )
- })
- .await;
- app.read(|ctx| assert_eq!(handle.read(ctx).events, [Some(1), Some(2), Some(3), None]));
- })
+ handle_2.update(app, |model, c| {
+ model.count = 5;
+ c.notify()
+ });
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5])
}
- #[test]
- fn test_view_handles() {
+ #[crate::test(self)]
+ fn test_view_handles(app: &mut MutableAppContext) {
struct View {
other: Option<ViewHandle<View>>,
events: Vec<String>,
@@ -3080,39 +2853,37 @@ mod tests {
}
}
- App::test((), |app| {
- let (window_id, _) = app.add_window(|ctx| View::new(None, ctx));
- let handle_1 = app.add_view(window_id, |ctx| View::new(None, ctx));
- let handle_2 = app.add_view(window_id, |ctx| View::new(Some(handle_1.clone()), ctx));
- assert_eq!(app.ctx.views.len(), 3);
+ let (window_id, _) = app.add_window(|ctx| View::new(None, ctx));
+ let handle_1 = app.add_view(window_id, |ctx| View::new(None, ctx));
+ let handle_2 = app.add_view(window_id, |ctx| View::new(Some(handle_1.clone()), ctx));
+ assert_eq!(app.ctx.views.len(), 3);
- handle_1.update(app, |view, ctx| {
- view.events.push("updated".into());
- ctx.emit(1);
- ctx.emit(2);
- });
- assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
- assert_eq!(
- handle_2.read(app).events,
- vec![
- "observed event 1".to_string(),
- "observed event 2".to_string(),
- ]
- );
+ handle_1.update(app, |view, ctx| {
+ view.events.push("updated".into());
+ ctx.emit(1);
+ ctx.emit(2);
+ });
+ assert_eq!(handle_1.read(app).events, vec!["updated".to_string()]);
+ assert_eq!(
+ handle_2.read(app).events,
+ vec![
+ "observed event 1".to_string(),
+ "observed event 2".to_string(),
+ ]
+ );
- handle_2.update(app, |view, _| {
- drop(handle_1);
- view.other.take();
- });
+ handle_2.update(app, |view, _| {
+ drop(handle_1);
+ view.other.take();
+ });
- assert_eq!(app.ctx.views.len(), 2);
- assert!(app.subscriptions.is_empty());
- assert!(app.model_observations.is_empty());
- })
+ assert_eq!(app.ctx.views.len(), 2);
+ assert!(app.subscriptions.is_empty());
+ assert!(app.model_observations.is_empty());
}
- #[test]
- fn test_subscribe_and_emit_from_view() {
+ #[crate::test(self)]
+ fn test_subscribe_and_emit_from_view(app: &mut MutableAppContext) {
#[derive(Default)]
struct View {
events: Vec<usize>,
@@ -3138,39 +2909,37 @@ mod tests {
type Event = usize;
}
- App::test((), |app| {
- let (window_id, handle_1) = app.add_window(|_| View::default());
- let handle_2 = app.add_view(window_id, |_| View::default());
- let handle_2b = handle_2.clone();
- let handle_3 = app.add_model(|_| Model);
+ let (window_id, handle_1) = app.add_window(|_| View::default());
+ let handle_2 = app.add_view(window_id, |_| View::default());
+ let handle_2b = handle_2.clone();
+ let handle_3 = app.add_model(|_| Model);
- handle_1.update(app, |_, c| {
- c.subscribe_to_view(&handle_2, move |me, _, event, c| {
- me.events.push(*event);
+ handle_1.update(app, |_, c| {
+ c.subscribe_to_view(&handle_2, move |me, _, event, c| {
+ me.events.push(*event);
- c.subscribe_to_view(&handle_2b, |me, _, event, _| {
- me.events.push(*event * 2);
- });
+ c.subscribe_to_view(&handle_2b, |me, _, event, _| {
+ me.events.push(*event * 2);
});
-
- c.subscribe_to_model(&handle_3, |me, _, event, _| {
- me.events.push(*event);
- })
});
- handle_2.update(app, |_, c| c.emit(7));
- assert_eq!(handle_1.read(app).events, vec![7]);
+ c.subscribe_to_model(&handle_3, |me, _, event, _| {
+ me.events.push(*event);
+ })
+ });
+
+ handle_2.update(app, |_, c| c.emit(7));
+ assert_eq!(handle_1.read(app).events, vec![7]);
- handle_2.update(app, |_, c| c.emit(5));
- assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
+ handle_2.update(app, |_, c| c.emit(5));
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5]);
- handle_3.update(app, |_, c| c.emit(9));
- assert_eq!(handle_1.read(app).events, vec![7, 10, 5, 9]);
- })
+ handle_3.update(app, |_, c| c.emit(9));
+ assert_eq!(handle_1.read(app).events, vec![7, 10, 5, 9]);
}
- #[test]
- fn test_dropping_subscribers() {
+ #[crate::test(self)]
+ fn test_dropping_subscribers(app: &mut MutableAppContext) {
struct View;
impl Entity for View {
@@ -3193,33 +2962,31 @@ mod tests {
type Event = ();
}
- App::test((), |app| {
- let (window_id, _) = app.add_window(|_| View);
- let observing_view = app.add_view(window_id, |_| View);
- let emitting_view = app.add_view(window_id, |_| View);
- let observing_model = app.add_model(|_| Model);
- let observed_model = app.add_model(|_| Model);
+ let (window_id, _) = app.add_window(|_| View);
+ let observing_view = app.add_view(window_id, |_| View);
+ let emitting_view = app.add_view(window_id, |_| View);
+ let observing_model = app.add_model(|_| Model);
+ let observed_model = app.add_model(|_| Model);
- observing_view.update(app, |_, ctx| {
- ctx.subscribe_to_view(&emitting_view, |_, _, _, _| {});
- ctx.subscribe_to_model(&observed_model, |_, _, _, _| {});
- });
- observing_model.update(app, |_, ctx| {
- ctx.subscribe(&observed_model, |_, _, _| {});
- });
+ observing_view.update(app, |_, ctx| {
+ ctx.subscribe_to_view(&emitting_view, |_, _, _, _| {});
+ ctx.subscribe_to_model(&observed_model, |_, _, _, _| {});
+ });
+ observing_model.update(app, |_, ctx| {
+ ctx.subscribe(&observed_model, |_, _, _| {});
+ });
- app.update(|| {
- drop(observing_view);
- drop(observing_model);
- });
+ app.update(|| {
+ drop(observing_view);
+ drop(observing_model);
+ });
- emitting_view.update(app, |_, ctx| ctx.emit(()));
- observed_model.update(app, |_, ctx| ctx.emit(()));
- })
+ emitting_view.update(app, |_, ctx| ctx.emit(()));
+ observed_model.update(app, |_, ctx| ctx.emit(()));
}
- #[test]
- fn test_observe_and_notify_from_view() {
+ #[crate::test(self)]
+ fn test_observe_and_notify_from_view(app: &mut MutableAppContext) {
#[derive(Default)]
struct View {
events: Vec<usize>,
@@ -24,6 +24,7 @@ pub mod color;
pub mod json;
pub mod keymap;
mod platform;
+pub use gpui_macros::test;
pub use platform::{Event, PathPromptOptions, PromptLevel};
pub use presenter::{
AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
@@ -0,0 +1,11 @@
+[package]
+name = "gpui_macros"
+version = "0.1.0"
+edition = "2018"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+syn = "1.0"
+quote = "1.0"
@@ -0,0 +1,57 @@
+use std::mem;
+
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, parse_quote, AttributeArgs, ItemFn, Meta, NestedMeta};
+
+#[proc_macro_attribute]
+pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
+ let mut namespace = format_ident!("gpui");
+
+ let args = syn::parse_macro_input!(args as AttributeArgs);
+ for arg in args {
+ match arg {
+ NestedMeta::Meta(Meta::Path(name))
+ if name.get_ident().map_or(false, |n| n == "self") =>
+ {
+ namespace = format_ident!("crate");
+ }
+ other => {
+ return TokenStream::from(
+ syn::Error::new_spanned(other, "invalid argument").into_compile_error(),
+ )
+ }
+ }
+ }
+
+ let mut inner_fn = parse_macro_input!(function as ItemFn);
+ let inner_fn_attributes = mem::take(&mut inner_fn.attrs);
+ let inner_fn_name = format_ident!("_{}", inner_fn.sig.ident);
+ let outer_fn_name = mem::replace(&mut inner_fn.sig.ident, inner_fn_name.clone());
+ let mut outer_fn: ItemFn = if inner_fn.sig.asyncness.is_some() {
+ parse_quote! {
+ #[test]
+ fn #outer_fn_name() {
+ #inner_fn
+
+ #namespace::App::test_async((), move |ctx| async {
+ #inner_fn_name(ctx).await;
+ });
+ }
+ }
+ } else {
+ parse_quote! {
+ #[test]
+ fn #outer_fn_name() {
+ #inner_fn
+
+ #namespace::App::test((), |ctx| {
+ #inner_fn_name(ctx);
+ });
+ }
+ }
+ };
+ outer_fn.attrs.extend(inner_fn_attributes);
+
+ TokenStream::from(quote!(#outer_fn))
+}
@@ -4,12 +4,10 @@ mod selection;
mod text;
pub use anchor::*;
-use futures_core::future::LocalBoxFuture;
pub use point::*;
use seahash::SeaHasher;
pub use selection::*;
use similar::{ChangeTag, TextDiff};
-use smol::future::FutureExt;
pub use text::*;
use crate::{
@@ -20,7 +18,7 @@ use crate::{
worktree::FileHandle,
};
use anyhow::{anyhow, Result};
-use gpui::{Entity, EntityTask, ModelContext};
+use gpui::{Entity, ModelContext, Task};
use lazy_static::lazy_static;
use rand::prelude::*;
use std::{
@@ -385,23 +383,27 @@ impl Buffer {
if file.is_deleted() {
ctx.emit(Event::Dirtied);
} else {
- ctx.spawn(
- file.load_history(ctx.as_ref()),
- move |this, history, ctx| {
- if let (Ok(history), true) = (history, this.version == version) {
- let task = this.set_text_via_diff(history.base_text, ctx);
- ctx.spawn(task, move |this, ops, ctx| {
- if ops.is_some() {
- this.saved_version = this.version.clone();
- this.saved_mtime = file.mtime();
- ctx.emit(Event::Reloaded);
- }
+ ctx.spawn(|handle, mut ctx| async move {
+ let (current_version, history) = handle.read_with(&ctx, |this, ctx| {
+ (this.version.clone(), file.load_history(ctx.as_ref()))
+ });
+ if let (Ok(history), true) = (history.await, current_version == version)
+ {
+ let operations = handle
+ .update(&mut ctx, |this, ctx| {
+ this.set_text_via_diff(history.base_text, ctx)
})
- .detach();
+ .await;
+ if operations.is_some() {
+ handle.update(&mut ctx, |this, ctx| {
+ this.saved_version = this.version.clone();
+ this.saved_mtime = file.mtime();
+ ctx.emit(Event::Reloaded);
+ });
}
- },
- )
- .detach()
+ }
+ })
+ .detach();
}
}
ctx.emit(Event::FileHandleChanged);
@@ -499,21 +501,22 @@ impl Buffer {
&mut self,
new_file: Option<FileHandle>,
ctx: &mut ModelContext<Self>,
- ) -> LocalBoxFuture<'static, Result<()>> {
+ ) -> Task<Result<()>> {
let snapshot = self.snapshot();
let version = self.version.clone();
- 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);
+ let file = self.file.clone();
+
+ ctx.spawn(|handle, mut ctx| async move {
+ if let Some(file) = new_file.as_ref().or(file.as_ref()) {
+ let result = ctx.read(|ctx| file.save(snapshot, ctx.as_ref())).await;
+ if result.is_ok() {
+ handle.update(&mut ctx, |me, ctx| me.did_save(version, new_file, ctx));
}
- save_result
- })
- .boxed_local()
- } else {
- async { Ok(()) }.boxed_local()
- }
+ result
+ } else {
+ Ok(())
+ }
+ })
}
fn did_save(
@@ -536,20 +539,23 @@ impl Buffer {
&mut self,
new_text: Arc<str>,
ctx: &mut ModelContext<Self>,
- ) -> EntityTask<Option<Vec<Operation>>> {
+ ) -> Task<Option<Vec<Operation>>> {
let version = self.version.clone();
let old_text = self.text();
- ctx.spawn(
- ctx.background_executor().spawn({
- let new_text = new_text.clone();
- async move {
- TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
- .iter_all_changes()
- .map(|c| (c.tag(), c.value().len()))
- .collect::<Vec<_>>()
- }
- }),
- move |this, diff, ctx| {
+ ctx.spawn(|handle, mut ctx| async move {
+ let diff = ctx
+ .background_executor()
+ .spawn({
+ let new_text = new_text.clone();
+ async move {
+ TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
+ .iter_all_changes()
+ .map(|c| (c.tag(), c.value().len()))
+ .collect::<Vec<_>>()
+ }
+ })
+ .await;
+ handle.update(&mut ctx, |this, ctx| {
if this.version == version {
this.start_transaction(None).unwrap();
let mut operations = Vec::new();
@@ -575,8 +581,8 @@ impl Buffer {
} else {
None
}
- },
- )
+ })
+ })
}
pub fn is_dirty(&self) -> bool {
@@ -2480,259 +2486,245 @@ mod tests {
sync::atomic::{self, AtomicUsize},
};
- #[test]
- fn test_edit() {
- App::test((), |ctx| {
- 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");
- buffer.edit(vec![0..0], "ghi", None).unwrap();
- assert_eq!(buffer.text(), "ghiabcdef");
- buffer.edit(vec![5..5], "jkl", None).unwrap();
- assert_eq!(buffer.text(), "ghiabjklcdef");
- buffer.edit(vec![6..7], "", None).unwrap();
- assert_eq!(buffer.text(), "ghiabjlcdef");
- buffer.edit(vec![4..9], "mno", None).unwrap();
- assert_eq!(buffer.text(), "ghiamnoef");
- buffer
- });
- })
+ #[gpui::test]
+ fn test_edit(ctx: &mut gpui::MutableAppContext) {
+ 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");
+ buffer.edit(vec![0..0], "ghi", None).unwrap();
+ assert_eq!(buffer.text(), "ghiabcdef");
+ buffer.edit(vec![5..5], "jkl", None).unwrap();
+ assert_eq!(buffer.text(), "ghiabjklcdef");
+ buffer.edit(vec![6..7], "", None).unwrap();
+ assert_eq!(buffer.text(), "ghiabjlcdef");
+ buffer.edit(vec![4..9], "mno", None).unwrap();
+ assert_eq!(buffer.text(), "ghiamnoef");
+ buffer
+ });
}
- #[test]
- fn test_edit_events() {
- App::test((), |app| {
- let mut now = Instant::now();
- 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(|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();
- ctx.subscribe(&buffer1, move |_, event, _| {
- buffer_1_events.borrow_mut().push(event.clone())
- });
- let buffer_2_events = buffer_2_events.clone();
- ctx.subscribe(&buffer2, move |_, event, _| {
- buffer_2_events.borrow_mut().push(event.clone())
- });
+ #[gpui::test]
+ fn test_edit_events(app: &mut gpui::MutableAppContext) {
+ let mut now = Instant::now();
+ let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
+ let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
- // An edit emits an edited event, followed by a dirtied event,
- // since the buffer was previously in a clean state.
- let ops = buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap();
- buffer_ops.extend_from_slice(&ops);
-
- // An empty transaction does not emit any events.
- buffer.start_transaction(None).unwrap();
- buffer.end_transaction(None, Some(ctx)).unwrap();
-
- // A transaction containing two edits emits one edited event.
- now += Duration::from_secs(1);
- buffer.start_transaction_at(None, now).unwrap();
- let ops = buffer.edit(Some(5..5), "u", Some(ctx)).unwrap();
- buffer_ops.extend_from_slice(&ops);
- let ops = buffer.edit(Some(6..6), "w", Some(ctx)).unwrap();
- buffer_ops.extend_from_slice(&ops);
- buffer.end_transaction_at(None, now, Some(ctx)).unwrap();
-
- // Undoing a transaction emits one edited event.
- let ops = buffer.undo(Some(ctx));
- buffer_ops.extend_from_slice(&ops);
+ 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();
+ ctx.subscribe(&buffer1, move |_, event, _| {
+ buffer_1_events.borrow_mut().push(event.clone())
});
-
- // Incorporating a set of remote ops emits a single edited event,
- // followed by a dirtied event.
- buffer2.update(app, |buffer, ctx| {
- buffer.apply_ops(buffer_ops, Some(ctx)).unwrap();
+ let buffer_2_events = buffer_2_events.clone();
+ ctx.subscribe(&buffer2, move |_, event, _| {
+ buffer_2_events.borrow_mut().push(event.clone())
});
- let buffer_1_events = buffer_1_events.borrow();
- assert_eq!(
- *buffer_1_events,
- vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
- );
+ // An edit emits an edited event, followed by a dirtied event,
+ // since the buffer was previously in a clean state.
+ let ops = buffer.edit(Some(2..4), "XYZ", Some(ctx)).unwrap();
+ buffer_ops.extend_from_slice(&ops);
+
+ // An empty transaction does not emit any events.
+ buffer.start_transaction(None).unwrap();
+ buffer.end_transaction(None, Some(ctx)).unwrap();
+
+ // A transaction containing two edits emits one edited event.
+ now += Duration::from_secs(1);
+ buffer.start_transaction_at(None, now).unwrap();
+ let ops = buffer.edit(Some(5..5), "u", Some(ctx)).unwrap();
+ buffer_ops.extend_from_slice(&ops);
+ let ops = buffer.edit(Some(6..6), "w", Some(ctx)).unwrap();
+ buffer_ops.extend_from_slice(&ops);
+ buffer.end_transaction_at(None, now, Some(ctx)).unwrap();
+
+ // Undoing a transaction emits one edited event.
+ let ops = buffer.undo(Some(ctx));
+ buffer_ops.extend_from_slice(&ops);
+ });
- let buffer_2_events = buffer_2_events.borrow();
- assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
+ // Incorporating a set of remote ops emits a single edited event,
+ // followed by a dirtied event.
+ buffer2.update(app, |buffer, ctx| {
+ buffer.apply_ops(buffer_ops, Some(ctx)).unwrap();
});
+
+ let buffer_1_events = buffer_1_events.borrow();
+ assert_eq!(
+ *buffer_1_events,
+ vec![Event::Edited, Event::Dirtied, Event::Edited, Event::Edited]
+ );
+
+ let buffer_2_events = buffer_2_events.borrow();
+ assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
}
- #[test]
- fn test_random_edits() {
+ #[gpui::test]
+ fn test_random_edits(ctx: &mut gpui::MutableAppContext) {
for seed in 0..100 {
- App::test((), |ctx| {
- println!("{:?}", seed);
- let mut rng = &mut StdRng::seed_from_u64(seed);
-
- let reference_string_len = rng.gen_range(0..3);
- let mut reference_string = RandomCharIter::new(&mut rng)
- .take(reference_string_len)
- .collect::<String>();
- 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);
- for old_range in old_ranges.iter().rev() {
- reference_string = [
- &reference_string[0..old_range.start],
- new_text.as_str(),
- &reference_string[old_range.end..],
- ]
- .concat();
- }
- assert_eq!(buffer.text(), reference_string);
+ println!("{:?}", seed);
+ let mut rng = &mut StdRng::seed_from_u64(seed);
- if rng.gen_bool(0.25) {
- buffer.randomly_undo_redo(rng);
- reference_string = buffer.text();
- }
+ let reference_string_len = rng.gen_range(0..3);
+ let mut reference_string = RandomCharIter::new(&mut rng)
+ .take(reference_string_len)
+ .collect::<String>();
+ 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);
+ for old_range in old_ranges.iter().rev() {
+ reference_string = [
+ &reference_string[0..old_range.start],
+ new_text.as_str(),
+ &reference_string[old_range.end..],
+ ]
+ .concat();
+ }
+ assert_eq!(buffer.text(), reference_string);
+
+ if rng.gen_bool(0.25) {
+ buffer.randomly_undo_redo(rng);
+ reference_string = buffer.text();
+ }
- {
- let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len());
+ {
+ let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len());
- for (len, rows) in &line_lengths {
- for row in rows {
- assert_eq!(buffer.line_len(*row).unwrap(), *len);
- }
+ for (len, rows) in &line_lengths {
+ for row in rows {
+ assert_eq!(buffer.line_len(*row).unwrap(), *len);
}
-
- let (longest_column, longest_rows) =
- line_lengths.iter().next_back().unwrap();
- let rightmost_point = buffer.rightmost_point();
- assert_eq!(rightmost_point.column, *longest_column);
- assert!(longest_rows.contains(&rightmost_point.row));
}
- for _ in 0..5 {
- let end = rng.gen_range(0..buffer.len() + 1);
- let start = rng.gen_range(0..end + 1);
-
- let line_lengths = line_lengths_in_range(&buffer, start..end);
- let (longest_column, longest_rows) =
- line_lengths.iter().next_back().unwrap();
- let range_sum = buffer.text_summary_for_range(start..end);
- assert_eq!(range_sum.rightmost_point.column, *longest_column);
- assert!(longest_rows.contains(&range_sum.rightmost_point.row));
- let range_text = &buffer.text()[start..end];
- assert_eq!(range_sum.chars, range_text.chars().count());
- assert_eq!(range_sum.bytes, range_text.len());
- }
+ let (longest_column, longest_rows) =
+ line_lengths.iter().next_back().unwrap();
+ let rightmost_point = buffer.rightmost_point();
+ assert_eq!(rightmost_point.column, *longest_column);
+ assert!(longest_rows.contains(&rightmost_point.row));
+ }
- if rng.gen_bool(0.3) {
- buffer_versions.push(buffer.clone());
- }
+ for _ in 0..5 {
+ let end = rng.gen_range(0..buffer.len() + 1);
+ let start = rng.gen_range(0..end + 1);
+
+ let line_lengths = line_lengths_in_range(&buffer, start..end);
+ let (longest_column, longest_rows) =
+ line_lengths.iter().next_back().unwrap();
+ let range_sum = buffer.text_summary_for_range(start..end);
+ assert_eq!(range_sum.rightmost_point.column, *longest_column);
+ assert!(longest_rows.contains(&range_sum.rightmost_point.row));
+ let range_text = &buffer.text()[start..end];
+ assert_eq!(range_sum.chars, range_text.chars().count());
+ assert_eq!(range_sum.bytes, range_text.len());
}
- for mut old_buffer in buffer_versions {
- let mut delta = 0_isize;
- for Edit {
- old_range,
- new_range,
- } in buffer.edits_since(old_buffer.version.clone())
- {
- let old_len = old_range.end - old_range.start;
- let new_len = new_range.end - new_range.start;
- let old_start = (old_range.start as isize + delta) as usize;
- let new_text: String =
- buffer.text_for_range(new_range).unwrap().collect();
- old_buffer
- .edit(Some(old_start..old_start + old_len), new_text, None)
- .unwrap();
-
- delta += new_len as isize - old_len as isize;
- }
- assert_eq!(old_buffer.text(), buffer.text());
+ if rng.gen_bool(0.3) {
+ buffer_versions.push(buffer.clone());
}
+ }
- buffer
- })
+ for mut old_buffer in buffer_versions {
+ let mut delta = 0_isize;
+ for Edit {
+ old_range,
+ new_range,
+ } in buffer.edits_since(old_buffer.version.clone())
+ {
+ let old_len = old_range.end - old_range.start;
+ let new_len = new_range.end - new_range.start;
+ let old_start = (old_range.start as isize + delta) as usize;
+ let new_text: String = buffer.text_for_range(new_range).unwrap().collect();
+ old_buffer
+ .edit(Some(old_start..old_start + old_len), new_text, None)
+ .unwrap();
+
+ delta += new_len as isize - old_len as isize;
+ }
+ assert_eq!(old_buffer.text(), buffer.text());
+ }
+
+ buffer
});
}
}
- #[test]
- fn test_line_len() {
- App::test((), |ctx| {
- 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();
- buffer.edit(vec![18..21], "\nPQ", None).unwrap();
+ #[gpui::test]
+ fn test_line_len(ctx: &mut gpui::MutableAppContext) {
+ 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();
+ buffer.edit(vec![18..21], "\nPQ", None).unwrap();
- assert_eq!(buffer.line_len(0).unwrap(), 4);
- assert_eq!(buffer.line_len(1).unwrap(), 3);
- assert_eq!(buffer.line_len(2).unwrap(), 5);
- assert_eq!(buffer.line_len(3).unwrap(), 3);
- assert_eq!(buffer.line_len(4).unwrap(), 4);
- assert_eq!(buffer.line_len(5).unwrap(), 0);
- assert!(buffer.line_len(6).is_err());
- buffer
- });
+ assert_eq!(buffer.line_len(0).unwrap(), 4);
+ assert_eq!(buffer.line_len(1).unwrap(), 3);
+ assert_eq!(buffer.line_len(2).unwrap(), 5);
+ assert_eq!(buffer.line_len(3).unwrap(), 3);
+ assert_eq!(buffer.line_len(4).unwrap(), 4);
+ assert_eq!(buffer.line_len(5).unwrap(), 0);
+ assert!(buffer.line_len(6).is_err());
+ buffer
});
}
- #[test]
- fn test_rightmost_point() {
- App::test((), |ctx| {
- 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);
- buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
- assert_eq!(buffer.rightmost_point().row, 2);
- buffer.edit(vec![18..18], "\npqrs", None).unwrap();
- assert_eq!(buffer.rightmost_point().row, 2);
- buffer.edit(vec![10..12], "", None).unwrap();
- assert_eq!(buffer.rightmost_point().row, 0);
- buffer.edit(vec![24..24], "tuv", None).unwrap();
- assert_eq!(buffer.rightmost_point().row, 4);
- buffer
- });
+ #[gpui::test]
+ fn test_rightmost_point(ctx: &mut gpui::MutableAppContext) {
+ 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);
+ buffer.edit(vec![12..12], "kl\nmno", None).unwrap();
+ assert_eq!(buffer.rightmost_point().row, 2);
+ buffer.edit(vec![18..18], "\npqrs", None).unwrap();
+ assert_eq!(buffer.rightmost_point().row, 2);
+ buffer.edit(vec![10..12], "", None).unwrap();
+ assert_eq!(buffer.rightmost_point().row, 0);
+ buffer.edit(vec![24..24], "tuv", None).unwrap();
+ assert_eq!(buffer.rightmost_point().row, 4);
+ buffer
});
}
- #[test]
- fn test_text_summary_for_range() {
- App::test((), |ctx| {
- 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),
- text.slice(1..3).summary()
- );
- assert_eq!(
- buffer.text_summary_for_range(1..12),
- text.slice(1..12).summary()
- );
- assert_eq!(
- buffer.text_summary_for_range(0..20),
- text.slice(0..20).summary()
- );
- assert_eq!(
- buffer.text_summary_for_range(0..22),
- text.slice(0..22).summary()
- );
- assert_eq!(
- buffer.text_summary_for_range(7..22),
- text.slice(7..22).summary()
- );
- buffer
- });
+ #[gpui::test]
+ fn test_text_summary_for_range(ctx: &mut gpui::MutableAppContext) {
+ 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),
+ text.slice(1..3).summary()
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(1..12),
+ text.slice(1..12).summary()
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(0..20),
+ text.slice(0..20).summary()
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(0..22),
+ text.slice(0..22).summary()
+ );
+ assert_eq!(
+ buffer.text_summary_for_range(7..22),
+ text.slice(7..22).summary()
+ );
+ buffer
});
}
- #[test]
- fn test_chars_at() {
- App::test((), |ctx| {
- ctx.add_model(|ctx| {
+ #[gpui::test]
+ fn test_chars_at(ctx: &mut gpui::MutableAppContext) {
+ 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();
@@ -2764,7 +2756,6 @@ mod tests {
buffer
});
- });
}
// #[test]
@@ -2881,196 +2872,192 @@ mod tests {
}
}
- #[test]
- fn test_anchors() {
- App::test((), |ctx| {
- 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();
-
- buffer.edit(vec![1..1], "def\n", None).unwrap();
- assert_eq!(buffer.text(), "adef\nbc");
- assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 6);
- assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 6);
- assert_eq!(
- left_anchor.to_point(&buffer).unwrap(),
- Point { row: 1, column: 1 }
- );
- assert_eq!(
- right_anchor.to_point(&buffer).unwrap(),
- Point { row: 1, column: 1 }
- );
+ #[gpui::test]
+ fn test_anchors(ctx: &mut gpui::MutableAppContext) {
+ 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();
- buffer.edit(vec![2..3], "", None).unwrap();
- assert_eq!(buffer.text(), "adf\nbc");
- assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
- assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 5);
- assert_eq!(
- left_anchor.to_point(&buffer).unwrap(),
- Point { row: 1, column: 1 }
- );
- assert_eq!(
- right_anchor.to_point(&buffer).unwrap(),
- Point { row: 1, column: 1 }
- );
+ buffer.edit(vec![1..1], "def\n", None).unwrap();
+ assert_eq!(buffer.text(), "adef\nbc");
+ assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 6);
+ assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 6);
+ assert_eq!(
+ left_anchor.to_point(&buffer).unwrap(),
+ Point { row: 1, column: 1 }
+ );
+ assert_eq!(
+ right_anchor.to_point(&buffer).unwrap(),
+ Point { row: 1, column: 1 }
+ );
- buffer.edit(vec![5..5], "ghi\n", None).unwrap();
- assert_eq!(buffer.text(), "adf\nbghi\nc");
- assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
- assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 9);
- assert_eq!(
- left_anchor.to_point(&buffer).unwrap(),
- Point { row: 1, column: 1 }
- );
- assert_eq!(
- right_anchor.to_point(&buffer).unwrap(),
- Point { row: 2, column: 0 }
- );
+ buffer.edit(vec![2..3], "", None).unwrap();
+ assert_eq!(buffer.text(), "adf\nbc");
+ assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
+ assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 5);
+ assert_eq!(
+ left_anchor.to_point(&buffer).unwrap(),
+ Point { row: 1, column: 1 }
+ );
+ assert_eq!(
+ right_anchor.to_point(&buffer).unwrap(),
+ Point { row: 1, column: 1 }
+ );
- buffer.edit(vec![7..9], "", None).unwrap();
- assert_eq!(buffer.text(), "adf\nbghc");
- assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
- assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 7);
- assert_eq!(
- left_anchor.to_point(&buffer).unwrap(),
- Point { row: 1, column: 1 },
- );
- assert_eq!(
- right_anchor.to_point(&buffer).unwrap(),
- Point { row: 1, column: 3 }
- );
+ buffer.edit(vec![5..5], "ghi\n", None).unwrap();
+ assert_eq!(buffer.text(), "adf\nbghi\nc");
+ assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
+ assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 9);
+ assert_eq!(
+ left_anchor.to_point(&buffer).unwrap(),
+ Point { row: 1, column: 1 }
+ );
+ assert_eq!(
+ right_anchor.to_point(&buffer).unwrap(),
+ Point { row: 2, column: 0 }
+ );
- // Ensure anchoring to a point is equivalent to anchoring to an offset.
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 0 }).unwrap(),
- buffer.anchor_before(0).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 1 }).unwrap(),
- buffer.anchor_before(1).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 2 }).unwrap(),
- buffer.anchor_before(2).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 0, column: 3 }).unwrap(),
- buffer.anchor_before(3).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 0 }).unwrap(),
- buffer.anchor_before(4).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 1 }).unwrap(),
- buffer.anchor_before(5).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 2 }).unwrap(),
- buffer.anchor_before(6).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 3 }).unwrap(),
- buffer.anchor_before(7).unwrap()
- );
- assert_eq!(
- buffer.anchor_before(Point { row: 1, column: 4 }).unwrap(),
- buffer.anchor_before(8).unwrap()
- );
+ buffer.edit(vec![7..9], "", None).unwrap();
+ assert_eq!(buffer.text(), "adf\nbghc");
+ assert_eq!(left_anchor.to_offset(&buffer).unwrap(), 5);
+ assert_eq!(right_anchor.to_offset(&buffer).unwrap(), 7);
+ assert_eq!(
+ left_anchor.to_point(&buffer).unwrap(),
+ Point { row: 1, column: 1 },
+ );
+ assert_eq!(
+ right_anchor.to_point(&buffer).unwrap(),
+ Point { row: 1, column: 3 }
+ );
- // Comparison between anchors.
- let anchor_at_offset_0 = buffer.anchor_before(0).unwrap();
- let anchor_at_offset_1 = buffer.anchor_before(1).unwrap();
- let anchor_at_offset_2 = buffer.anchor_before(2).unwrap();
+ // Ensure anchoring to a point is equivalent to anchoring to an offset.
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 0 }).unwrap(),
+ buffer.anchor_before(0).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 1 }).unwrap(),
+ buffer.anchor_before(1).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 2 }).unwrap(),
+ buffer.anchor_before(2).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 0, column: 3 }).unwrap(),
+ buffer.anchor_before(3).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 0 }).unwrap(),
+ buffer.anchor_before(4).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 1 }).unwrap(),
+ buffer.anchor_before(5).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 2 }).unwrap(),
+ buffer.anchor_before(6).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 3 }).unwrap(),
+ buffer.anchor_before(7).unwrap()
+ );
+ assert_eq!(
+ buffer.anchor_before(Point { row: 1, column: 4 }).unwrap(),
+ buffer.anchor_before(8).unwrap()
+ );
- assert_eq!(
- anchor_at_offset_0
- .cmp(&anchor_at_offset_0, &buffer)
- .unwrap(),
- Ordering::Equal
- );
- assert_eq!(
- anchor_at_offset_1
- .cmp(&anchor_at_offset_1, &buffer)
- .unwrap(),
- Ordering::Equal
- );
- assert_eq!(
- anchor_at_offset_2
- .cmp(&anchor_at_offset_2, &buffer)
- .unwrap(),
- Ordering::Equal
- );
+ // Comparison between anchors.
+ let anchor_at_offset_0 = buffer.anchor_before(0).unwrap();
+ let anchor_at_offset_1 = buffer.anchor_before(1).unwrap();
+ let anchor_at_offset_2 = buffer.anchor_before(2).unwrap();
- assert_eq!(
- anchor_at_offset_0
- .cmp(&anchor_at_offset_1, &buffer)
- .unwrap(),
- Ordering::Less
- );
- assert_eq!(
- anchor_at_offset_1
- .cmp(&anchor_at_offset_2, &buffer)
- .unwrap(),
- Ordering::Less
- );
- assert_eq!(
- anchor_at_offset_0
- .cmp(&anchor_at_offset_2, &buffer)
- .unwrap(),
- Ordering::Less
- );
+ assert_eq!(
+ anchor_at_offset_0
+ .cmp(&anchor_at_offset_0, &buffer)
+ .unwrap(),
+ Ordering::Equal
+ );
+ assert_eq!(
+ anchor_at_offset_1
+ .cmp(&anchor_at_offset_1, &buffer)
+ .unwrap(),
+ Ordering::Equal
+ );
+ assert_eq!(
+ anchor_at_offset_2
+ .cmp(&anchor_at_offset_2, &buffer)
+ .unwrap(),
+ Ordering::Equal
+ );
- assert_eq!(
- anchor_at_offset_1
- .cmp(&anchor_at_offset_0, &buffer)
- .unwrap(),
- Ordering::Greater
- );
- assert_eq!(
- anchor_at_offset_2
- .cmp(&anchor_at_offset_1, &buffer)
- .unwrap(),
- Ordering::Greater
- );
- assert_eq!(
- anchor_at_offset_2
- .cmp(&anchor_at_offset_0, &buffer)
- .unwrap(),
- Ordering::Greater
- );
- buffer
- });
+ assert_eq!(
+ anchor_at_offset_0
+ .cmp(&anchor_at_offset_1, &buffer)
+ .unwrap(),
+ Ordering::Less
+ );
+ assert_eq!(
+ anchor_at_offset_1
+ .cmp(&anchor_at_offset_2, &buffer)
+ .unwrap(),
+ Ordering::Less
+ );
+ assert_eq!(
+ anchor_at_offset_0
+ .cmp(&anchor_at_offset_2, &buffer)
+ .unwrap(),
+ Ordering::Less
+ );
+
+ assert_eq!(
+ anchor_at_offset_1
+ .cmp(&anchor_at_offset_0, &buffer)
+ .unwrap(),
+ Ordering::Greater
+ );
+ assert_eq!(
+ anchor_at_offset_2
+ .cmp(&anchor_at_offset_1, &buffer)
+ .unwrap(),
+ Ordering::Greater
+ );
+ assert_eq!(
+ anchor_at_offset_2
+ .cmp(&anchor_at_offset_0, &buffer)
+ .unwrap(),
+ Ordering::Greater
+ );
+ buffer
});
}
- #[test]
- fn test_anchors_at_start_and_end() {
- App::test((), |ctx| {
- 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();
-
- buffer.edit(vec![0..0], "abc", None).unwrap();
- assert_eq!(buffer.text(), "abc");
- assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
- assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 3);
-
- let after_start_anchor = buffer.anchor_after(0).unwrap();
- let before_end_anchor = buffer.anchor_before(3).unwrap();
-
- buffer.edit(vec![3..3], "def", None).unwrap();
- buffer.edit(vec![0..0], "ghi", None).unwrap();
- assert_eq!(buffer.text(), "ghiabcdef");
- assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
- assert_eq!(after_start_anchor.to_offset(&buffer).unwrap(), 3);
- assert_eq!(before_end_anchor.to_offset(&buffer).unwrap(), 6);
- assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9);
- buffer
- });
+ #[gpui::test]
+ fn test_anchors_at_start_and_end(ctx: &mut gpui::MutableAppContext) {
+ 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();
+
+ buffer.edit(vec![0..0], "abc", None).unwrap();
+ assert_eq!(buffer.text(), "abc");
+ assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
+ assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 3);
+
+ let after_start_anchor = buffer.anchor_after(0).unwrap();
+ let before_end_anchor = buffer.anchor_before(3).unwrap();
+
+ buffer.edit(vec![3..3], "def", None).unwrap();
+ buffer.edit(vec![0..0], "ghi", None).unwrap();
+ assert_eq!(buffer.text(), "ghiabcdef");
+ assert_eq!(before_start_anchor.to_offset(&buffer).unwrap(), 0);
+ assert_eq!(after_start_anchor.to_offset(&buffer).unwrap(), 3);
+ assert_eq!(before_end_anchor.to_offset(&buffer).unwrap(), 6);
+ assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9);
+ buffer
});
}
@@ -3190,227 +3177,219 @@ mod tests {
});
}
- #[test]
- fn test_file_changes_on_disk() {
- App::test_async((), |mut app| async move {
- let initial_contents = "aaa\nbbbbb\nc\n";
- let dir = temp_tree(json!({ "the-file": initial_contents }));
- let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
- app.read(|ctx| tree.read(ctx).scan_complete()).await;
+ #[gpui::test]
+ async fn test_file_changes_on_disk(mut app: gpui::TestAppContext) {
+ let initial_contents = "aaa\nbbbbb\nc\n";
+ let dir = temp_tree(json!({ "the-file": initial_contents }));
+ let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
+ app.read(|ctx| tree.read(ctx).scan_complete()).await;
- let abs_path = dir.path().join("the-file");
- let file = app.read(|ctx| tree.file("the-file", ctx));
- let buffer = app.add_model(|ctx| {
- Buffer::from_history(0, History::new(initial_contents.into()), Some(file), ctx)
- });
-
- // Add a cursor at the start of each row.
- let (selection_set_id, _) = buffer.update(&mut app, |buffer, ctx| {
- assert!(!buffer.is_dirty());
- buffer.add_selection_set(
- (0..3)
- .map(|row| {
- let anchor = buffer
- .anchor_at(Point::new(row, 0), AnchorBias::Right)
- .unwrap();
- Selection {
- id: row as usize,
- start: anchor.clone(),
- end: anchor,
- reversed: false,
- goal: SelectionGoal::None,
- }
- })
- .collect::<Vec<_>>(),
- Some(ctx),
- )
- });
-
- // Change the file on disk, adding two new lines of text, and removing
- // one line.
- buffer.update(&mut app, |buffer, _| {
- assert!(!buffer.is_dirty());
- assert!(!buffer.has_conflict());
- });
- tree.flush_fs_events(&app).await;
- let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
+ let abs_path = dir.path().join("the-file");
+ let file = app.read(|ctx| tree.file("the-file", ctx));
+ let buffer = app.add_model(|ctx| {
+ Buffer::from_history(0, History::new(initial_contents.into()), Some(file), ctx)
+ });
- fs::write(&abs_path, new_contents).unwrap();
+ // Add a cursor at the start of each row.
+ let (selection_set_id, _) = buffer.update(&mut app, |buffer, ctx| {
+ assert!(!buffer.is_dirty());
+ buffer.add_selection_set(
+ (0..3)
+ .map(|row| {
+ let anchor = buffer
+ .anchor_at(Point::new(row, 0), AnchorBias::Right)
+ .unwrap();
+ Selection {
+ id: row as usize,
+ start: anchor.clone(),
+ end: anchor,
+ reversed: false,
+ goal: SelectionGoal::None,
+ }
+ })
+ .collect::<Vec<_>>(),
+ Some(ctx),
+ )
+ });
- // Because the buffer was not modified, it is reloaded from disk. Its
- // contents are edited according to the diff between the old and new
- // file contents.
- buffer
- .condition_with_duration(Duration::from_millis(500), &app, |buffer, _| {
- buffer.text() != initial_contents
- })
- .await;
+ // Change the file on disk, adding two new lines of text, and removing
+ // one line.
+ buffer.update(&mut app, |buffer, _| {
+ assert!(!buffer.is_dirty());
+ assert!(!buffer.has_conflict());
+ });
+ tree.flush_fs_events(&app).await;
+ let new_contents = "AAAA\naaa\nBB\nbbbbb\n";
- buffer.update(&mut app, |buffer, _| {
- assert_eq!(buffer.text(), new_contents);
- assert!(!buffer.is_dirty());
- assert!(!buffer.has_conflict());
-
- let selections = buffer.selections(selection_set_id).unwrap();
- let cursor_positions = selections
- .iter()
- .map(|selection| {
- assert_eq!(selection.start, selection.end);
- selection.start.to_point(&buffer).unwrap()
- })
- .collect::<Vec<_>>();
- assert_eq!(
- cursor_positions,
- &[Point::new(1, 0), Point::new(3, 0), Point::new(4, 0),]
- );
- });
+ fs::write(&abs_path, new_contents).unwrap();
- // Modify the buffer
- buffer.update(&mut app, |buffer, ctx| {
- buffer.edit(vec![0..0], " ", Some(ctx)).unwrap();
- assert!(buffer.is_dirty());
- });
+ // Because the buffer was not modified, it is reloaded from disk. Its
+ // contents are edited according to the diff between the old and new
+ // file contents.
+ buffer
+ .condition_with_duration(Duration::from_millis(500), &app, |buffer, _| {
+ buffer.text() != initial_contents
+ })
+ .await;
- // Change the file on disk again, adding blank lines to the beginning.
- fs::write(&abs_path, "\n\n\nAAAA\naaa\nBB\nbbbbb\n").unwrap();
+ buffer.update(&mut app, |buffer, _| {
+ assert_eq!(buffer.text(), new_contents);
+ assert!(!buffer.is_dirty());
+ assert!(!buffer.has_conflict());
- // Becaues the buffer is modified, it doesn't reload from disk, but is
- // marked as having a conflict.
- buffer
- .condition(&app, |buffer, _| buffer.has_conflict())
- .await;
+ let selections = buffer.selections(selection_set_id).unwrap();
+ let cursor_positions = selections
+ .iter()
+ .map(|selection| {
+ assert_eq!(selection.start, selection.end);
+ selection.start.to_point(&buffer).unwrap()
+ })
+ .collect::<Vec<_>>();
+ assert_eq!(
+ cursor_positions,
+ &[Point::new(1, 0), Point::new(3, 0), Point::new(4, 0),]
+ );
});
- }
- #[test]
- fn test_set_text_via_diff() {
- App::test_async((), |mut app| async move {
- let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
- let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
+ // Modify the buffer
+ buffer.update(&mut app, |buffer, ctx| {
+ buffer.edit(vec![0..0], " ", Some(ctx)).unwrap();
+ assert!(buffer.is_dirty());
+ });
- let text = "a\nccc\ndddd\nffffff\n";
- buffer
- .update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx))
- .await;
- app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text));
+ // Change the file on disk again, adding blank lines to the beginning.
+ fs::write(&abs_path, "\n\n\nAAAA\naaa\nBB\nbbbbb\n").unwrap();
+
+ // Becaues the buffer is modified, it doesn't reload from disk, but is
+ // marked as having a conflict.
+ buffer
+ .condition(&app, |buffer, _| buffer.has_conflict())
+ .await;
+ }
+
+ #[gpui::test]
+ async fn test_set_text_via_diff(mut app: gpui::TestAppContext) {
+ let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
+ let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
+
+ let text = "a\nccc\ndddd\nffffff\n";
+ buffer
+ .update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx))
+ .await;
+ app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text));
+
+ let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
+ buffer
+ .update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx))
+ .await;
+ app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text));
+ }
+
+ #[gpui::test]
+ fn test_undo_redo(app: &mut gpui::MutableAppContext) {
+ 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();
+ let edit3 = buffer.edit(vec![3..5], "cd", None).unwrap();
+ assert_eq!(buffer.text(), "1abcdef234");
+
+ buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1cdef234");
+ buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1abcdef234");
+
+ buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1abcdx234");
+ buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1abx234");
+ buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1abyzef234");
+ buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1abcdef234");
+
+ buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1abyzef234");
+ buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1yzef234");
+ buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
+ assert_eq!(buffer.text(), "1234");
- let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
buffer
- .update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx))
- .await;
- app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text));
});
}
- #[test]
- fn test_undo_redo() {
- App::test((), |app| {
- 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();
- let edit3 = buffer.edit(vec![3..5], "cd", None).unwrap();
- assert_eq!(buffer.text(), "1abcdef234");
-
- buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1cdef234");
- buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1abcdef234");
-
- buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1abcdx234");
- buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1abx234");
- buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1abyzef234");
- buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1abcdef234");
-
- buffer.undo_or_redo(edit3[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1abyzef234");
- buffer.undo_or_redo(edit1[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1yzef234");
- buffer.undo_or_redo(edit2[0].edit_id().unwrap()).unwrap();
- assert_eq!(buffer.text(), "1234");
+ #[gpui::test]
+ fn test_history(app: &mut gpui::MutableAppContext) {
+ app.add_model(|ctx| {
+ let mut now = Instant::now();
+ let mut buffer = Buffer::new(0, "123456", ctx);
- buffer
- });
- });
- }
+ let (set_id, _) =
+ buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None);
+ buffer.start_transaction_at(Some(set_id), now).unwrap();
+ buffer.edit(vec![2..4], "cd", None).unwrap();
+ buffer.end_transaction_at(Some(set_id), now, None).unwrap();
+ assert_eq!(buffer.text(), "12cd56");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
- #[test]
- fn test_history() {
- App::test((), |app| {
- app.add_model(|ctx| {
- let mut now = Instant::now();
- let mut buffer = Buffer::new(0, "123456", ctx);
-
- let (set_id, _) = buffer
- .add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None);
- buffer.start_transaction_at(Some(set_id), now).unwrap();
- buffer.edit(vec![2..4], "cd", None).unwrap();
- buffer.end_transaction_at(Some(set_id), now, None).unwrap();
- assert_eq!(buffer.text(), "12cd56");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
-
- buffer.start_transaction_at(Some(set_id), now).unwrap();
- buffer
- .update_selection_set(
- set_id,
- buffer.selections_from_ranges(vec![1..3]).unwrap(),
- None,
- )
- .unwrap();
- buffer.edit(vec![4..5], "e", None).unwrap();
- buffer.end_transaction_at(Some(set_id), now, None).unwrap();
- assert_eq!(buffer.text(), "12cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+ buffer.start_transaction_at(Some(set_id), now).unwrap();
+ buffer
+ .update_selection_set(
+ set_id,
+ buffer.selections_from_ranges(vec![1..3]).unwrap(),
+ None,
+ )
+ .unwrap();
+ buffer.edit(vec![4..5], "e", None).unwrap();
+ buffer.end_transaction_at(Some(set_id), now, None).unwrap();
+ assert_eq!(buffer.text(), "12cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
- now += UNDO_GROUP_INTERVAL + Duration::from_millis(1);
- buffer.start_transaction_at(Some(set_id), now).unwrap();
- buffer
- .update_selection_set(
- set_id,
- buffer.selections_from_ranges(vec![2..2]).unwrap(),
- None,
- )
- .unwrap();
- buffer.edit(vec![0..1], "a", None).unwrap();
- buffer.edit(vec![1..1], "b", None).unwrap();
- buffer.end_transaction_at(Some(set_id), now, None).unwrap();
- assert_eq!(buffer.text(), "ab2cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
-
- // Last transaction happened past the group interval, undo it on its
- // own.
- buffer.undo(None);
- assert_eq!(buffer.text(), "12cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
- // First two transactions happened within the group interval, undo them
- // together.
- buffer.undo(None);
- assert_eq!(buffer.text(), "123456");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
-
- // Redo the first two transactions together.
- buffer.redo(None);
- assert_eq!(buffer.text(), "12cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
-
- // Redo the last transaction on its own.
- buffer.redo(None);
- assert_eq!(buffer.text(), "ab2cde6");
- assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+ now += UNDO_GROUP_INTERVAL + Duration::from_millis(1);
+ buffer.start_transaction_at(Some(set_id), now).unwrap();
+ buffer
+ .update_selection_set(
+ set_id,
+ buffer.selections_from_ranges(vec![2..2]).unwrap(),
+ None,
+ )
+ .unwrap();
+ buffer.edit(vec![0..1], "a", None).unwrap();
+ buffer.edit(vec![1..1], "b", None).unwrap();
+ buffer.end_transaction_at(Some(set_id), now, None).unwrap();
+ assert_eq!(buffer.text(), "ab2cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
+
+ // Last transaction happened past the group interval, undo it on its
+ // own.
+ buffer.undo(None);
+ assert_eq!(buffer.text(), "12cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+ // First two transactions happened within the group interval, undo them
+ // together.
+ buffer.undo(None);
+ assert_eq!(buffer.text(), "123456");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
+
+ // Redo the first two transactions together.
+ buffer.redo(None);
+ assert_eq!(buffer.text(), "12cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
+
+ // Redo the last transaction on its own.
+ buffer.redo(None);
+ assert_eq!(buffer.text(), "ab2cde6");
+ assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
- buffer
- });
+ buffer
});
}
- #[test]
- fn test_random_concurrent_edits() {
+ #[gpui::test]
+ fn test_random_concurrent_edits(ctx: &mut gpui::MutableAppContext) {
use crate::test::Network;
const PEERS: usize = 5;
@@ -4,11 +4,10 @@ use super::{
};
use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle};
use anyhow::Result;
-use futures_core::future::LocalBoxFuture;
use gpui::{
fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout,
AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle,
- MutableAppContext, TextLayoutCache, View, ViewContext, WeakViewHandle,
+ MutableAppContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle,
};
use parking_lot::Mutex;
use postage::watch;
@@ -2348,13 +2347,12 @@ impl BufferView {
ctx.notify();
let epoch = self.next_blink_epoch();
- ctx.spawn(
- async move {
- Timer::after(CURSOR_BLINK_INTERVAL).await;
- epoch
- },
- Self::resume_cursor_blinking,
- )
+ ctx.spawn(|this, mut ctx| async move {
+ Timer::after(CURSOR_BLINK_INTERVAL).await;
+ this.update(&mut ctx, |this, ctx| {
+ this.resume_cursor_blinking(epoch, ctx);
+ })
+ })
.detach();
}
@@ -2371,13 +2369,10 @@ impl BufferView {
ctx.notify();
let epoch = self.next_blink_epoch();
- ctx.spawn(
- async move {
- Timer::after(CURSOR_BLINK_INTERVAL).await;
- epoch
- },
- Self::blink_cursors,
- )
+ ctx.spawn(|this, mut ctx| async move {
+ Timer::after(CURSOR_BLINK_INTERVAL).await;
+ this.update(&mut ctx, |this, ctx| this.blink_cursors(epoch, ctx));
+ })
.detach();
}
}
@@ -2499,7 +2494,7 @@ impl workspace::ItemView for BufferView {
&mut self,
new_file: Option<FileHandle>,
ctx: &mut ViewContext<Self>,
- ) -> LocalBoxFuture<'static, Result<()>> {
+ ) -> Task<Result<()>> {
self.buffer.update(ctx, |b, ctx| b.save(new_file, ctx))
}
@@ -2516,222 +2511,207 @@ impl workspace::ItemView for BufferView {
mod tests {
use super::*;
use crate::{editor::Point, settings, test::sample_text};
- use gpui::App;
use unindent::Unindent;
- #[test]
- fn test_selection_with_mouse() {
- App::test((), |app| {
- 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, settings, ctx));
-
- buffer_view.update(app, |view, ctx| {
- view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
- });
+ #[gpui::test]
+ fn test_selection_with_mouse(app: &mut gpui::MutableAppContext) {
+ 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, settings, ctx));
- let view = buffer_view.read(app);
- let selections = view
- .selections_in_range(
- DisplayPoint::zero()..view.max_point(app.as_ref()),
- app.as_ref(),
- )
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
- );
+ buffer_view.update(app, |view, ctx| {
+ view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
+ });
- buffer_view.update(app, |view, ctx| {
- view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+ );
- let view = buffer_view.read(app);
- let selections = view
- .selections_in_range(
- DisplayPoint::zero()..view.max_point(app.as_ref()),
- app.as_ref(),
- )
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
- );
+ buffer_view.update(app, |view, ctx| {
+ view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
+ });
- buffer_view.update(app, |view, ctx| {
- view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+ );
- let view = buffer_view.read(app);
- let selections = view
- .selections_in_range(
- DisplayPoint::zero()..view.max_point(app.as_ref()),
- app.as_ref(),
- )
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
- );
+ buffer_view.update(app, |view, ctx| {
+ view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
+ });
- buffer_view.update(app, |view, ctx| {
- view.end_selection(ctx);
- view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+ );
- let view = buffer_view.read(app);
- let selections = view
- .selections_in_range(
- DisplayPoint::zero()..view.max_point(app.as_ref()),
- app.as_ref(),
- )
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
- );
+ buffer_view.update(app, |view, ctx| {
+ view.end_selection(ctx);
+ view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
+ });
- buffer_view.update(app, |view, ctx| {
- view.begin_selection(DisplayPoint::new(3, 3), true, ctx);
- view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx);
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1)]
+ );
- let view = buffer_view.read(app);
- let selections = view
- .selections_in_range(
- DisplayPoint::zero()..view.max_point(app.as_ref()),
- app.as_ref(),
- )
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [
- DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
- DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
- ]
- );
+ buffer_view.update(app, |view, ctx| {
+ view.begin_selection(DisplayPoint::new(3, 3), true, ctx);
+ view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), ctx);
+ });
- buffer_view.update(app, |view, ctx| {
- view.end_selection(ctx);
- });
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [
+ DisplayPoint::new(2, 2)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)
+ ]
+ );
- let view = buffer_view.read(app);
- let selections = view
- .selections_in_range(
- DisplayPoint::zero()..view.max_point(app.as_ref()),
- app.as_ref(),
- )
- .collect::<Vec<_>>();
- assert_eq!(
- selections,
- [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
- );
+ buffer_view.update(app, |view, ctx| {
+ view.end_selection(ctx);
});
+
+ let view = buffer_view.read(app);
+ let selections = view
+ .selections_in_range(
+ DisplayPoint::zero()..view.max_point(app.as_ref()),
+ app.as_ref(),
+ )
+ .collect::<Vec<_>>();
+ assert_eq!(
+ selections,
+ [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
+ );
}
- #[test]
- fn test_canceling_pending_selection() {
- App::test((), |app| {
- 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 (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
+ #[gpui::test]
+ fn test_canceling_pending_selection(app: &mut gpui::MutableAppContext) {
+ 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 (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
- view.update(app, |view, ctx| {
- view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
- );
+ view.update(app, |view, ctx| {
+ view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2)]
+ );
- view.update(app, |view, ctx| {
- view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
- );
+ view.update(app, |view, ctx| {
+ view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), ctx);
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+ );
- view.update(app, |view, ctx| {
- view.cancel(&(), ctx);
- view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
- );
+ view.update(app, |view, ctx| {
+ view.cancel(&(), ctx);
+ view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
});
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
+ );
}
- #[test]
- fn test_cancel() {
- App::test((), |app| {
- 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 (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
+ #[gpui::test]
+ fn test_cancel(app: &mut gpui::MutableAppContext) {
+ 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 (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
- view.update(app, |view, ctx| {
- view.begin_selection(DisplayPoint::new(3, 4), false, ctx);
- view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
- view.end_selection(ctx);
+ view.update(app, |view, ctx| {
+ view.begin_selection(DisplayPoint::new(3, 4), false, ctx);
+ view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), ctx);
+ view.end_selection(ctx);
- view.begin_selection(DisplayPoint::new(0, 1), true, ctx);
- view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), ctx);
- view.end_selection(ctx);
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- [
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
- DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
- ]
- );
+ view.begin_selection(DisplayPoint::new(0, 1), true, ctx);
+ view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), ctx);
+ view.end_selection(ctx);
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1),
+ ]
+ );
- view.update(app, |view, ctx| view.cancel(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
- );
+ view.update(app, |view, ctx| view.cancel(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [DisplayPoint::new(3, 4)..DisplayPoint::new(1, 1)]
+ );
- view.update(app, |view, ctx| view.cancel(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
- );
- });
+ view.update(app, |view, ctx| view.cancel(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
+ );
}
- #[test]
- fn test_layout_line_numbers() {
- App::test((), |app| {
- let layout_cache = TextLayoutCache::new(app.platform().fonts());
- let font_cache = app.font_cache().clone();
+ #[gpui::test]
+ fn test_layout_line_numbers(app: &mut gpui::MutableAppContext) {
+ let layout_cache = TextLayoutCache::new(app.platform().fonts());
+ let font_cache = app.font_cache().clone();
- let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 6), ctx));
+ 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(), settings, ctx));
+ let settings = settings::channel(&font_cache).unwrap().1;
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
- let layouts = view
- .read(app)
- .layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref())
- .unwrap();
- assert_eq!(layouts.len(), 6);
- })
+ let layouts = view
+ .read(app)
+ .layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref())
+ .unwrap();
+ assert_eq!(layouts.len(), 6);
}
- #[test]
- fn test_fold() {
- App::test((), |app| {
- let buffer = app.add_model(|ctx| {
- Buffer::new(
- 0,
- "
+ #[gpui::test]
+ fn test_fold(app: &mut gpui::MutableAppContext) {
+ let buffer = app.add_model(|ctx| {
+ Buffer::new(
+ 0,
+ "
impl Foo {
// Hello!
@@ -2748,24 +2728,20 @@ mod tests {
}
}
"
- .unindent(),
- ctx,
- )
- });
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (_, view) =
- app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
-
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)],
- ctx,
- )
+ .unindent(),
+ ctx,
+ )
+ });
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
+
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
.unwrap();
- view.fold(&(), ctx);
- assert_eq!(
- view.text(ctx.as_ref()),
- "
+ view.fold(&(), ctx);
+ assert_eq!(
+ view.text(ctx.as_ref()),
+ "
impl Foo {
// Hello!
@@ -2780,23 +2756,23 @@ mod tests {
}
}
"
- .unindent(),
- );
+ .unindent(),
+ );
- view.fold(&(), ctx);
- assert_eq!(
- view.text(ctx.as_ref()),
- "
+ view.fold(&(), ctx);
+ assert_eq!(
+ view.text(ctx.as_ref()),
+ "
impl Foo {…
}
"
- .unindent(),
- );
+ .unindent(),
+ );
- view.unfold(&(), ctx);
- assert_eq!(
- view.text(ctx.as_ref()),
- "
+ view.unfold(&(), ctx);
+ assert_eq!(
+ view.text(ctx.as_ref()),
+ "
impl Foo {
// Hello!
@@ -2811,1109 +2787,1055 @@ mod tests {
}
}
"
- .unindent(),
- );
+ .unindent(),
+ );
- view.unfold(&(), ctx);
- assert_eq!(view.text(ctx.as_ref()), buffer.read(ctx).text());
- });
+ view.unfold(&(), ctx);
+ assert_eq!(view.text(ctx.as_ref()), buffer.read(ctx).text());
});
}
- #[test]
- fn test_move_cursor() {
- App::test((), |app| {
- 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(), settings, ctx));
-
- buffer.update(app, |buffer, ctx| {
- buffer
- .edit(
- vec![
- Point::new(1, 0)..Point::new(1, 0),
- Point::new(1, 1)..Point::new(1, 1),
- ],
- "\t",
- Some(ctx),
- )
- .unwrap();
- });
-
- view.update(app, |view, ctx| {
- view.move_down(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
- );
-
- view.move_right(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
- );
-
- view.move_left(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
- );
-
- view.move_up(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
- );
-
- view.move_to_end(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
- );
-
- view.move_to_beginning(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
- );
-
- view.select_display_ranges(
- &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)],
- ctx,
- )
- .unwrap();
- view.select_to_beginning(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
- );
-
- view.select_to_end(&(), ctx);
- assert_eq!(
- view.selection_ranges(ctx.as_ref()),
- &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
- );
- });
- });
- }
+ #[gpui::test]
+ fn test_move_cursor(app: &mut gpui::MutableAppContext) {
+ 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(), settings, ctx));
- #[test]
- fn test_beginning_end_of_line() {
- App::test((), |app| {
- 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, settings, ctx));
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
+ buffer.update(app, |buffer, ctx| {
+ buffer
+ .edit(
+ vec![
+ Point::new(1, 0)..Point::new(1, 0),
+ Point::new(1, 1)..Point::new(1, 1),
],
- ctx,
+ "\t",
+ Some(ctx),
)
.unwrap();
- });
-
- view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
- DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
- ]
- );
+ });
- view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx));
+ view.update(app, |view, ctx| {
+ view.move_down(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
- DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
);
- view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx));
+ view.move_right(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
- DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4)]
);
- view.update(app, |view, ctx| view.move_to_end_of_line(&(), ctx));
+ view.move_left(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)]
);
- // Moving to the end of line again is a no-op.
- view.update(app, |view, ctx| view.move_to_end_of_line(&(), ctx));
+ view.move_up(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
);
- view.update(app, |view, ctx| {
- view.move_left(&(), ctx);
- view.select_to_beginning_of_line(&true, ctx);
- });
+ view.move_to_end(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(5, 6)..DisplayPoint::new(5, 6)]
);
- view.update(app, |view, ctx| {
- view.select_to_beginning_of_line(&true, ctx)
- });
+ view.move_to_beginning(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
);
- view.update(app, |view, ctx| {
- view.select_to_beginning_of_line(&true, ctx)
- });
+ view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)], ctx)
+ .unwrap();
+ view.select_to_beginning(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 0)]
);
- view.update(app, |view, ctx| view.select_to_end_of_line(&(), ctx));
+ view.select_to_end(&(), ctx);
assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
- ]
+ view.selection_ranges(ctx.as_ref()),
+ &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
);
+ });
+ }
- view.update(app, |view, ctx| view.delete_to_end_of_line(&(), ctx));
- assert_eq!(view.read(app).text(app.as_ref()), "ab\n de");
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
+ #[gpui::test]
+ fn test_beginning_end_of_line(app: &mut gpui::MutableAppContext) {
+ 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, settings, ctx));
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
&[
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
- ]
- );
-
- view.update(app, |view, ctx| view.delete_to_beginning_of_line(&(), ctx));
- assert_eq!(view.read(app).text(app.as_ref()), "\n");
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
- DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
- ]
- );
+ ],
+ ctx,
+ )
+ .unwrap();
});
- }
- #[test]
- fn test_prev_next_word_boundary() {
- App::test((), |app| {
- 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, settings, ctx));
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[
- DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
- DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
- ],
- ctx,
- )
- .unwrap();
- });
+ view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.move_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
- DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
- ]
- );
+ view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.move_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7),
- DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
- ]
- );
+ view.update(app, |view, ctx| view.move_to_beginning_of_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.move_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
- DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
- ]
- );
+ view.update(app, |view, ctx| view.move_to_end_of_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.move_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
- ]
- );
+ // Moving to the end of line again is a no-op.
+ view.update(app, |view, ctx| view.move_to_end_of_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 5)..DisplayPoint::new(1, 5),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.move_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
- DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24),
- ]
- );
+ view.update(app, |view, ctx| {
+ view.move_left(&(), ctx);
+ view.select_to_beginning_of_line(&true, ctx);
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.move_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
- DisplayPoint::new(0, 23)..DisplayPoint::new(0, 23),
- ]
- );
+ view.update(app, |view, ctx| {
+ view.select_to_beginning_of_line(&true, ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 0),
+ ]
+ );
- view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
- DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24),
- ]
- );
+ view.update(app, |view, ctx| {
+ view.select_to_beginning_of_line(&true, ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 2),
+ ]
+ );
- view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
- DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
- ]
- );
+ view.update(app, |view, ctx| view.select_to_end_of_line(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 5),
+ ]
+ );
- view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7),
- DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
- ]
- );
+ view.update(app, |view, ctx| view.delete_to_end_of_line(&(), ctx));
+ assert_eq!(view.read(app).text(app.as_ref()), "ab\n de");
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 4),
+ ]
+ );
- view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
- DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
- ]
- );
+ view.update(app, |view, ctx| view.delete_to_beginning_of_line(&(), ctx));
+ assert_eq!(view.read(app).text(app.as_ref()), "\n");
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ ]
+ );
+ }
- view.update(app, |view, ctx| {
- view.move_right(&(), ctx);
- view.select_to_previous_word_boundary(&(), ctx);
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
+ #[gpui::test]
+ fn test_prev_next_word_boundary(app: &mut gpui::MutableAppContext) {
+ 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, settings, ctx));
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
&[
- DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9),
- DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2),
- ]
- );
+ DisplayPoint::new(0, 11)..DisplayPoint::new(0, 11),
+ DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4),
+ ],
+ ctx,
+ )
+ .unwrap();
+ });
- view.update(app, |view, ctx| {
- view.select_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 10)..DisplayPoint::new(0, 7),
- DisplayPoint::new(2, 3)..DisplayPoint::new(2, 0),
- ]
- );
+ view.update(app, |view, ctx| {
+ view.move_to_previous_word_boundary(&(), ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
+ DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
+ ]
+ );
- view.update(app, |view, ctx| view.select_to_next_word_boundary(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9),
- DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2),
- ]
- );
+ view.update(app, |view, ctx| {
+ view.move_to_previous_word_boundary(&(), ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7),
+ DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
+ ]
+ );
- view.update(app, |view, ctx| view.delete_to_next_word_boundary(&(), ctx));
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "use std::s::{foo, bar}\n\n {az.qux()}"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 10)..DisplayPoint::new(0, 10),
- DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
- ]
- );
+ view.update(app, |view, ctx| {
+ view.move_to_previous_word_boundary(&(), ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
+ DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.delete_to_previous_word_boundary(&(), ctx)
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "use std::::{foo, bar}\n\n az.qux()}"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
- DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
- ]
- );
+ view.update(app, |view, ctx| {
+ view.move_to_previous_word_boundary(&(), ctx)
});
- }
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ ]
+ );
- #[test]
- fn test_backspace() {
- App::test((), |app| {
- 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(), settings, ctx));
-
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[
- // an empty selection - the preceding character is deleted
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
- // one character selected - it is deleted
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
- // a line suffix selected - it is deleted
- DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
- ],
- ctx,
- )
- .unwrap();
- view.backspace(&(), ctx);
- });
+ view.update(app, |view, ctx| {
+ view.move_to_previous_word_boundary(&(), ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24),
+ ]
+ );
- assert_eq!(
- buffer.read(app).text(),
- "oe two three\nfou five six\nseven ten\n"
- );
- })
+ view.update(app, |view, ctx| {
+ view.move_to_previous_word_boundary(&(), ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(0, 23)..DisplayPoint::new(0, 23),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(0, 24)..DisplayPoint::new(0, 24),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 4)..DisplayPoint::new(0, 4),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 7)..DisplayPoint::new(0, 7),
+ DisplayPoint::new(2, 0)..DisplayPoint::new(2, 0),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.move_to_next_word_boundary(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
+ DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
+ ]
+ );
+
+ view.update(app, |view, ctx| {
+ view.move_right(&(), ctx);
+ view.select_to_previous_word_boundary(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9),
+ DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2),
+ ]
+ );
+
+ view.update(app, |view, ctx| {
+ view.select_to_previous_word_boundary(&(), ctx)
+ });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 10)..DisplayPoint::new(0, 7),
+ DisplayPoint::new(2, 3)..DisplayPoint::new(2, 0),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.select_to_next_word_boundary(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 10)..DisplayPoint::new(0, 9),
+ DisplayPoint::new(2, 3)..DisplayPoint::new(2, 2),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.delete_to_next_word_boundary(&(), ctx));
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "use std::s::{foo, bar}\n\n {az.qux()}"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 10)..DisplayPoint::new(0, 10),
+ DisplayPoint::new(2, 3)..DisplayPoint::new(2, 3),
+ ]
+ );
+
+ view.update(app, |view, ctx| {
+ view.delete_to_previous_word_boundary(&(), ctx)
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "use std::::{foo, bar}\n\n az.qux()}"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 9)..DisplayPoint::new(0, 9),
+ DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
+ ]
+ );
}
- #[test]
- fn test_delete() {
- App::test((), |app| {
- 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(), settings, ctx));
-
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[
- // an empty selection - the following character is deleted
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
- // one character selected - it is deleted
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
- // a line suffix selected - it is deleted
- DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
- ],
- ctx,
- )
- .unwrap();
- view.delete(&(), ctx);
- });
+ #[gpui::test]
+ fn test_backspace(app: &mut gpui::MutableAppContext) {
+ 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(), settings, ctx));
- assert_eq!(
- buffer.read(app).text(),
- "on two three\nfou five six\nseven ten\n"
- );
- })
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
+ &[
+ // an empty selection - the preceding character is deleted
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ // one character selected - it is deleted
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
+ // a line suffix selected - it is deleted
+ DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.backspace(&(), ctx);
+ });
+
+ assert_eq!(
+ buffer.read(app).text(),
+ "oe two three\nfou five six\nseven ten\n"
+ );
}
- #[test]
- fn test_delete_line() {
- App::test((), |app| {
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- 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(0, 1)..DisplayPoint::new(0, 1),
- DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
- DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
- ],
- ctx,
- )
- .unwrap();
- view.delete_line(&(), ctx);
- });
- assert_eq!(view.read(app).text(app.as_ref()), "ghi");
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
- ]
- );
+ #[gpui::test]
+ fn test_delete(app: &mut gpui::MutableAppContext) {
+ 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(), settings, ctx));
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- 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)],
- ctx,
- )
- .unwrap();
- view.delete_line(&(), ctx);
- });
- assert_eq!(view.read(app).text(app.as_ref()), "ghi\n");
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
- );
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
+ &[
+ // an empty selection - the following character is deleted
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ // one character selected - it is deleted
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
+ // a line suffix selected - it is deleted
+ DisplayPoint::new(2, 6)..DisplayPoint::new(3, 0),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.delete(&(), ctx);
});
+
+ assert_eq!(
+ buffer.read(app).text(),
+ "on two three\nfou five six\nseven ten\n"
+ );
}
- #[test]
- fn test_duplicate_line() {
- App::test((), |app| {
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- 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(0, 0)..DisplayPoint::new(0, 1),
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
- DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
- DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
- ],
- ctx,
- )
- .unwrap();
- view.duplicate_line(&(), ctx);
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "abc\nabc\ndef\ndef\nghi\n\n"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
+ #[gpui::test]
+ fn test_delete_line(app: &mut gpui::MutableAppContext) {
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ 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(0, 1)..DisplayPoint::new(0, 1),
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
- DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
- DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
- ]
- );
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.delete_line(&(), ctx);
+ });
+ assert_eq!(view.read(app).text(app.as_ref()), "ghi");
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0),
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)
+ ]
+ );
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- 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(0, 1)..DisplayPoint::new(1, 1),
- DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
- ],
- ctx,
- )
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ 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)], ctx)
.unwrap();
- view.duplicate_line(&(), ctx);
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "abc\ndef\nghi\nabc\ndef\nghi\n"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
- DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
- ]
- );
+ view.delete_line(&(), ctx);
});
+ assert_eq!(view.read(app).text(app.as_ref()), "ghi\n");
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
+ );
}
- #[test]
- fn test_move_line_up_down() {
- App::test((), |app| {
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- 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![
- 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, 1)..DisplayPoint::new(0, 1),
- DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
- DisplayPoint::new(3, 2)..DisplayPoint::new(4, 2),
- DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
- ],
- ctx,
- )
- .unwrap();
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj"
- );
+ #[gpui::test]
+ fn test_duplicate_line(app: &mut gpui::MutableAppContext) {
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ 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(0, 0)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
+ DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.duplicate_line(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "abc\nabc\ndef\ndef\nghi\n\n"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(1, 2)..DisplayPoint::new(1, 2),
+ DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+ DisplayPoint::new(6, 0)..DisplayPoint::new(6, 0),
+ ]
+ );
- view.update(app, |view, ctx| view.move_line_up(&(), ctx));
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
- DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
- DisplayPoint::new(2, 2)..DisplayPoint::new(3, 2),
- DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
- ]
- );
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ 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(0, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(1, 2)..DisplayPoint::new(2, 1),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.duplicate_line(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "abc\ndef\nghi\nabc\ndef\nghi\n"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(3, 1)..DisplayPoint::new(4, 1),
+ DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
+ ]
+ );
+ }
- view.update(app, |view, ctx| view.move_line_down(&(), ctx));
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
+ #[gpui::test]
+ fn test_move_line_up_down(app: &mut gpui::MutableAppContext) {
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ 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![
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
- DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
- DisplayPoint::new(3, 2)..DisplayPoint::new(4, 2),
- DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
- ]
+ 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.update(app, |view, ctx| view.move_line_down(&(), ctx));
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+ view.select_display_ranges(
+ &[
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
DisplayPoint::new(3, 2)..DisplayPoint::new(4, 2),
- DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
- ]
- );
-
- view.update(app, |view, ctx| view.move_line_up(&(), ctx));
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff"
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
- DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
- DisplayPoint::new(2, 2)..DisplayPoint::new(3, 2),
- DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
- ]
- );
+ DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2),
+ ],
+ ctx,
+ )
+ .unwrap();
});
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj"
+ );
+
+ view.update(app, |view, ctx| view.move_line_up(&(), ctx));
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+ DisplayPoint::new(2, 2)..DisplayPoint::new(3, 2),
+ DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
+ ]
+ );
+
+ view.update(app, |view, ctx| view.move_line_down(&(), ctx));
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
+ DisplayPoint::new(3, 2)..DisplayPoint::new(4, 2),
+ DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
+ ]
+ );
+
+ view.update(app, |view, ctx| view.move_line_down(&(), ctx));
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+ DisplayPoint::new(3, 1)..DisplayPoint::new(3, 1),
+ DisplayPoint::new(3, 2)..DisplayPoint::new(4, 2),
+ DisplayPoint::new(5, 0)..DisplayPoint::new(5, 2)
+ ]
+ );
+
+ view.update(app, |view, ctx| view.move_line_up(&(), ctx));
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff"
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+ DisplayPoint::new(2, 2)..DisplayPoint::new(3, 2),
+ DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
+ ]
+ );
}
- #[test]
- fn test_clipboard() {
- App::test((), |app| {
- 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(), settings, ctx))
- .1;
+ #[gpui::test]
+ fn test_clipboard(app: &mut gpui::MutableAppContext) {
+ 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(), settings, ctx))
+ .1;
- // Cut with three selections. Clipboard text is divided into three slices.
- view.update(app, |view, ctx| {
- view.select_ranges(vec![0..4, 8..14, 19..24], false, ctx);
- view.cut(&(), ctx);
- });
- assert_eq!(view.read(app).text(app.as_ref()), "two four six ");
+ // Cut with three selections. Clipboard text is divided into three slices.
+ view.update(app, |view, ctx| {
+ view.select_ranges(vec![0..4, 8..14, 19..24], false, ctx);
+ view.cut(&(), ctx);
+ });
+ assert_eq!(view.read(app).text(app.as_ref()), "two four six ");
- // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
- view.update(app, |view, ctx| {
- view.select_ranges(vec![4..4, 9..9, 13..13], false, ctx);
- view.paste(&(), ctx);
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "two one four three six five "
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(0, 8)..DisplayPoint::new(0, 8),
- DisplayPoint::new(0, 19)..DisplayPoint::new(0, 19),
- DisplayPoint::new(0, 28)..DisplayPoint::new(0, 28)
- ]
- );
+ // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
+ view.update(app, |view, ctx| {
+ view.select_ranges(vec![4..4, 9..9, 13..13], false, ctx);
+ view.paste(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "two one four three six five "
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 8)..DisplayPoint::new(0, 8),
+ DisplayPoint::new(0, 19)..DisplayPoint::new(0, 19),
+ DisplayPoint::new(0, 28)..DisplayPoint::new(0, 28)
+ ]
+ );
- // Paste again but with only two cursors. Since the number of cursors doesn't
- // match the number of slices in the clipboard, the entire clipboard text
- // is pasted at each cursor.
- view.update(app, |view, ctx| {
- view.select_ranges(vec![0..0, 28..28], false, ctx);
- view.insert(&"( ".to_string(), ctx);
- view.paste(&(), ctx);
- view.insert(&") ".to_string(), ctx);
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "( one three five ) two one four three six five ( one three five ) "
- );
+ // Paste again but with only two cursors. Since the number of cursors doesn't
+ // match the number of slices in the clipboard, the entire clipboard text
+ // is pasted at each cursor.
+ view.update(app, |view, ctx| {
+ view.select_ranges(vec![0..0, 28..28], false, ctx);
+ view.insert(&"( ".to_string(), ctx);
+ view.paste(&(), ctx);
+ view.insert(&") ".to_string(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "( one three five ) two one four three six five ( one three five ) "
+ );
- view.update(app, |view, ctx| {
- view.select_ranges(vec![0..0], false, ctx);
- view.insert(&"123\n4567\n89\n".to_string(), ctx);
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "123\n4567\n89\n( one three five ) two one four three six five ( one three five ) "
- );
+ view.update(app, |view, ctx| {
+ view.select_ranges(vec![0..0], false, ctx);
+ view.insert(&"123\n4567\n89\n".to_string(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "123\n4567\n89\n( one three five ) two one four three six five ( one three five ) "
+ );
- // Cut with three selections, one of which is full-line.
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2),
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
- DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
- ],
- ctx,
- )
- .unwrap();
- view.cut(&(), ctx);
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "13\n9\n( one three five ) two one four three six five ( one three five ) "
- );
+ // Cut with three selections, one of which is full-line.
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
+ &[
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(2, 0)..DisplayPoint::new(2, 1),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.cut(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "13\n9\n( one three five ) two one four three six five ( one three five ) "
+ );
- // Paste with three selections, noticing how the copied selection that was full-line
- // gets inserted before the second cursor.
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
- DisplayPoint::new(2, 2)..DisplayPoint::new(2, 3),
- ],
- ctx,
- )
- .unwrap();
- view.paste(&(), ctx);
- });
- assert_eq!(
- view.read(app).text(app.as_ref()),
- "123\n4567\n9\n( 8ne three five ) two one four three six five ( one three five ) "
- );
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
+ // Paste with three selections, noticing how the copied selection that was full-line
+ // gets inserted before the second cursor.
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
&[
- DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
- DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
- DisplayPoint::new(3, 3)..DisplayPoint::new(3, 3),
- ]
- );
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(2, 2)..DisplayPoint::new(2, 3),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.paste(&(), ctx);
+ });
+ assert_eq!(
+ view.read(app).text(app.as_ref()),
+ "123\n4567\n9\n( 8ne three five ) two one four three six five ( one three five ) "
+ );
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(0, 2)..DisplayPoint::new(0, 2),
+ DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+ DisplayPoint::new(3, 3)..DisplayPoint::new(3, 3),
+ ]
+ );
- // Copy with a single cursor only, which writes the whole line into the clipboard.
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)],
- ctx,
- )
+ // Copy with a single cursor only, which writes the whole line into the clipboard.
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)], ctx)
.unwrap();
- view.copy(&(), ctx);
- });
+ view.copy(&(), ctx);
+ });
- // Paste with three selections, noticing how the copied full-line selection is inserted
- // before the empty selections but replaces the selection that is non-empty.
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
- DisplayPoint::new(1, 0)..DisplayPoint::new(1, 2),
- DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
- ],
- ctx,
- )
- .unwrap();
- view.paste(&(), ctx);
- });
- assert_eq!(
+ // Paste with three selections, noticing how the copied full-line selection is inserted
+ // before the empty selections but replaces the selection that is non-empty.
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(
+ &[
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(1, 0)..DisplayPoint::new(1, 2),
+ DisplayPoint::new(2, 1)..DisplayPoint::new(2, 1),
+ ],
+ ctx,
+ )
+ .unwrap();
+ view.paste(&(), ctx);
+ });
+ assert_eq!(
view.read(app).text(app.as_ref()),
"123\n123\n123\n67\n123\n9\n( 8ne three five ) two one four three six five ( one three five ) "
);
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
- DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
- DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1),
- ]
- );
- });
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
+ DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1),
+ ]
+ );
}
- #[test]
- fn test_select_all() {
- App::test((), |app| {
- 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, settings, ctx));
- view.update(app, |b, ctx| b.select_all(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
- );
- });
+ #[gpui::test]
+ fn test_select_all(app: &mut gpui::MutableAppContext) {
+ 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, settings, ctx));
+ view.update(app, |b, ctx| b.select_all(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
+ );
}
- #[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),
- ]
- );
+ #[gpui::test]
+ fn test_select_line(app: &mut gpui::MutableAppContext) {
+ 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(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)]
- );
- });
+ 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"
+ #[gpui::test]
+ fn test_split_selection_into_lines(app: &mut gpui::MutableAppContext) {
+ 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,
);
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- [
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1),
+ 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)
- ]
- );
+ 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.select_display_ranges(
- &[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)],
- ctx,
- )
+ 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)
- ]
- );
+ 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)
+ ]
+ );
}
- #[test]
- fn test_add_selection_above_below() {
- App::test((), |app| {
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\n", ctx));
- let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
+ #[gpui::test]
+ fn test_add_selection_above_below(app: &mut gpui::MutableAppContext) {
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndefghi\n\njk\nlmno\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(1, 3)..DisplayPoint::new(1, 3)],
- ctx,
- )
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(&[DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)], ctx)
.unwrap();
- });
- view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
- ]
- );
+ });
+ view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
+ ]
+ );
- view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
- ]
- );
+ view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)
+ ]
+ );
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
- );
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)]
+ );
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
- DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
- ]
- );
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
+ DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
+ ]
+ );
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
- DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
- ]
- );
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3),
+ DisplayPoint::new(4, 3)..DisplayPoint::new(4, 3)
+ ]
+ );
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)],
- ctx,
- )
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(&[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)], ctx)
.unwrap();
- });
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
- DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
- ]
- );
+ });
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
+ DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
+ ]
+ );
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
- DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
- ]
- );
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3),
+ DisplayPoint::new(4, 4)..DisplayPoint::new(4, 3)
+ ]
+ );
- view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
- );
+ view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
+ );
- view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
- );
+ view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)]
+ );
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)],
- ctx,
- )
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)], ctx)
.unwrap();
- });
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
- DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
- ]
- );
+ });
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+ DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+ ]
+ );
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
- DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
- DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
- ]
- );
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+ DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+ DisplayPoint::new(4, 1)..DisplayPoint::new(4, 4),
+ ]
+ );
- view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
- DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
- DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
- ]
- );
+ view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 1)..DisplayPoint::new(0, 3),
+ DisplayPoint::new(1, 1)..DisplayPoint::new(1, 4),
+ DisplayPoint::new(3, 1)..DisplayPoint::new(3, 2),
+ ]
+ );
- view.update(app, |view, ctx| {
- view.select_display_ranges(
- &[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)],
- ctx,
- )
+ view.update(app, |view, ctx| {
+ view.select_display_ranges(&[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)], ctx)
.unwrap();
- });
- view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
- DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
- DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
- DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
- ]
- );
-
- view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
- assert_eq!(
- view.read(app).selection_ranges(app.as_ref()),
- vec![
- DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
- DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
- DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
- ]
- );
});
+ view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(0, 3)..DisplayPoint::new(0, 1),
+ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
+ DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
+ ]
+ );
+
+ view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
+ assert_eq!(
+ view.read(app).selection_ranges(app.as_ref()),
+ vec![
+ DisplayPoint::new(1, 3)..DisplayPoint::new(1, 1),
+ DisplayPoint::new(3, 2)..DisplayPoint::new(3, 1),
+ DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
+ ]
+ );
}
impl BufferView {
@@ -671,176 +671,163 @@ mod tests {
use super::*;
use crate::test::sample_text;
use buffer::ToPoint;
- use gpui::App;
- #[test]
- fn test_basic_folds() {
- App::test((), |app| {
- 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![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(2, 4)..Point::new(4, 1),
- ],
- app.as_ref(),
- )
- .unwrap();
- assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
-
- buffer.update(app, |buffer, ctx| {
- buffer
- .edit(
- vec![
- Point::new(0, 0)..Point::new(0, 1),
- Point::new(2, 3)..Point::new(2, 3),
- ],
- "123",
- Some(ctx),
- )
- .unwrap();
- });
- assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
-
- buffer.update(app, |buffer, ctx| {
- let start_version = buffer.version.clone();
- buffer
- .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
- .unwrap();
- buffer.edits_since(start_version).collect::<Vec<_>>()
- });
- assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
+ #[gpui::test]
+ fn test_basic_folds(app: &mut gpui::MutableAppContext) {
+ 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![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(2, 4)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+ assert_eq!(map.text(app.as_ref()), "aa…cc…eeeee");
+
+ buffer.update(app, |buffer, ctx| {
+ buffer
+ .edit(
+ vec![
+ Point::new(0, 0)..Point::new(0, 1),
+ Point::new(2, 3)..Point::new(2, 3),
+ ],
+ "123",
+ Some(ctx),
+ )
+ .unwrap();
+ });
+ assert_eq!(map.text(app.as_ref()), "123a…c123c…eeeee");
- map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), app.as_ref())
+ buffer.update(app, |buffer, ctx| {
+ let start_version = buffer.version.clone();
+ buffer
+ .edit(Some(Point::new(2, 6)..Point::new(4, 3)), "456", Some(ctx))
.unwrap();
- assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
+ buffer.edits_since(start_version).collect::<Vec<_>>()
});
+ assert_eq!(map.text(app.as_ref()), "123a…c123456eee");
+
+ 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");
}
- #[test]
- fn test_adjacent_folds() {
- App::test((), |app| {
- let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
+ #[gpui::test]
+ fn test_adjacent_folds(app: &mut gpui::MutableAppContext) {
+ let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
- {
- let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+ {
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
- map.fold(vec![5..8], app.as_ref()).unwrap();
- map.check_invariants(app.as_ref());
- assert_eq!(map.text(app.as_ref()), "abcde…ijkl");
+ map.fold(vec![5..8], app.as_ref()).unwrap();
+ map.check_invariants(app.as_ref());
+ assert_eq!(map.text(app.as_ref()), "abcde…ijkl");
- // Create an fold adjacent to the start of the first fold.
- map.fold(vec![0..1, 2..5], app.as_ref()).unwrap();
- map.check_invariants(app.as_ref());
- assert_eq!(map.text(app.as_ref()), "…b…ijkl");
+ // Create an fold adjacent to the start of the first fold.
+ map.fold(vec![0..1, 2..5], app.as_ref()).unwrap();
+ map.check_invariants(app.as_ref());
+ assert_eq!(map.text(app.as_ref()), "…b…ijkl");
- // Create an fold adjacent to the end of the first fold.
- map.fold(vec![11..11, 8..10], app.as_ref()).unwrap();
- map.check_invariants(app.as_ref());
- assert_eq!(map.text(app.as_ref()), "…b…kl");
- }
+ // Create an fold adjacent to the end of the first fold.
+ map.fold(vec![11..11, 8..10], app.as_ref()).unwrap();
+ map.check_invariants(app.as_ref());
+ assert_eq!(map.text(app.as_ref()), "…b…kl");
+ }
- {
- let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+ {
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
- // Create two adjacent folds.
- map.fold(vec![0..2, 2..5], app.as_ref()).unwrap();
- map.check_invariants(app.as_ref());
- assert_eq!(map.text(app.as_ref()), "…fghijkl");
+ // Create two adjacent folds.
+ map.fold(vec![0..2, 2..5], app.as_ref()).unwrap();
+ map.check_invariants(app.as_ref());
+ assert_eq!(map.text(app.as_ref()), "…fghijkl");
- // Edit within one of the folds.
- buffer.update(app, |buffer, ctx| {
- let version = buffer.version();
- buffer.edit(vec![0..1], "12345", Some(ctx)).unwrap();
- buffer.edits_since(version).collect::<Vec<_>>()
- });
- map.check_invariants(app.as_ref());
- assert_eq!(map.text(app.as_ref()), "12345…fghijkl");
- }
- });
+ // Edit within one of the folds.
+ buffer.update(app, |buffer, ctx| {
+ let version = buffer.version();
+ buffer.edit(vec![0..1], "12345", Some(ctx)).unwrap();
+ buffer.edits_since(version).collect::<Vec<_>>()
+ });
+ map.check_invariants(app.as_ref());
+ assert_eq!(map.text(app.as_ref()), "12345…fghijkl");
+ }
}
- #[test]
- fn test_overlapping_folds() {
- App::test((), |app| {
- 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![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(0, 4)..Point::new(1, 0),
- Point::new(1, 2)..Point::new(3, 2),
- Point::new(3, 1)..Point::new(4, 1),
- ],
- app.as_ref(),
- )
- .unwrap();
- assert_eq!(map.text(app.as_ref()), "aa…eeeee");
- })
+ #[gpui::test]
+ fn test_overlapping_folds(app: &mut gpui::MutableAppContext) {
+ 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![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(0, 4)..Point::new(1, 0),
+ Point::new(1, 2)..Point::new(3, 2),
+ Point::new(3, 1)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+ assert_eq!(map.text(app.as_ref()), "aa…eeeee");
}
- #[test]
- fn test_merging_folds_via_edit() {
- App::test((), |app| {
- 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![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(3, 1)..Point::new(4, 1),
- ],
- app.as_ref(),
- )
- .unwrap();
- assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
-
- buffer.update(app, |buffer, ctx| {
- buffer
- .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
- .unwrap();
- });
- assert_eq!(map.text(app.as_ref()), "aa…eeeee");
+ #[gpui::test]
+ fn test_merging_folds_via_edit(app: &mut gpui::MutableAppContext) {
+ 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![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(3, 1)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+ assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee");
+
+ buffer.update(app, |buffer, ctx| {
+ buffer
+ .edit(Some(Point::new(2, 2)..Point::new(3, 1)), "", Some(ctx))
+ .unwrap();
});
+ assert_eq!(map.text(app.as_ref()), "aa…eeeee");
}
- #[test]
- fn test_folds_in_range() {
- App::test((), |app| {
- 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);
-
- map.fold(
- vec![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(0, 4)..Point::new(1, 0),
- Point::new(1, 2)..Point::new(3, 2),
- Point::new(3, 1)..Point::new(4, 1),
- ],
- app.as_ref(),
- )
- .unwrap();
- let fold_ranges = map
- .folds_in_range(Point::new(1, 0)..Point::new(1, 3), app.as_ref())
- .unwrap()
- .map(|fold| {
- fold.start.to_point(buffer).unwrap()..fold.end.to_point(buffer).unwrap()
- })
- .collect::<Vec<_>>();
- assert_eq!(
- fold_ranges,
- vec![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(1, 2)..Point::new(3, 2)
- ]
- );
- });
+ #[gpui::test]
+ fn test_folds_in_range(app: &mut gpui::MutableAppContext) {
+ 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);
+
+ map.fold(
+ vec![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(0, 4)..Point::new(1, 0),
+ Point::new(1, 2)..Point::new(3, 2),
+ Point::new(3, 1)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+ let fold_ranges = map
+ .folds_in_range(Point::new(1, 0)..Point::new(1, 3), app.as_ref())
+ .unwrap()
+ .map(|fold| fold.start.to_point(buffer).unwrap()..fold.end.to_point(buffer).unwrap())
+ .collect::<Vec<_>>();
+ assert_eq!(
+ fold_ranges,
+ vec![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(1, 2)..Point::new(3, 2)
+ ]
+ );
}
- #[test]
- fn test_random_folds() {
+ #[gpui::test]
+ fn test_random_folds(app: &mut gpui::MutableAppContext) {
use crate::editor::ToPoint;
use crate::util::RandomCharIter;
use rand::prelude::*;
@@ -863,203 +850,197 @@ mod tests {
dbg!(seed);
let mut rng = StdRng::seed_from_u64(seed);
- App::test((), |app| {
- 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, ctx)
- });
- let mut map = FoldMap::new(buffer.clone(), app.as_ref());
-
- for _ in 0..operations {
- log::info!("text: {:?}", buffer.read(app).text());
- 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 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, ctx)
+ });
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+ for _ in 0..operations {
+ log::info!("text: {:?}", buffer.read(app).text());
+ 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);
}
- _ => {
- 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 {:?}", 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();
}
- map.check_invariants(app.as_ref());
-
- let buffer = map.buffer.read(app);
- let mut expected_text = buffer.text();
- let mut expected_buffer_rows = Vec::new();
- let mut next_row = buffer.max_point().row;
- for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
- let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
- let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
- expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
- next_row = fold_start.row;
-
- expected_text.replace_range(fold_range.start..fold_range.end, "…");
+ _ => {
+ 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);
}
- expected_buffer_rows.extend((0..=next_row).rev());
- expected_buffer_rows.reverse();
-
- assert_eq!(map.text(app.as_ref()), expected_text);
+ }
+ map.check_invariants(app.as_ref());
- for (display_row, line) in expected_text.lines().enumerate() {
- let line_len = map.line_len(display_row as u32, app.as_ref()).unwrap();
- assert_eq!(line_len, line.chars().count() as u32);
- }
+ let buffer = map.buffer.read(app);
+ let mut expected_text = buffer.text();
+ let mut expected_buffer_rows = Vec::new();
+ let mut next_row = buffer.max_point().row;
+ for fold_range in map.merged_fold_ranges(app.as_ref()).into_iter().rev() {
+ let fold_start = buffer.point_for_offset(fold_range.start).unwrap();
+ let fold_end = buffer.point_for_offset(fold_range.end).unwrap();
+ expected_buffer_rows.extend((fold_end.row + 1..=next_row).rev());
+ next_row = fold_start.row;
+
+ expected_text.replace_range(fold_range.start..fold_range.end, "…");
+ }
+ expected_buffer_rows.extend((0..=next_row).rev());
+ expected_buffer_rows.reverse();
- let mut display_point = DisplayPoint::new(0, 0);
- let mut display_offset = DisplayOffset(0);
- for c in expected_text.chars() {
- let buffer_point = map.to_buffer_point(display_point, app.as_ref());
- let buffer_offset = buffer_point.to_offset(buffer).unwrap();
- assert_eq!(
- map.to_display_point(buffer_point, app.as_ref()),
- display_point
- );
- assert_eq!(
- map.to_buffer_offset(display_point, app.as_ref()).unwrap(),
- buffer_offset
- );
- assert_eq!(
- map.to_display_offset(display_point, app.as_ref()).unwrap(),
- display_offset
- );
-
- if c == '\n' {
- *display_point.row_mut() += 1;
- *display_point.column_mut() = 0;
- } else {
- *display_point.column_mut() += 1;
- }
- display_offset.0 += 1;
- }
+ assert_eq!(map.text(app.as_ref()), expected_text);
- for _ in 0..5 {
- let row = rng.gen_range(0..=map.max_point(app.as_ref()).row());
- let column = rng.gen_range(0..=map.line_len(row, app.as_ref()).unwrap());
- let point = DisplayPoint::new(row, column);
- let offset = map.to_display_offset(point, app.as_ref()).unwrap().0;
- let len = rng.gen_range(0..=map.len(app.as_ref()) - offset);
- assert_eq!(
- map.snapshot(app.as_ref())
- .chars_at(point, app.as_ref())
- .unwrap()
- .take(len)
- .collect::<String>(),
- expected_text
- .chars()
- .skip(offset)
- .take(len)
- .collect::<String>()
- );
- }
+ for (display_row, line) in expected_text.lines().enumerate() {
+ let line_len = map.line_len(display_row as u32, app.as_ref()).unwrap();
+ assert_eq!(line_len, line.chars().count() as u32);
+ }
- for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
- let display_row = map
- .to_display_point(Point::new(*buffer_row, 0), app.as_ref())
- .row();
- assert_eq!(
- map.snapshot(app.as_ref())
- .buffer_rows(display_row)
- .unwrap()
- .collect::<Vec<_>>(),
- expected_buffer_rows[idx..],
- );
- }
+ let mut display_point = DisplayPoint::new(0, 0);
+ let mut display_offset = DisplayOffset(0);
+ for c in expected_text.chars() {
+ let buffer_point = map.to_buffer_point(display_point, app.as_ref());
+ let buffer_offset = buffer_point.to_offset(buffer).unwrap();
+ assert_eq!(
+ map.to_display_point(buffer_point, app.as_ref()),
+ display_point
+ );
+ assert_eq!(
+ map.to_buffer_offset(display_point, app.as_ref()).unwrap(),
+ buffer_offset
+ );
+ assert_eq!(
+ map.to_display_offset(display_point, app.as_ref()).unwrap(),
+ display_offset
+ );
- for fold_range in map.merged_fold_ranges(app.as_ref()) {
- let display_point = map.to_display_point(
- fold_range.start.to_point(buffer).unwrap(),
- app.as_ref(),
- );
- assert!(map.is_line_folded(display_point.row(), app.as_ref()));
+ if c == '\n' {
+ *display_point.row_mut() += 1;
+ *display_point.column_mut() = 0;
+ } else {
+ *display_point.column_mut() += 1;
}
+ display_offset.0 += 1;
+ }
- 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
- );
- }
+ for _ in 0..5 {
+ let row = rng.gen_range(0..=map.max_point(app.as_ref()).row());
+ let column = rng.gen_range(0..=map.line_len(row, app.as_ref()).unwrap());
+ let point = DisplayPoint::new(row, column);
+ let offset = map.to_display_offset(point, app.as_ref()).unwrap().0;
+ let len = rng.gen_range(0..=map.len(app.as_ref()) - offset);
+ assert_eq!(
+ map.snapshot(app.as_ref())
+ .chars_at(point, app.as_ref())
+ .unwrap()
+ .take(len)
+ .collect::<String>(),
+ expected_text
+ .chars()
+ .skip(offset)
+ .take(len)
+ .collect::<String>()
+ );
}
- });
- }
- }
- #[test]
- fn test_buffer_rows() {
- App::test((), |app| {
- let text = sample_text(6, 6) + "\n";
- let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
+ for (idx, buffer_row) in expected_buffer_rows.iter().enumerate() {
+ let display_row = map
+ .to_display_point(Point::new(*buffer_row, 0), app.as_ref())
+ .row();
+ assert_eq!(
+ map.snapshot(app.as_ref())
+ .buffer_rows(display_row)
+ .unwrap()
+ .collect::<Vec<_>>(),
+ expected_buffer_rows[idx..],
+ );
+ }
- let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+ for fold_range in map.merged_fold_ranges(app.as_ref()) {
+ let display_point = map
+ .to_display_point(fold_range.start.to_point(buffer).unwrap(), app.as_ref());
+ assert!(map.is_line_folded(display_point.row(), app.as_ref()));
+ }
- map.fold(
- vec![
- Point::new(0, 2)..Point::new(2, 2),
- Point::new(3, 1)..Point::new(4, 1),
- ],
- app.as_ref(),
- )
- .unwrap();
+ 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
+ );
+ }
+ }
+ }
+ }
- assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
- assert_eq!(
- map.snapshot(app.as_ref())
- .buffer_rows(0)
- .unwrap()
- .collect::<Vec<_>>(),
- vec![0, 3, 5, 6]
- );
- assert_eq!(
- map.snapshot(app.as_ref())
- .buffer_rows(3)
- .unwrap()
- .collect::<Vec<_>>(),
- vec![6]
- );
- });
+ #[gpui::test]
+ fn test_buffer_rows(app: &mut gpui::MutableAppContext) {
+ let text = sample_text(6, 6) + "\n";
+ let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
+
+ let mut map = FoldMap::new(buffer.clone(), app.as_ref());
+
+ map.fold(
+ vec![
+ Point::new(0, 2)..Point::new(2, 2),
+ Point::new(3, 1)..Point::new(4, 1),
+ ],
+ app.as_ref(),
+ )
+ .unwrap();
+
+ assert_eq!(map.text(app.as_ref()), "aa…cccc\nd…eeeee\nffffff\n");
+ assert_eq!(
+ map.snapshot(app.as_ref())
+ .buffer_rows(0)
+ .unwrap()
+ .collect::<Vec<_>>(),
+ vec![0, 3, 5, 6]
+ );
+ assert_eq!(
+ map.snapshot(app.as_ref())
+ .buffer_rows(3)
+ .unwrap()
+ .collect::<Vec<_>>(),
+ vec![6]
+ );
}
impl FoldMap {
@@ -339,53 +339,50 @@ pub fn collapse_tabs(
mod tests {
use super::*;
use crate::test::*;
- use gpui::App;
- #[test]
- fn test_chars_at() {
- App::test((), |app| {
- let text = sample_text(6, 6);
- 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| {
- buffer.edit(
- vec![
- Point::new(1, 0)..Point::new(1, 0),
- Point::new(1, 1)..Point::new(1, 1),
- Point::new(2, 1)..Point::new(2, 1),
- ],
- "\t",
- Some(ctx),
- )
- })
- .unwrap();
-
- assert_eq!(
- map.snapshot(app.as_ref())
- .chars_at(DisplayPoint::new(1, 0), app.as_ref())
- .unwrap()
- .take(10)
- .collect::<String>(),
- " b bb"
- );
- assert_eq!(
- map.snapshot(app.as_ref())
- .chars_at(DisplayPoint::new(1, 2), app.as_ref())
- .unwrap()
- .take(10)
- .collect::<String>(),
- " b bbbb"
- );
- assert_eq!(
- map.snapshot(app.as_ref())
- .chars_at(DisplayPoint::new(1, 6), app.as_ref())
- .unwrap()
- .take(13)
- .collect::<String>(),
- " bbbbb\nc c"
- );
- });
+ #[gpui::test]
+ fn test_chars_at(app: &mut gpui::MutableAppContext) {
+ let text = sample_text(6, 6);
+ 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| {
+ buffer.edit(
+ vec![
+ Point::new(1, 0)..Point::new(1, 0),
+ Point::new(1, 1)..Point::new(1, 1),
+ Point::new(2, 1)..Point::new(2, 1),
+ ],
+ "\t",
+ Some(ctx),
+ )
+ })
+ .unwrap();
+
+ assert_eq!(
+ map.snapshot(app.as_ref())
+ .chars_at(DisplayPoint::new(1, 0), app.as_ref())
+ .unwrap()
+ .take(10)
+ .collect::<String>(),
+ " b bb"
+ );
+ assert_eq!(
+ map.snapshot(app.as_ref())
+ .chars_at(DisplayPoint::new(1, 2), app.as_ref())
+ .unwrap()
+ .take(10)
+ .collect::<String>(),
+ " b bbbb"
+ );
+ assert_eq!(
+ map.snapshot(app.as_ref())
+ .chars_at(DisplayPoint::new(1, 6), app.as_ref())
+ .unwrap()
+ .take(13)
+ .collect::<String>(),
+ " bbbbb\nc c"
+ );
}
#[test]
@@ -411,12 +408,10 @@ mod tests {
assert_eq!(collapse_tabs("\ta".chars(), 5, Bias::Right, 4), (2, 0));
}
- #[test]
- fn test_max_point() {
- App::test((), |app| {
- 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))
- });
+ #[gpui::test]
+ fn test_max_point(app: &mut gpui::MutableAppContext) {
+ 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))
}
}
@@ -399,7 +399,7 @@ impl FileFinder {
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone();
- let task = ctx.background_executor().spawn(async move {
+ let background_task = ctx.background_executor().spawn(async move {
let include_root_name = snapshots.len() > 1;
let matches = match_paths(
snapshots.iter(),
@@ -415,7 +415,11 @@ impl FileFinder {
(search_id, did_cancel, query, matches)
});
- ctx.spawn(task, Self::update_matches).detach();
+ ctx.spawn(|this, mut ctx| async move {
+ let matches = background_task.await;
+ this.update(&mut ctx, |this, ctx| this.update_matches(matches, ctx));
+ })
+ .detach();
Some(())
}
@@ -453,220 +457,208 @@ impl FileFinder {
mod tests {
use super::*;
use crate::{editor, settings, test::temp_tree, workspace::Workspace};
- use gpui::App;
use serde_json::json;
use std::fs;
use tempdir::TempDir;
- #[test]
- fn test_matching_paths() {
- App::test_async((), |mut app| async move {
- let tmp_dir = TempDir::new("example").unwrap();
- fs::create_dir(tmp_dir.path().join("a")).unwrap();
- fs::write(tmp_dir.path().join("a/banana"), "banana").unwrap();
- fs::write(tmp_dir.path().join("a/bandana"), "bandana").unwrap();
- app.update(|ctx| {
- super::init(ctx);
- editor::init(ctx);
- });
-
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (window_id, workspace) = app.add_window(|ctx| {
- let mut workspace = Workspace::new(0, settings, ctx);
- workspace.add_worktree(tmp_dir.path(), ctx);
- workspace
- });
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
- app.dispatch_action(
- window_id,
- vec![workspace.id()],
- "file_finder:toggle".into(),
- (),
- );
+ #[gpui::test]
+ async fn test_matching_paths(mut app: gpui::TestAppContext) {
+ let tmp_dir = TempDir::new("example").unwrap();
+ fs::create_dir(tmp_dir.path().join("a")).unwrap();
+ fs::write(tmp_dir.path().join("a/banana"), "banana").unwrap();
+ fs::write(tmp_dir.path().join("a/bandana"), "bandana").unwrap();
+ app.update(|ctx| {
+ super::init(ctx);
+ editor::init(ctx);
+ });
- let finder = app.read(|ctx| {
- workspace
- .read(ctx)
- .modal()
- .cloned()
- .unwrap()
- .downcast::<FileFinder>()
- .unwrap()
- });
- let query_buffer = app.read(|ctx| finder.read(ctx).query_buffer.clone());
-
- let chain = vec![finder.id(), query_buffer.id()];
- app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
- app.dispatch_action(window_id, chain.clone(), "buffer:insert", "n".to_string());
- app.dispatch_action(window_id, chain.clone(), "buffer:insert", "a".to_string());
- finder
- .condition(&app, |finder, _| finder.matches.len() == 2)
- .await;
-
- let active_pane = app.read(|ctx| workspace.read(ctx).active_pane().clone());
- app.dispatch_action(
- window_id,
- vec![workspace.id(), finder.id()],
- "menu:select_next",
- (),
- );
- app.dispatch_action(
- window_id,
- vec![workspace.id(), finder.id()],
- "file_finder:confirm",
- (),
- );
- active_pane
- .condition(&app, |pane, _| pane.active_item().is_some())
- .await;
- app.read(|ctx| {
- let active_item = active_pane.read(ctx).active_item().unwrap();
- assert_eq!(active_item.title(ctx), "bandana");
- });
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (window_id, workspace) = app.add_window(|ctx| {
+ let mut workspace = Workspace::new(0, settings, ctx);
+ workspace.add_worktree(tmp_dir.path(), ctx);
+ workspace
+ });
+ app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
+ .await;
+ app.dispatch_action(
+ window_id,
+ vec![workspace.id()],
+ "file_finder:toggle".into(),
+ (),
+ );
+
+ let finder = app.read(|ctx| {
+ workspace
+ .read(ctx)
+ .modal()
+ .cloned()
+ .unwrap()
+ .downcast::<FileFinder>()
+ .unwrap()
+ });
+ let query_buffer = app.read(|ctx| finder.read(ctx).query_buffer.clone());
+
+ let chain = vec![finder.id(), query_buffer.id()];
+ app.dispatch_action(window_id, chain.clone(), "buffer:insert", "b".to_string());
+ app.dispatch_action(window_id, chain.clone(), "buffer:insert", "n".to_string());
+ app.dispatch_action(window_id, chain.clone(), "buffer:insert", "a".to_string());
+ finder
+ .condition(&app, |finder, _| finder.matches.len() == 2)
+ .await;
+
+ let active_pane = app.read(|ctx| workspace.read(ctx).active_pane().clone());
+ app.dispatch_action(
+ window_id,
+ vec![workspace.id(), finder.id()],
+ "menu:select_next",
+ (),
+ );
+ app.dispatch_action(
+ window_id,
+ vec![workspace.id(), finder.id()],
+ "file_finder:confirm",
+ (),
+ );
+ active_pane
+ .condition(&app, |pane, _| pane.active_item().is_some())
+ .await;
+ app.read(|ctx| {
+ let active_item = active_pane.read(ctx).active_item().unwrap();
+ assert_eq!(active_item.title(ctx), "bandana");
});
}
- #[test]
- fn test_matching_cancellation() {
- App::test_async((), |mut app| async move {
- let tmp_dir = temp_tree(json!({
- "hello": "",
- "goodbye": "",
- "halogen-light": "",
- "happiness": "",
- "height": "",
- "hi": "",
- "hiccup": "",
- }));
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (_, workspace) = app.add_window(|ctx| {
- let mut workspace = Workspace::new(0, settings.clone(), ctx);
- workspace.add_worktree(tmp_dir.path(), ctx);
- workspace
- });
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
- let (_, finder) =
- app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
-
- let query = "hi".to_string();
- finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx));
- finder.condition(&app, |f, _| f.matches.len() == 5).await;
-
- finder.update(&mut app, |finder, ctx| {
- let matches = finder.matches.clone();
-
- // Simulate a search being cancelled after the time limit,
- // returning only a subset of the matches that would have been found.
- finder.spawn_search(query.clone(), ctx);
- finder.update_matches(
- (
- finder.latest_search_id,
- true, // did-cancel
- query.clone(),
- vec![matches[1].clone(), matches[3].clone()],
- ),
- ctx,
- );
-
- // Simulate another cancellation.
- finder.spawn_search(query.clone(), ctx);
- finder.update_matches(
- (
- finder.latest_search_id,
- true, // did-cancel
- query.clone(),
- vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
- ),
- ctx,
- );
+ #[gpui::test]
+ async fn test_matching_cancellation(mut app: gpui::TestAppContext) {
+ let tmp_dir = temp_tree(json!({
+ "hello": "",
+ "goodbye": "",
+ "halogen-light": "",
+ "happiness": "",
+ "height": "",
+ "hi": "",
+ "hiccup": "",
+ }));
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (_, workspace) = app.add_window(|ctx| {
+ let mut workspace = Workspace::new(0, settings.clone(), ctx);
+ workspace.add_worktree(tmp_dir.path(), ctx);
+ workspace
+ });
+ app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
+ .await;
+ let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
+
+ let query = "hi".to_string();
+ finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx));
+ finder.condition(&app, |f, _| f.matches.len() == 5).await;
+
+ finder.update(&mut app, |finder, ctx| {
+ let matches = finder.matches.clone();
+
+ // Simulate a search being cancelled after the time limit,
+ // returning only a subset of the matches that would have been found.
+ finder.spawn_search(query.clone(), ctx);
+ finder.update_matches(
+ (
+ finder.latest_search_id,
+ true, // did-cancel
+ query.clone(),
+ vec![matches[1].clone(), matches[3].clone()],
+ ),
+ ctx,
+ );
+
+ // Simulate another cancellation.
+ finder.spawn_search(query.clone(), ctx);
+ finder.update_matches(
+ (
+ finder.latest_search_id,
+ true, // did-cancel
+ query.clone(),
+ vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
+ ),
+ ctx,
+ );
- assert_eq!(finder.matches, matches[0..4])
- });
+ assert_eq!(finder.matches, matches[0..4])
});
}
- #[test]
- fn test_single_file_worktrees() {
- App::test_async((), |mut app| async move {
- let temp_dir = TempDir::new("test-single-file-worktrees").unwrap();
- let dir_path = temp_dir.path().join("the-parent-dir");
- let file_path = dir_path.join("the-file");
- fs::create_dir(&dir_path).unwrap();
- fs::write(&file_path, "").unwrap();
-
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (_, workspace) = app.add_window(|ctx| {
- let mut workspace = Workspace::new(0, settings.clone(), ctx);
- workspace.add_worktree(&file_path, ctx);
- workspace
- });
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
- let (_, finder) =
- app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
-
- // Even though there is only one worktree, that worktree's filename
- // is included in the matching, because the worktree is a single file.
- finder.update(&mut app, |f, ctx| f.spawn_search("thf".into(), ctx));
- finder.condition(&app, |f, _| f.matches.len() == 1).await;
-
- app.read(|ctx| {
- let finder = finder.read(ctx);
- let (file_name, file_name_positions, full_path, full_path_positions) =
- finder.labels_for_match(&finder.matches[0], ctx).unwrap();
-
- assert_eq!(file_name, "the-file");
- assert_eq!(file_name_positions, &[0, 1, 4]);
- assert_eq!(full_path, "the-file");
- assert_eq!(full_path_positions, &[0, 1, 4]);
- });
-
- // Since the worktree root is a file, searching for its name followed by a slash does
- // not match anything.
- finder.update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx));
- finder.condition(&app, |f, _| f.matches.len() == 0).await;
+ #[gpui::test]
+ async fn test_single_file_worktrees(mut app: gpui::TestAppContext) {
+ let temp_dir = TempDir::new("test-single-file-worktrees").unwrap();
+ let dir_path = temp_dir.path().join("the-parent-dir");
+ let file_path = dir_path.join("the-file");
+ fs::create_dir(&dir_path).unwrap();
+ fs::write(&file_path, "").unwrap();
+
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (_, workspace) = app.add_window(|ctx| {
+ let mut workspace = Workspace::new(0, settings.clone(), ctx);
+ workspace.add_worktree(&file_path, ctx);
+ workspace
+ });
+ app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
+ .await;
+ let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
+
+ // Even though there is only one worktree, that worktree's filename
+ // is included in the matching, because the worktree is a single file.
+ finder.update(&mut app, |f, ctx| f.spawn_search("thf".into(), ctx));
+ finder.condition(&app, |f, _| f.matches.len() == 1).await;
+
+ app.read(|ctx| {
+ let finder = finder.read(ctx);
+ let (file_name, file_name_positions, full_path, full_path_positions) =
+ finder.labels_for_match(&finder.matches[0], ctx).unwrap();
+
+ assert_eq!(file_name, "the-file");
+ assert_eq!(file_name_positions, &[0, 1, 4]);
+ assert_eq!(full_path, "the-file");
+ assert_eq!(full_path_positions, &[0, 1, 4]);
});
+
+ // Since the worktree root is a file, searching for its name followed by a slash does
+ // not match anything.
+ finder.update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx));
+ finder.condition(&app, |f, _| f.matches.len() == 0).await;
}
- #[test]
- fn test_multiple_matches_with_same_relative_path() {
- App::test_async((), |mut app| async move {
- let tmp_dir = temp_tree(json!({
- "dir1": { "a.txt": "" },
- "dir2": { "a.txt": "" }
- }));
- let settings = settings::channel(&app.font_cache()).unwrap().1;
+ #[gpui::test]
+ async fn test_multiple_matches_with_same_relative_path(mut app: gpui::TestAppContext) {
+ let tmp_dir = temp_tree(json!({
+ "dir1": { "a.txt": "" },
+ "dir2": { "a.txt": "" }
+ }));
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx));
+ let (_, workspace) = app.add_window(|ctx| Workspace::new(0, settings.clone(), ctx));
- workspace
- .update(&mut app, |workspace, ctx| {
- workspace.open_paths(
- &[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")],
- ctx,
- )
- })
- .await;
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
-
- let (_, finder) =
- app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
-
- // Run a search that matches two files with the same relative path.
- finder.update(&mut app, |f, ctx| f.spawn_search("a.t".into(), ctx));
- finder.condition(&app, |f, _| f.matches.len() == 2).await;
-
- // Can switch between different matches with the same relative path.
- finder.update(&mut app, |f, ctx| {
- assert_eq!(f.selected_index(), 0);
- f.select_next(&(), ctx);
- assert_eq!(f.selected_index(), 1);
- f.select_prev(&(), ctx);
- assert_eq!(f.selected_index(), 0);
- });
+ workspace
+ .update(&mut app, |workspace, ctx| {
+ workspace.open_paths(
+ &[tmp_dir.path().join("dir1"), tmp_dir.path().join("dir2")],
+ ctx,
+ )
+ })
+ .await;
+ app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
+ .await;
+
+ let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
+
+ // Run a search that matches two files with the same relative path.
+ finder.update(&mut app, |f, ctx| f.spawn_search("a.t".into(), ctx));
+ finder.condition(&app, |f, _| f.matches.len() == 2).await;
+
+ // Can switch between different matches with the same relative path.
+ finder.update(&mut app, |f, ctx| {
+ assert_eq!(f.selected_index(), 0);
+ f.select_next(&(), ctx);
+ assert_eq!(f.selected_index(), 1);
+ f.select_prev(&(), ctx);
+ assert_eq!(f.selected_index(), 0);
});
}
}
@@ -6,11 +6,11 @@ use crate::{
time::ReplicaId,
worktree::{FileHandle, Worktree, WorktreeHandle},
};
-use futures_core::{future::LocalBoxFuture, Future};
+use futures_core::Future;
use gpui::{
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
- ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, PathPromptOptions,
- PromptLevel, View, ViewContext, ViewHandle, WeakModelHandle,
+ ClipboardItem, Entity, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, Task,
+ View, ViewContext, ViewHandle, WeakModelHandle,
};
use log::error;
pub use pane::*;
@@ -126,7 +126,7 @@ pub trait ItemView: View {
&mut self,
_: Option<FileHandle>,
_: &mut ViewContext<Self>,
- ) -> LocalBoxFuture<'static, anyhow::Result<()>>;
+ ) -> Task<anyhow::Result<()>>;
fn should_activate_item_on_event(_: &Self::Event) -> bool {
false
}
@@ -165,7 +165,7 @@ pub trait ItemViewHandle: Send + Sync {
&self,
file: Option<FileHandle>,
ctx: &mut MutableAppContext,
- ) -> LocalBoxFuture<'static, anyhow::Result<()>>;
+ ) -> Task<anyhow::Result<()>>;
}
impl<T: Item> ItemHandle for ModelHandle<T> {
@@ -243,7 +243,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
&self,
file: Option<FileHandle>,
ctx: &mut MutableAppContext,
- ) -> LocalBoxFuture<'static, anyhow::Result<()>> {
+ ) -> Task<anyhow::Result<()>> {
self.update(ctx, |item, ctx| item.save(file, ctx))
}
@@ -367,16 +367,17 @@ impl Workspace {
.cloned()
.zip(entries.into_iter())
.map(|(abs_path, file)| {
- ctx.spawn(
- bg.spawn(async move { abs_path.is_file() }),
- move |me, is_file, ctx| {
+ let is_file = bg.spawn(async move { abs_path.is_file() });
+ ctx.spawn(|this, mut ctx| async move {
+ let is_file = is_file.await;
+ this.update(&mut ctx, |this, ctx| {
if is_file {
- me.open_entry(file.entry_id(), ctx)
+ this.open_entry(file.entry_id(), ctx)
} else {
None
}
- },
- )
+ })
+ })
})
.collect::<Vec<_>>();
async move {
@@ -450,7 +451,7 @@ impl Workspace {
&mut self,
entry: (usize, Arc<Path>),
ctx: &mut ViewContext<Self>,
- ) -> Option<EntityTask<()>> {
+ ) -> Option<Task<()>> {
// If the active pane contains a view for this file, then activate
// that item view.
if self
@@ -504,44 +505,46 @@ impl Workspace {
let history = ctx
.background_executor()
.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)),
+
+ ctx.as_mut()
+ .spawn(|mut ctx| async move {
+ *tx.borrow_mut() = Some(match history.await {
+ 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()
+ .detach();
}
let mut watch = self.loading_items.get(&entry).unwrap().clone();
- Some(ctx.spawn(
- async move {
- loop {
- if let Some(load_result) = watch.borrow().as_ref() {
- return load_result.clone();
- }
- watch.next().await;
+
+ Some(ctx.spawn(|this, mut ctx| async move {
+ let load_result = loop {
+ if let Some(load_result) = watch.borrow().as_ref() {
+ break load_result.clone();
}
- },
- move |me, load_result, ctx| {
- me.loading_items.remove(&entry);
+ watch.next().await;
+ };
+
+ this.update(&mut ctx, |this, ctx| {
+ this.loading_items.remove(&entry);
match load_result {
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);
+ this.items.push(weak_item);
+ this.add_item_view(view, ctx);
}
Err(error) => {
log::error!("error opening item: {}", error);
}
}
- },
- ))
+ })
+ }))
}
pub fn active_item(&self, ctx: &ViewContext<Self>) -> Option<Box<dyn ItemViewHandle>> {
@@ -550,8 +553,8 @@ impl Workspace {
pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
if let Some(item) = self.active_item(ctx) {
+ let handle = ctx.handle();
if item.entry_id(ctx.as_ref()).is_none() {
- let handle = ctx.handle();
let start_path = self
.worktrees
.iter()
@@ -560,46 +563,39 @@ impl Workspace {
.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()
+ ctx.spawn(|mut ctx| async move {
+ let file =
+ handle.update(&mut ctx, |me, ctx| me.file_for_path(&path, ctx));
+ if let Err(error) = ctx.update(|ctx| item.save(Some(file), ctx)).await {
+ error!("failed to save item: {:?}, ", error);
+ }
})
+ .detach()
}
});
return;
} else if item.has_conflict(ctx.as_ref()) {
const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
- let handle = ctx.handle();
ctx.prompt(
PromptLevel::Warning,
CONFLICT_MESSAGE,
&["Overwrite", "Cancel"],
move |answer, ctx| {
if answer == 0 {
- handle.update(ctx, move |_, ctx| {
- let task = item.save(None, ctx.as_mut());
- ctx.spawn(task, |_, result, _| {
- if let Err(e) = result {
- error!("failed to save item: {:?}, ", e);
- }
- })
- .detach();
- });
+ ctx.spawn(|mut ctx| async move {
+ if let Err(error) = ctx.update(|ctx| item.save(None, ctx)).await {
+ error!("failed to save item: {:?}, ", error);
+ }
+ })
+ .detach();
}
},
);
} else {
- let task = item.save(None, ctx.as_mut());
- ctx.spawn(task, |_, result, _| {
- if let Err(e) = result {
- error!("failed to save item: {:?}, ", e);
+ ctx.spawn(|_, mut ctx| async move {
+ if let Err(error) = ctx.update(|ctx| item.save(None, ctx)).await {
+ error!("failed to save item: {:?}, ", error);
}
})
.detach();
@@ -759,451 +755,436 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
mod tests {
use super::*;
use crate::{editor::BufferView, settings, test::temp_tree};
- use gpui::App;
use serde_json::json;
use std::{collections::HashSet, fs};
use tempdir::TempDir;
- #[test]
- fn test_open_paths_action() {
- App::test((), |app| {
- let settings = settings::channel(&app.font_cache()).unwrap().1;
-
- init(app);
-
- let dir = temp_tree(json!({
- "a": {
- "aa": null,
- "ab": null,
- },
- "b": {
- "ba": null,
- "bb": null,
- },
- "c": {
- "ca": null,
- "cb": null,
- },
- }));
-
- app.dispatch_global_action(
- "workspace:open_paths",
- OpenParams {
- paths: vec![
- dir.path().join("a").to_path_buf(),
- dir.path().join("b").to_path_buf(),
- ],
- settings: settings.clone(),
- },
- );
- assert_eq!(app.window_ids().count(), 1);
-
- app.dispatch_global_action(
- "workspace:open_paths",
- OpenParams {
- paths: vec![dir.path().join("a").to_path_buf()],
- settings: settings.clone(),
- },
- );
- assert_eq!(app.window_ids().count(), 1);
- let workspace_view_1 = app
- .root_view::<Workspace>(app.window_ids().next().unwrap())
- .unwrap();
- assert_eq!(workspace_view_1.read(app).worktrees().len(), 2);
-
- app.dispatch_global_action(
- "workspace:open_paths",
- OpenParams {
- paths: vec![
- dir.path().join("b").to_path_buf(),
- dir.path().join("c").to_path_buf(),
- ],
- settings: settings.clone(),
- },
- );
- assert_eq!(app.window_ids().count(), 2);
- });
- }
+ #[gpui::test]
+ fn test_open_paths_action(app: &mut gpui::MutableAppContext) {
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
- #[test]
- fn test_open_entry() {
- App::test_async((), |mut app| async move {
- let dir = temp_tree(json!({
- "a": {
- "file1": "contents 1",
- "file2": "contents 2",
- "file3": "contents 3",
- },
- }));
-
- 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.path(), ctx);
- workspace
- });
+ init(app);
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
- let entries = app.read(|ctx| workspace.file_entries(ctx));
- let file1 = entries[0].clone();
- let file2 = entries[1].clone();
- let file3 = entries[2].clone();
+ let dir = temp_tree(json!({
+ "a": {
+ "aa": null,
+ "ab": null,
+ },
+ "b": {
+ "ba": null,
+ "bb": null,
+ },
+ "c": {
+ "ca": null,
+ "cb": null,
+ },
+ }));
+
+ app.dispatch_global_action(
+ "workspace:open_paths",
+ OpenParams {
+ paths: vec![
+ dir.path().join("a").to_path_buf(),
+ dir.path().join("b").to_path_buf(),
+ ],
+ settings: settings.clone(),
+ },
+ );
+ assert_eq!(app.window_ids().count(), 1);
+
+ app.dispatch_global_action(
+ "workspace:open_paths",
+ OpenParams {
+ paths: vec![dir.path().join("a").to_path_buf()],
+ settings: settings.clone(),
+ },
+ );
+ assert_eq!(app.window_ids().count(), 1);
+ let workspace_view_1 = app
+ .root_view::<Workspace>(app.window_ids().next().unwrap())
+ .unwrap();
+ assert_eq!(workspace_view_1.read(app).worktrees().len(), 2);
+
+ app.dispatch_global_action(
+ "workspace:open_paths",
+ OpenParams {
+ paths: vec![
+ dir.path().join("b").to_path_buf(),
+ dir.path().join("c").to_path_buf(),
+ ],
+ settings: settings.clone(),
+ },
+ );
+ assert_eq!(app.window_ids().count(), 2);
+ }
- // Open the first entry
- workspace
- .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
- .unwrap()
- .await;
- app.read(|ctx| {
- let pane = workspace.read(ctx).active_pane().read(ctx);
- assert_eq!(
- pane.active_item().unwrap().entry_id(ctx),
- Some(file1.clone())
- );
- assert_eq!(pane.items().len(), 1);
- });
+ #[gpui::test]
+ async fn test_open_entry(mut app: gpui::TestAppContext) {
+ let dir = temp_tree(json!({
+ "a": {
+ "file1": "contents 1",
+ "file2": "contents 2",
+ "file3": "contents 3",
+ },
+ }));
- // Open the second entry
+ 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.path(), ctx);
workspace
- .update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx))
- .unwrap()
- .await;
- app.read(|ctx| {
- let pane = workspace.read(ctx).active_pane().read(ctx);
- assert_eq!(
- pane.active_item().unwrap().entry_id(ctx),
- Some(file2.clone())
- );
- assert_eq!(pane.items().len(), 2);
- });
-
- // Open the first entry again. The existing pane item is activated.
- workspace.update(&mut app, |w, ctx| {
- assert!(w.open_entry(file1.clone(), ctx).is_none())
- });
- app.read(|ctx| {
- let pane = workspace.read(ctx).active_pane().read(ctx);
- assert_eq!(
- pane.active_item().unwrap().entry_id(ctx),
- Some(file1.clone())
- );
- assert_eq!(pane.items().len(), 2);
- });
-
- // 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 = workspace.read(ctx).active_pane().read(ctx);
- assert_eq!(
- pane.active_item().unwrap().entry_id(ctx),
- Some(file3.clone())
- );
- let pane_entries = pane
- .items()
- .iter()
- .map(|i| i.entry_id(ctx).unwrap())
- .collect::<Vec<_>>();
- assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]);
- });
});
- }
- #[test]
- fn test_open_paths() {
- App::test_async((), |mut app| async move {
- let dir1 = temp_tree(json!({
- "a.txt": "",
- }));
- let dir2 = temp_tree(json!({
- "b.txt": "",
- }));
-
- 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(dir1.path(), ctx);
- workspace
- });
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
-
- // Open a file within an existing worktree.
- app.update(|ctx| {
- workspace.update(ctx, |view, ctx| {
- view.open_paths(&[dir1.path().join("a.txt")], ctx)
- })
- })
+ app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await;
- app.read(|ctx| {
- assert_eq!(
- workspace
- .read(ctx)
- .active_pane()
- .read(ctx)
- .active_item()
- .unwrap()
- .title(ctx),
- "a.txt"
- );
- });
+ let entries = app.read(|ctx| workspace.file_entries(ctx));
+ let file1 = entries[0].clone();
+ let file2 = entries[1].clone();
+ let file3 = entries[2].clone();
+
+ // Open the first entry
+ workspace
+ .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
+ .unwrap()
+ .await;
+ app.read(|ctx| {
+ let pane = workspace.read(ctx).active_pane().read(ctx);
+ assert_eq!(
+ pane.active_item().unwrap().entry_id(ctx),
+ Some(file1.clone())
+ );
+ assert_eq!(pane.items().len(), 1);
+ });
- // Open a file outside of any existing worktree.
- app.update(|ctx| {
- workspace.update(ctx, |view, ctx| {
- view.open_paths(&[dir2.path().join("b.txt")], ctx)
- })
- })
+ // Open the second entry
+ workspace
+ .update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx))
+ .unwrap()
.await;
- app.read(|ctx| {
- let worktree_roots = workspace
+ app.read(|ctx| {
+ let pane = workspace.read(ctx).active_pane().read(ctx);
+ assert_eq!(
+ pane.active_item().unwrap().entry_id(ctx),
+ Some(file2.clone())
+ );
+ assert_eq!(pane.items().len(), 2);
+ });
+
+ // Open the first entry again. The existing pane item is activated.
+ workspace.update(&mut app, |w, ctx| {
+ assert!(w.open_entry(file1.clone(), ctx).is_none())
+ });
+ app.read(|ctx| {
+ let pane = workspace.read(ctx).active_pane().read(ctx);
+ assert_eq!(
+ pane.active_item().unwrap().entry_id(ctx),
+ Some(file1.clone())
+ );
+ assert_eq!(pane.items().len(), 2);
+ });
+
+ // 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)
- .worktrees()
- .iter()
- .map(|w| w.read(ctx).abs_path())
- .collect::<HashSet<_>>();
- assert_eq!(
- worktree_roots,
- vec![dir1.path(), &dir2.path().join("b.txt")]
- .into_iter()
- .collect(),
- );
- assert_eq!(
- workspace
- .read(ctx)
- .active_pane()
- .read(ctx)
- .active_item()
- .unwrap()
- .title(ctx),
- "b.txt"
- );
- });
+ .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 = workspace.read(ctx).active_pane().read(ctx);
+ assert_eq!(
+ pane.active_item().unwrap().entry_id(ctx),
+ Some(file3.clone())
+ );
+ let pane_entries = pane
+ .items()
+ .iter()
+ .map(|i| i.entry_id(ctx).unwrap())
+ .collect::<Vec<_>>();
+ assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]);
});
}
- #[test]
- fn test_open_and_save_new_file() {
- App::test_async((), |mut app| async move {
- 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.path(), ctx);
- workspace
- });
- let tree = app.read(|ctx| {
+ #[gpui::test]
+ async fn test_open_paths(mut app: gpui::TestAppContext) {
+ let dir1 = temp_tree(json!({
+ "a.txt": "",
+ }));
+ let dir2 = temp_tree(json!({
+ "b.txt": "",
+ }));
+
+ 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(dir1.path(), ctx);
+ workspace
+ });
+ app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
+ .await;
+
+ // Open a file within an existing worktree.
+ app.update(|ctx| {
+ workspace.update(ctx, |view, ctx| {
+ view.open_paths(&[dir1.path().join("a.txt")], ctx)
+ })
+ })
+ .await;
+ app.read(|ctx| {
+ assert_eq!(
workspace
.read(ctx)
- .worktrees()
- .iter()
- .next()
+ .active_pane()
+ .read(ctx)
+ .active_item()
.unwrap()
- .clone()
- });
- tree.flush_fs_events(&app).await;
+ .title(ctx),
+ "a.txt"
+ );
+ });
- // 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()));
- });
-
- // 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!(editor.is_dirty(ctx));
- assert_eq!(editor.title(ctx), "untitled");
- });
-
- // When the save completes, the buffer's title is updated.
- tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
- .await;
- app.read(|ctx| {
- assert!(!editor.is_dirty(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((tree.id(), Path::new("the-new-name").into()), ctx)
- .is_none());
- });
- let editor2 = workspace.update(&mut app, |workspace, ctx| {
+ // Open a file outside of any existing worktree.
+ app.update(|ctx| {
+ workspace.update(ctx, |view, ctx| {
+ view.open_paths(&[dir2.path().join("b.txt")], ctx)
+ })
+ })
+ .await;
+ app.read(|ctx| {
+ let worktree_roots = workspace
+ .read(ctx)
+ .worktrees()
+ .iter()
+ .map(|w| w.read(ctx).abs_path())
+ .collect::<HashSet<_>>();
+ assert_eq!(
+ worktree_roots,
+ vec![dir1.path(), &dir2.path().join("b.txt")]
+ .into_iter()
+ .collect(),
+ );
+ assert_eq!(
workspace
- .active_item(ctx)
- .unwrap()
- .to_any()
- .downcast::<BufferView>()
+ .read(ctx)
+ .active_pane()
+ .read(ctx)
+ .active_item()
.unwrap()
- });
- app.read(|ctx| {
- assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer());
- })
+ .title(ctx),
+ "b.txt"
+ );
});
}
- #[test]
- fn test_save_conflicting_item() {
- App::test_async((), |mut app| async move {
- let dir = temp_tree(json!({
- "a.txt": "",
- }));
-
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (window_id, workspace) = app.add_window(|ctx| {
- let mut workspace = Workspace::new(0, settings, ctx);
- workspace.add_worktree(dir.path(), ctx);
- workspace
- });
- let tree = app.read(|ctx| {
- let mut trees = workspace.read(ctx).worktrees().iter();
- trees.next().unwrap().clone()
- });
- tree.flush_fs_events(&app).await;
-
- // Open a file within an existing worktree.
- app.update(|ctx| {
- workspace.update(ctx, |view, ctx| {
- view.open_paths(&[dir.path().join("a.txt")], ctx)
- })
+ #[gpui::test]
+ async fn test_save_conflicting_item(mut app: gpui::TestAppContext) {
+ let dir = temp_tree(json!({
+ "a.txt": "",
+ }));
+
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (window_id, workspace) = app.add_window(|ctx| {
+ let mut workspace = Workspace::new(0, settings, ctx);
+ workspace.add_worktree(dir.path(), ctx);
+ workspace
+ });
+ let tree = app.read(|ctx| {
+ let mut trees = workspace.read(ctx).worktrees().iter();
+ trees.next().unwrap().clone()
+ });
+ tree.flush_fs_events(&app).await;
+
+ // Open a file within an existing worktree.
+ app.update(|ctx| {
+ workspace.update(ctx, |view, ctx| {
+ view.open_paths(&[dir.path().join("a.txt")], ctx)
})
+ })
+ .await;
+ let editor = app.read(|ctx| {
+ let pane = workspace.read(ctx).active_pane().read(ctx);
+ let item = pane.active_item().unwrap();
+ item.to_any().downcast::<BufferView>().unwrap()
+ });
+
+ app.update(|ctx| editor.update(ctx, |editor, ctx| editor.insert(&"x".to_string(), ctx)));
+ fs::write(dir.path().join("a.txt"), "changed").unwrap();
+ tree.flush_fs_events(&app).await;
+ app.read(|ctx| {
+ assert!(editor.is_dirty(ctx));
+ assert!(editor.has_conflict(ctx));
+ });
+
+ app.update(|ctx| workspace.update(ctx, |w, ctx| w.save_active_item(&(), ctx)));
+ app.simulate_prompt_answer(window_id, 0);
+ tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
.await;
- let editor = app.read(|ctx| {
- let pane = workspace.read(ctx).active_pane().read(ctx);
- let item = pane.active_item().unwrap();
- item.to_any().downcast::<BufferView>().unwrap()
- });
-
- app.update(|ctx| {
- editor.update(ctx, |editor, ctx| editor.insert(&"x".to_string(), ctx))
- });
- fs::write(dir.path().join("a.txt"), "changed").unwrap();
- tree.flush_fs_events(&app).await;
- app.read(|ctx| {
- assert!(editor.is_dirty(ctx));
- assert!(editor.has_conflict(ctx));
- });
-
- app.update(|ctx| workspace.update(ctx, |w, ctx| w.save_active_item(&(), ctx)));
- app.simulate_prompt_answer(window_id, 0);
- tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
- .await;
- app.read(|ctx| {
- assert!(!editor.is_dirty(ctx));
- assert!(!editor.has_conflict(ctx));
- });
+ app.read(|ctx| {
+ assert!(!editor.is_dirty(ctx));
+ assert!(!editor.has_conflict(ctx));
});
}
- #[test]
- fn test_pane_actions() {
- App::test_async((), |mut app| async move {
- app.update(|ctx| pane::init(ctx));
-
- let dir = temp_tree(json!({
- "a": {
- "file1": "contents 1",
- "file2": "contents 2",
- "file3": "contents 3",
- },
- }));
-
- let settings = settings::channel(&app.font_cache()).unwrap().1;
- let (window_id, workspace) = app.add_window(|ctx| {
- let mut workspace = Workspace::new(0, settings, ctx);
- workspace.add_worktree(dir.path(), ctx);
- workspace
- });
- app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
- .await;
- let entries = app.read(|ctx| workspace.file_entries(ctx));
- let file1 = entries[0].clone();
+ #[gpui::test]
+ async fn test_open_and_save_new_file(mut app: gpui::TestAppContext) {
+ let dir = TempDir::new("test-new-file").unwrap();
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (_, workspace) = app.add_window(|ctx| {
+ let mut workspace = Workspace::new(0, settings, ctx);
+ workspace.add_worktree(dir.path(), ctx);
+ workspace
+ });
+ let tree = app.read(|ctx| {
+ workspace
+ .read(ctx)
+ .worktrees()
+ .iter()
+ .next()
+ .unwrap()
+ .clone()
+ });
+ tree.flush_fs_events(&app).await;
+
+ // 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()));
+ });
+
+ // 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!(editor.is_dirty(ctx));
+ assert_eq!(editor.title(ctx), "untitled");
+ });
- let pane_1 = app.read(|ctx| workspace.read(ctx).active_pane().clone());
+ // When the save completes, the buffer's title is updated.
+ tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
+ .await;
+ app.read(|ctx| {
+ assert!(!editor.is_dirty(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((tree.id(), Path::new("the-new-name").into()), ctx)
+ .is_none());
+ });
+ let editor2 = workspace.update(&mut app, |workspace, ctx| {
workspace
- .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
+ .active_item(ctx)
.unwrap()
- .await;
- app.read(|ctx| {
- assert_eq!(
- pane_1.read(ctx).active_item().unwrap().entry_id(ctx),
- Some(file1.clone())
- );
- });
+ .to_any()
+ .downcast::<BufferView>()
+ .unwrap()
+ });
+ app.read(|ctx| {
+ assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer());
+ })
+ }
+
+ #[gpui::test]
+ async fn test_pane_actions(mut app: gpui::TestAppContext) {
+ app.update(|ctx| pane::init(ctx));
+
+ let dir = temp_tree(json!({
+ "a": {
+ "file1": "contents 1",
+ "file2": "contents 2",
+ "file3": "contents 3",
+ },
+ }));
+
+ let settings = settings::channel(&app.font_cache()).unwrap().1;
+ let (window_id, workspace) = app.add_window(|ctx| {
+ let mut workspace = Workspace::new(0, settings, ctx);
+ workspace.add_worktree(dir.path(), ctx);
+ workspace
+ });
+ app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
+ .await;
+ let entries = app.read(|ctx| workspace.file_entries(ctx));
+ let file1 = entries[0].clone();
+
+ let pane_1 = app.read(|ctx| workspace.read(ctx).active_pane().clone());
+
+ workspace
+ .update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx))
+ .unwrap()
+ .await;
+ app.read(|ctx| {
+ assert_eq!(
+ pane_1.read(ctx).active_item().unwrap().entry_id(ctx),
+ Some(file1.clone())
+ );
+ });
- app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
- app.update(|ctx| {
- let pane_2 = workspace.read(ctx).active_pane().clone();
- assert_ne!(pane_1, pane_2);
+ app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
+ app.update(|ctx| {
+ let pane_2 = workspace.read(ctx).active_pane().clone();
+ assert_ne!(pane_1, pane_2);
- let pane2_item = pane_2.read(ctx).active_item().unwrap();
- assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone()));
+ let pane2_item = pane_2.read(ctx).active_item().unwrap();
+ assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone()));
- ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
- let workspace_view = workspace.read(ctx);
- assert_eq!(workspace_view.panes.len(), 1);
- assert_eq!(workspace_view.active_pane(), &pane_1);
- });
+ ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
+ let workspace_view = workspace.read(ctx);
+ assert_eq!(workspace_view.panes.len(), 1);
+ assert_eq!(workspace_view.active_pane(), &pane_1);
});
}
}
@@ -16,7 +16,7 @@ use postage::{
prelude::{Sink, Stream},
watch,
};
-use smol::{channel::Sender, Timer};
+use smol::channel::Sender;
use std::{
cmp,
collections::{HashMap, HashSet},
@@ -99,8 +99,27 @@ impl Worktree {
scanner.run(event_stream)
});
- ctx.spawn_stream(scan_state_rx, Self::observe_scan_state, |_, _| {})
- .detach();
+ ctx.spawn(|this, mut ctx| {
+ let this = this.downgrade();
+ async move {
+ while let Ok(scan_state) = scan_state_rx.recv().await {
+ let alive = ctx.update(|ctx| {
+ if let Some(handle) = this.upgrade(&ctx) {
+ handle
+ .update(ctx, |this, ctx| this.observe_scan_state(scan_state, ctx));
+ true
+ } else {
+ false
+ }
+ });
+
+ if !alive {
+ break;
+ }
+ }
+ }
+ })
+ .detach();
tree
}
@@ -117,15 +136,16 @@ impl Worktree {
pub fn next_scan_complete(&self, ctx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
let scan_id = self.snapshot.scan_id;
- ctx.spawn_stream(
- self.scan_state.1.clone(),
- move |this, scan_state, ctx| {
- if matches!(scan_state, ScanState::Idle) && this.snapshot.scan_id > scan_id {
- ctx.halt_stream();
+ let mut scan_state = self.scan_state.1.clone();
+ ctx.spawn(|this, ctx| async move {
+ while let Some(scan_state) = scan_state.recv().await {
+ if this.read_with(&ctx, |this, _| {
+ matches!(scan_state, ScanState::Idle) && this.snapshot.scan_id > scan_id
+ }) {
+ break;
}
- },
- |_, _| {},
- )
+ }
+ })
}
fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
@@ -138,9 +158,11 @@ impl Worktree {
ctx.notify();
if self.is_scanning() && !self.poll_scheduled {
- ctx.spawn(Timer::after(Duration::from_millis(100)), |this, _, ctx| {
- this.poll_scheduled = false;
- this.poll_entries(ctx);
+ ctx.spawn(|this, mut ctx| async move {
+ this.update(&mut ctx, |this, ctx| {
+ this.poll_scheduled = false;
+ this.poll_entries(ctx);
+ })
})
.detach();
self.poll_scheduled = true;
@@ -1394,7 +1416,6 @@ mod tests {
use crate::editor::Buffer;
use crate::test::*;
use anyhow::Result;
- use gpui::App;
use rand::prelude::*;
use serde_json::json;
use std::env;
@@ -1402,248 +1423,237 @@ mod tests {
use std::os::unix;
use std::time::{SystemTime, UNIX_EPOCH};
- #[test]
- fn test_populate_and_search() {
- App::test_async((), |mut app| async move {
- let dir = temp_tree(json!({
- "root": {
- "apple": "",
- "banana": {
- "carrot": {
- "date": "",
- "endive": "",
- }
- },
- "fennel": {
- "grape": "",
+ #[gpui::test]
+ async fn test_populate_and_search(mut app: gpui::TestAppContext) {
+ let dir = temp_tree(json!({
+ "root": {
+ "apple": "",
+ "banana": {
+ "carrot": {
+ "date": "",
+ "endive": "",
}
+ },
+ "fennel": {
+ "grape": "",
}
- }));
+ }
+ }));
- let root_link_path = dir.path().join("root_link");
- unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
- unix::fs::symlink(
- &dir.path().join("root/fennel"),
- &dir.path().join("root/finnochio"),
- )
- .unwrap();
+ let root_link_path = dir.path().join("root_link");
+ unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
+ unix::fs::symlink(
+ &dir.path().join("root/fennel"),
+ &dir.path().join("root/finnochio"),
+ )
+ .unwrap();
- let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
+ let tree = app.add_model(|ctx| Worktree::new(root_link_path, ctx));
- app.read(|ctx| tree.read(ctx).scan_complete()).await;
- app.read(|ctx| {
- let tree = tree.read(ctx);
- assert_eq!(tree.file_count(), 5);
+ app.read(|ctx| tree.read(ctx).scan_complete()).await;
+ app.read(|ctx| {
+ let tree = tree.read(ctx);
+ assert_eq!(tree.file_count(), 5);
- assert_eq!(
- tree.inode_for_path("fennel/grape"),
- tree.inode_for_path("finnochio/grape")
- );
+ assert_eq!(
+ tree.inode_for_path("fennel/grape"),
+ tree.inode_for_path("finnochio/grape")
+ );
- let results = match_paths(
- Some(tree.snapshot()).iter(),
- "bna",
- false,
- false,
- false,
- 10,
- Default::default(),
- ctx.thread_pool().clone(),
- )
- .into_iter()
- .map(|result| result.path)
- .collect::<Vec<Arc<Path>>>();
- assert_eq!(
- results,
- vec![
- PathBuf::from("banana/carrot/date").into(),
- PathBuf::from("banana/carrot/endive").into(),
- ]
- );
- })
- });
+ let results = match_paths(
+ Some(tree.snapshot()).iter(),
+ "bna",
+ false,
+ false,
+ false,
+ 10,
+ Default::default(),
+ ctx.thread_pool().clone(),
+ )
+ .into_iter()
+ .map(|result| result.path)
+ .collect::<Vec<Arc<Path>>>();
+ assert_eq!(
+ results,
+ vec![
+ PathBuf::from("banana/carrot/date").into(),
+ PathBuf::from("banana/carrot/endive").into(),
+ ]
+ );
+ })
}
- #[test]
- fn test_save_file() {
- App::test_async((), |mut app| async move {
- let dir = temp_tree(json!({
- "file1": "the old contents",
- }));
+ #[gpui::test]
+ async fn test_save_file(mut app: gpui::TestAppContext) {
+ let dir = temp_tree(json!({
+ "file1": "the old contents",
+ }));
- let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
- app.read(|ctx| tree.read(ctx).scan_complete()).await;
- app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
+ let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
+ 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(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
+ 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();
- assert_eq!(path.file_name().unwrap(), "file1");
- smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref()))
- .unwrap();
- path
- });
+ let path = tree.update(&mut app, |tree, ctx| {
+ let path = tree.files(0).next().unwrap().path().clone();
+ assert_eq!(path.file_name().unwrap(), "file1");
+ smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
+ path
+ });
- let history = app
- .read(|ctx| tree.read(ctx).load_history(&path, ctx))
- .await
- .unwrap();
- app.read(|ctx| {
- assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text());
- });
+ let history = app
+ .read(|ctx| tree.read(ctx).load_history(&path, ctx))
+ .await
+ .unwrap();
+ app.read(|ctx| {
+ assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text());
});
}
- #[test]
- fn test_save_in_single_file_worktree() {
- App::test_async((), |mut app| async move {
- let dir = temp_tree(json!({
- "file1": "the old contents",
- }));
-
- let tree = app.add_model(|ctx| Worktree::new(dir.path().join("file1"), ctx));
- app.read(|ctx| tree.read(ctx).scan_complete()).await;
- app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
+ #[gpui::test]
+ async fn test_save_in_single_file_worktree(mut app: gpui::TestAppContext) {
+ let dir = temp_tree(json!({
+ "file1": "the old contents",
+ }));
- let buffer =
- app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
+ let tree = app.add_model(|ctx| Worktree::new(dir.path().join("file1"), ctx));
+ app.read(|ctx| tree.read(ctx).scan_complete()).await;
+ app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 1));
- let file = app.read(|ctx| tree.file("", ctx));
- app.update(|ctx| {
- assert_eq!(file.path().file_name(), None);
- smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
- });
+ let buffer =
+ app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx));
- let history = app.read(|ctx| file.load_history(ctx)).await.unwrap();
- app.read(|ctx| assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text()));
+ let file = app.read(|ctx| tree.file("", ctx));
+ app.update(|ctx| {
+ assert_eq!(file.path().file_name(), None);
+ smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
});
+
+ let history = app.read(|ctx| file.load_history(ctx)).await.unwrap();
+ app.read(|ctx| assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text()));
}
- #[test]
- fn test_rescan_simple() {
- App::test_async((), |mut app| async move {
- let dir = temp_tree(json!({
- "a": {
- "file1": "",
- "file2": "",
- "file3": "",
- },
- "b": {
- "c": {
- "file4": "",
- "file5": "",
- }
+ #[gpui::test]
+ async fn test_rescan_simple(mut app: gpui::TestAppContext) {
+ let dir = temp_tree(json!({
+ "a": {
+ "file1": "",
+ "file2": "",
+ "file3": "",
+ },
+ "b": {
+ "c": {
+ "file4": "",
+ "file5": "",
}
- }));
-
- let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
- let (file2, file3, file4, file5, non_existent_file) = app.read(|ctx| {
- (
- tree.file("a/file2", ctx),
- tree.file("a/file3", ctx),
- tree.file("b/c/file4", ctx),
- tree.file("b/c/file5", ctx),
- tree.file("a/filex", ctx),
- )
- });
+ }
+ }));
+
+ let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
+ let (file2, file3, file4, file5, non_existent_file) = app.read(|ctx| {
+ (
+ tree.file("a/file2", ctx),
+ tree.file("a/file3", ctx),
+ tree.file("b/c/file4", ctx),
+ tree.file("b/c/file5", ctx),
+ tree.file("a/filex", ctx),
+ )
+ });
- // The worktree hasn't scanned the directories containing these paths,
- // so it can't determine that the paths are deleted.
- assert!(!file2.is_deleted());
- assert!(!file3.is_deleted());
- assert!(!file4.is_deleted());
- assert!(!file5.is_deleted());
- assert!(!non_existent_file.is_deleted());
+ // The worktree hasn't scanned the directories containing these paths,
+ // so it can't determine that the paths are deleted.
+ assert!(!file2.is_deleted());
+ assert!(!file3.is_deleted());
+ assert!(!file4.is_deleted());
+ assert!(!file5.is_deleted());
+ assert!(!non_existent_file.is_deleted());
+
+ // After scanning, the worktree knows which files exist and which don't.
+ app.read(|ctx| tree.read(ctx).scan_complete()).await;
+ assert!(!file2.is_deleted());
+ assert!(!file3.is_deleted());
+ assert!(!file4.is_deleted());
+ assert!(!file5.is_deleted());
+ assert!(non_existent_file.is_deleted());
+
+ tree.flush_fs_events(&app).await;
+ std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
+ std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
+ std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
+ std::fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
+ tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
+ .await;
- // After scanning, the worktree knows which files exist and which don't.
- app.read(|ctx| tree.read(ctx).scan_complete()).await;
+ app.read(|ctx| {
+ assert_eq!(
+ tree.read(ctx)
+ .paths()
+ .map(|p| p.to_str().unwrap())
+ .collect::<Vec<_>>(),
+ vec![
+ "a",
+ "a/file1",
+ "a/file2.new",
+ "b",
+ "d",
+ "d/file3",
+ "d/file4"
+ ]
+ );
+
+ assert_eq!(file2.path().to_str().unwrap(), "a/file2.new");
+ assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
+ assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
assert!(!file2.is_deleted());
- assert!(!file3.is_deleted());
assert!(!file4.is_deleted());
- assert!(!file5.is_deleted());
- assert!(non_existent_file.is_deleted());
-
- tree.flush_fs_events(&app).await;
- fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
- fs::remove_file(dir.path().join("b/c/file5")).unwrap();
- fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
- fs::rename(dir.path().join("a/file2"), dir.path().join("a/file2.new")).unwrap();
- tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
- .await;
-
- app.read(|ctx| {
- assert_eq!(
- tree.read(ctx)
- .paths()
- .map(|p| p.to_str().unwrap())
- .collect::<Vec<_>>(),
- vec![
- "a",
- "a/file1",
- "a/file2.new",
- "b",
- "d",
- "d/file3",
- "d/file4"
- ]
- );
+ assert!(file5.is_deleted());
- assert_eq!(file2.path().to_str().unwrap(), "a/file2.new");
- assert_eq!(file4.path().as_ref(), Path::new("d/file4"));
- assert_eq!(file5.path().as_ref(), Path::new("d/file5"));
- assert!(!file2.is_deleted());
- assert!(!file4.is_deleted());
- assert!(file5.is_deleted());
-
- // Right now, this rename isn't detected because the target path
- // no longer exists on the file system by the time we process the
- // rename event.
- assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
- assert!(file3.is_deleted());
- });
+ // Right now, this rename isn't detected because the target path
+ // no longer exists on the file system by the time we process the
+ // rename event.
+ assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
+ assert!(file3.is_deleted());
});
}
- #[test]
- fn test_rescan_with_gitignore() {
- App::test_async((), |mut app| async move {
- let dir = temp_tree(json!({
- ".git": {},
- ".gitignore": "ignored-dir\n",
- "tracked-dir": {
- "tracked-file1": "tracked contents",
- },
- "ignored-dir": {
- "ignored-file1": "ignored contents",
- }
- }));
-
- let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
- app.read(|ctx| tree.read(ctx).scan_complete()).await;
- tree.flush_fs_events(&app).await;
- app.read(|ctx| {
- let tree = tree.read(ctx);
- let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
- let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
- assert_eq!(tracked.is_ignored(), false);
- assert_eq!(ignored.is_ignored(), true);
- });
+ #[gpui::test]
+ async fn test_rescan_with_gitignore(mut app: gpui::TestAppContext) {
+ let dir = temp_tree(json!({
+ ".git": {},
+ ".gitignore": "ignored-dir\n",
+ "tracked-dir": {
+ "tracked-file1": "tracked contents",
+ },
+ "ignored-dir": {
+ "ignored-file1": "ignored contents",
+ }
+ }));
+
+ let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
+ app.read(|ctx| tree.read(ctx).scan_complete()).await;
+ tree.flush_fs_events(&app).await;
+ app.read(|ctx| {
+ let tree = tree.read(ctx);
+ let tracked = tree.entry_for_path("tracked-dir/tracked-file1").unwrap();
+ let ignored = tree.entry_for_path("ignored-dir/ignored-file1").unwrap();
+ assert_eq!(tracked.is_ignored(), false);
+ assert_eq!(ignored.is_ignored(), true);
+ });
- fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
- fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
- tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
- .await;
- app.read(|ctx| {
- let tree = tree.read(ctx);
- let dot_git = tree.entry_for_path(".git").unwrap();
- let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
- let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
- assert_eq!(tracked.is_ignored(), false);
- assert_eq!(ignored.is_ignored(), true);
- assert_eq!(dot_git.is_ignored(), true);
- });
+ fs::write(dir.path().join("tracked-dir/tracked-file2"), "").unwrap();
+ fs::write(dir.path().join("ignored-dir/ignored-file2"), "").unwrap();
+ tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
+ .await;
+ app.read(|ctx| {
+ let tree = tree.read(ctx);
+ let dot_git = tree.entry_for_path(".git").unwrap();
+ let tracked = tree.entry_for_path("tracked-dir/tracked-file2").unwrap();
+ let ignored = tree.entry_for_path("ignored-dir/ignored-file2").unwrap();
+ assert_eq!(tracked.is_ignored(), false);
+ assert_eq!(ignored.is_ignored(), true);
+ assert_eq!(dot_git.is_ignored(), true);
});
}