Merge branch 'master' into file-changed-on-disk

This commit is contained in:
Max Brunsfeld 2021-05-12 16:20:03 -07:00
commit 4910bc50c6
14 changed files with 3718 additions and 4119 deletions

9
Cargo.lock generated
View file

@ -1180,6 +1180,7 @@ dependencies = [
"etagere", "etagere",
"font-kit", "font-kit",
"foreign-types", "foreign-types",
"gpui_macros",
"log", "log",
"metal", "metal",
"num_cpus", "num_cpus",
@ -1205,6 +1206,14 @@ dependencies = [
"usvg", "usvg",
] ]
[[package]]
name = "gpui_macros"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.9.1" version = "0.9.1"

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["zed", "gpui", "fsevent", "scoped_pool"] members = ["zed", "gpui", "gpui_macros", "fsevent", "scoped_pool"]
[patch.crates-io] [patch.crates-io]
async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"} async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}

View file

@ -8,6 +8,7 @@ version = "0.1.0"
async-task = "4.0.3" async-task = "4.0.3"
ctor = "0.1" ctor = "0.1"
etagere = "0.2" etagere = "0.2"
gpui_macros = {path = "../gpui_macros"}
log = "0.4" log = "0.4"
num_cpus = "1.13" num_cpus = "1.13"
ordered-float = "2.1.1" ordered-float = "2.1.1"

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@ pub mod color;
pub mod json; pub mod json;
pub mod keymap; pub mod keymap;
mod platform; mod platform;
pub use gpui_macros::test;
pub use platform::{Event, PathPromptOptions, PromptLevel}; pub use platform::{Event, PathPromptOptions, PromptLevel};
pub use presenter::{ pub use presenter::{
AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext, AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,

11
gpui_macros/Cargo.toml Normal file
View file

@ -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"

57
gpui_macros/src/lib.rs Normal file
View file

@ -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))
}

View file

@ -4,12 +4,10 @@ mod selection;
mod text; mod text;
pub use anchor::*; pub use anchor::*;
use futures_core::future::LocalBoxFuture;
pub use point::*; pub use point::*;
use seahash::SeaHasher; use seahash::SeaHasher;
pub use selection::*; pub use selection::*;
use similar::{ChangeTag, TextDiff}; use similar::{ChangeTag, TextDiff};
use smol::future::FutureExt;
pub use text::*; pub use text::*;
use crate::{ use crate::{
@ -20,7 +18,7 @@ use crate::{
worktree::FileHandle, worktree::FileHandle,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use gpui::{Entity, EntityTask, ModelContext}; use gpui::{Entity, ModelContext, Task};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rand::prelude::*; use rand::prelude::*;
use std::{ use std::{
@ -385,24 +383,28 @@ impl Buffer {
if file.is_deleted() { if file.is_deleted() {
ctx.emit(Event::Dirtied); ctx.emit(Event::Dirtied);
} else { } else {
ctx.spawn( ctx.spawn(|handle, mut ctx| async move {
file.load_history(ctx.as_ref()), let (current_version, history) = handle.read_with(&ctx, |this, ctx| {
move |this, history, ctx| { (this.version.clone(), file.load_history(ctx.as_ref()))
if let (Ok(history), true) = (history, this.version == version) { });
let task = this.set_text_via_diff(history.base_text, ctx); if let (Ok(history), true) = (history.await, current_version == version)
ctx.spawn(task, move |this, ops, ctx| { {
if ops.is_some() { let operations = handle
.update(&mut ctx, |this, ctx| {
this.set_text_via_diff(history.base_text, ctx)
})
.await;
if operations.is_some() {
handle.update(&mut ctx, |this, ctx| {
this.saved_version = this.version.clone(); this.saved_version = this.version.clone();
this.saved_mtime = file.mtime(); this.saved_mtime = file.mtime();
ctx.emit(Event::Reloaded); ctx.emit(Event::Reloaded);
});
}
} }
}) })
.detach(); .detach();
} }
},
)
.detach()
}
} }
ctx.emit(Event::FileHandleChanged); ctx.emit(Event::FileHandleChanged);
}); });
@ -499,21 +501,22 @@ impl Buffer {
&mut self, &mut self,
new_file: Option<FileHandle>, new_file: Option<FileHandle>,
ctx: &mut ModelContext<Self>, ctx: &mut ModelContext<Self>,
) -> LocalBoxFuture<'static, Result<()>> { ) -> Task<Result<()>> {
let snapshot = self.snapshot(); let snapshot = self.snapshot();
let version = self.version.clone(); let version = self.version.clone();
if let Some(file) = new_file.as_ref().or(self.file.as_ref()) { let file = self.file.clone();
let save_task = file.save(snapshot, ctx.as_ref());
ctx.spawn(save_task, |me, save_result, ctx| { ctx.spawn(|handle, mut ctx| async move {
if save_result.is_ok() { if let Some(file) = new_file.as_ref().or(file.as_ref()) {
me.did_save(version, new_file, ctx); 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 result
})
.boxed_local()
} else { } else {
async { Ok(()) }.boxed_local() Ok(())
} }
})
} }
fn did_save( fn did_save(
@ -536,11 +539,13 @@ impl Buffer {
&mut self, &mut self,
new_text: Arc<str>, new_text: Arc<str>,
ctx: &mut ModelContext<Self>, ctx: &mut ModelContext<Self>,
) -> EntityTask<Option<Vec<Operation>>> { ) -> Task<Option<Vec<Operation>>> {
let version = self.version.clone(); let version = self.version.clone();
let old_text = self.text(); let old_text = self.text();
ctx.spawn( ctx.spawn(|handle, mut ctx| async move {
ctx.background_executor().spawn({ let diff = ctx
.background_executor()
.spawn({
let new_text = new_text.clone(); let new_text = new_text.clone();
async move { async move {
TextDiff::from_lines(old_text.as_str(), new_text.as_ref()) TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
@ -548,8 +553,9 @@ impl Buffer {
.map(|c| (c.tag(), c.value().len())) .map(|c| (c.tag(), c.value().len()))
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
}), })
move |this, diff, ctx| { .await;
handle.update(&mut ctx, |this, ctx| {
if this.version == version { if this.version == version {
this.start_transaction(None).unwrap(); this.start_transaction(None).unwrap();
let mut operations = Vec::new(); let mut operations = Vec::new();
@ -575,8 +581,8 @@ impl Buffer {
} else { } else {
None None
} }
}, })
) })
} }
pub fn is_dirty(&self) -> bool { pub fn is_dirty(&self) -> bool {
@ -2480,9 +2486,8 @@ mod tests {
sync::atomic::{self, AtomicUsize}, sync::atomic::{self, AtomicUsize},
}; };
#[test] #[gpui::test]
fn test_edit() { fn test_edit(ctx: &mut gpui::MutableAppContext) {
App::test((), |ctx| {
ctx.add_model(|ctx| { ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "abc", ctx); let mut buffer = Buffer::new(0, "abc", ctx);
assert_eq!(buffer.text(), "abc"); assert_eq!(buffer.text(), "abc");
@ -2498,12 +2503,10 @@ mod tests {
assert_eq!(buffer.text(), "ghiamnoef"); assert_eq!(buffer.text(), "ghiamnoef");
buffer buffer
}); });
})
} }
#[test] #[gpui::test]
fn test_edit_events() { fn test_edit_events(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let mut now = Instant::now(); let mut now = Instant::now();
let buffer_1_events = Rc::new(RefCell::new(Vec::new())); let buffer_1_events = Rc::new(RefCell::new(Vec::new()));
let buffer_2_events = Rc::new(RefCell::new(Vec::new())); let buffer_2_events = Rc::new(RefCell::new(Vec::new()));
@ -2558,13 +2561,11 @@ mod tests {
let buffer_2_events = buffer_2_events.borrow(); let buffer_2_events = buffer_2_events.borrow();
assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]); assert_eq!(*buffer_2_events, vec![Event::Edited, Event::Dirtied]);
});
} }
#[test] #[gpui::test]
fn test_random_edits() { fn test_random_edits(ctx: &mut gpui::MutableAppContext) {
for seed in 0..100 { for seed in 0..100 {
App::test((), |ctx| {
println!("{:?}", seed); println!("{:?}", seed);
let mut rng = &mut StdRng::seed_from_u64(seed); let mut rng = &mut StdRng::seed_from_u64(seed);
@ -2638,8 +2639,7 @@ mod tests {
let old_len = old_range.end - old_range.start; let old_len = old_range.end - old_range.start;
let new_len = new_range.end - new_range.start; let new_len = new_range.end - new_range.start;
let old_start = (old_range.start as isize + delta) as usize; let old_start = (old_range.start as isize + delta) as usize;
let new_text: String = let new_text: String = buffer.text_for_range(new_range).unwrap().collect();
buffer.text_for_range(new_range).unwrap().collect();
old_buffer old_buffer
.edit(Some(old_start..old_start + old_len), new_text, None) .edit(Some(old_start..old_start + old_len), new_text, None)
.unwrap(); .unwrap();
@ -2650,14 +2650,12 @@ mod tests {
} }
buffer buffer
})
}); });
} }
} }
#[test] #[gpui::test]
fn test_line_len() { fn test_line_len(ctx: &mut gpui::MutableAppContext) {
App::test((), |ctx| {
ctx.add_model(|ctx| { ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx); let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap(); buffer.edit(vec![0..0], "abcd\nefg\nhij", None).unwrap();
@ -2674,12 +2672,10 @@ mod tests {
assert!(buffer.line_len(6).is_err()); assert!(buffer.line_len(6).is_err());
buffer buffer
}); });
});
} }
#[test] #[gpui::test]
fn test_rightmost_point() { fn test_rightmost_point(ctx: &mut gpui::MutableAppContext) {
App::test((), |ctx| {
ctx.add_model(|ctx| { ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx); let mut buffer = Buffer::new(0, "", ctx);
assert_eq!(buffer.rightmost_point().row, 0); assert_eq!(buffer.rightmost_point().row, 0);
@ -2695,12 +2691,10 @@ mod tests {
assert_eq!(buffer.rightmost_point().row, 4); assert_eq!(buffer.rightmost_point().row, 4);
buffer buffer
}); });
});
} }
#[test] #[gpui::test]
fn test_text_summary_for_range() { fn test_text_summary_for_range(ctx: &mut gpui::MutableAppContext) {
App::test((), |ctx| {
ctx.add_model(|ctx| { ctx.add_model(|ctx| {
let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx); let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx);
let text = Text::from(buffer.text()); let text = Text::from(buffer.text());
@ -2726,12 +2720,10 @@ mod tests {
); );
buffer buffer
}); });
});
} }
#[test] #[gpui::test]
fn test_chars_at() { fn test_chars_at(ctx: &mut gpui::MutableAppContext) {
App::test((), |ctx| {
ctx.add_model(|ctx| { ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx); let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap(); buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap();
@ -2764,7 +2756,6 @@ mod tests {
buffer buffer
}); });
});
} }
// #[test] // #[test]
@ -2881,9 +2872,8 @@ mod tests {
} }
} }
#[test] #[gpui::test]
fn test_anchors() { fn test_anchors(ctx: &mut gpui::MutableAppContext) {
App::test((), |ctx| {
ctx.add_model(|ctx| { ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx); let mut buffer = Buffer::new(0, "", ctx);
buffer.edit(vec![0..0], "abc", None).unwrap(); buffer.edit(vec![0..0], "abc", None).unwrap();
@ -3043,12 +3033,10 @@ mod tests {
); );
buffer buffer
}); });
});
} }
#[test] #[gpui::test]
fn test_anchors_at_start_and_end() { fn test_anchors_at_start_and_end(ctx: &mut gpui::MutableAppContext) {
App::test((), |ctx| {
ctx.add_model(|ctx| { ctx.add_model(|ctx| {
let mut buffer = Buffer::new(0, "", ctx); let mut buffer = Buffer::new(0, "", ctx);
let before_start_anchor = buffer.anchor_before(0).unwrap(); let before_start_anchor = buffer.anchor_before(0).unwrap();
@ -3071,7 +3059,6 @@ mod tests {
assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9); assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9);
buffer buffer
}); });
});
} }
#[test] #[test]
@ -3190,9 +3177,8 @@ mod tests {
}); });
} }
#[test] #[gpui::test]
fn test_file_changes_on_disk() { async fn test_file_changes_on_disk(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let initial_contents = "aaa\nbbbbb\nc\n"; let initial_contents = "aaa\nbbbbb\nc\n";
let dir = temp_tree(json!({ "the-file": initial_contents })); let dir = temp_tree(json!({ "the-file": initial_contents }));
let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx)); let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx));
@ -3279,12 +3265,10 @@ mod tests {
buffer buffer
.condition(&app, |buffer, _| buffer.has_conflict()) .condition(&app, |buffer, _| buffer.has_conflict())
.await; .await;
});
} }
#[test] #[gpui::test]
fn test_set_text_via_diff() { async fn test_set_text_via_diff(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n"; let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
@ -3299,12 +3283,10 @@ mod tests {
.update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx)) .update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx))
.await; .await;
app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text)); app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text));
});
} }
#[test] #[gpui::test]
fn test_undo_redo() { fn test_undo_redo(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
app.add_model(|ctx| { app.add_model(|ctx| {
let mut buffer = Buffer::new(0, "1234", ctx); let mut buffer = Buffer::new(0, "1234", ctx);
@ -3336,18 +3318,16 @@ mod tests {
buffer buffer
}); });
});
} }
#[test] #[gpui::test]
fn test_history() { fn test_history(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
app.add_model(|ctx| { app.add_model(|ctx| {
let mut now = Instant::now(); let mut now = Instant::now();
let mut buffer = Buffer::new(0, "123456", ctx); let mut buffer = Buffer::new(0, "123456", ctx);
let (set_id, _) = buffer let (set_id, _) =
.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None); buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None);
buffer.start_transaction_at(Some(set_id), now).unwrap(); buffer.start_transaction_at(Some(set_id), now).unwrap();
buffer.edit(vec![2..4], "cd", None).unwrap(); buffer.edit(vec![2..4], "cd", None).unwrap();
buffer.end_transaction_at(Some(set_id), now, None).unwrap(); buffer.end_transaction_at(Some(set_id), now, None).unwrap();
@ -3406,11 +3386,10 @@ mod tests {
buffer buffer
}); });
});
} }
#[test] #[gpui::test]
fn test_random_concurrent_edits() { fn test_random_concurrent_edits(ctx: &mut gpui::MutableAppContext) {
use crate::test::Network; use crate::test::Network;
const PEERS: usize = 5; const PEERS: usize = 5;
@ -3419,7 +3398,6 @@ mod tests {
println!("{:?}", seed); println!("{:?}", seed);
let mut rng = &mut StdRng::seed_from_u64(seed); let mut rng = &mut StdRng::seed_from_u64(seed);
App::test((), |ctx| {
let base_text_len = rng.gen_range(0..10); let base_text_len = rng.gen_range(0..10);
let base_text = RandomCharIter::new(&mut rng) let base_text = RandomCharIter::new(&mut rng)
.take(base_text_len) .take(base_text_len)
@ -3478,7 +3456,6 @@ mod tests {
.collect::<HashMap<_, _>>() .collect::<HashMap<_, _>>()
); );
} }
});
} }
} }

View file

@ -4,11 +4,10 @@ use super::{
}; };
use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle}; use crate::{settings::Settings, util::post_inc, workspace, worktree::FileHandle};
use anyhow::Result; use anyhow::Result;
use futures_core::future::LocalBoxFuture;
use gpui::{ use gpui::{
fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout, fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout,
AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle, AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle,
MutableAppContext, TextLayoutCache, View, ViewContext, WeakViewHandle, MutableAppContext, Task, TextLayoutCache, View, ViewContext, WeakViewHandle,
}; };
use parking_lot::Mutex; use parking_lot::Mutex;
use postage::watch; use postage::watch;
@ -2348,13 +2347,12 @@ impl BufferView {
ctx.notify(); ctx.notify();
let epoch = self.next_blink_epoch(); let epoch = self.next_blink_epoch();
ctx.spawn( ctx.spawn(|this, mut ctx| async move {
async move {
Timer::after(CURSOR_BLINK_INTERVAL).await; Timer::after(CURSOR_BLINK_INTERVAL).await;
epoch this.update(&mut ctx, |this, ctx| {
}, this.resume_cursor_blinking(epoch, ctx);
Self::resume_cursor_blinking, })
) })
.detach(); .detach();
} }
@ -2371,13 +2369,10 @@ impl BufferView {
ctx.notify(); ctx.notify();
let epoch = self.next_blink_epoch(); let epoch = self.next_blink_epoch();
ctx.spawn( ctx.spawn(|this, mut ctx| async move {
async move {
Timer::after(CURSOR_BLINK_INTERVAL).await; Timer::after(CURSOR_BLINK_INTERVAL).await;
epoch this.update(&mut ctx, |this, ctx| this.blink_cursors(epoch, ctx));
}, })
Self::blink_cursors,
)
.detach(); .detach();
} }
} }
@ -2499,7 +2494,7 @@ impl workspace::ItemView for BufferView {
&mut self, &mut self,
new_file: Option<FileHandle>, new_file: Option<FileHandle>,
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) -> LocalBoxFuture<'static, Result<()>> { ) -> Task<Result<()>> {
self.buffer.update(ctx, |b, ctx| b.save(new_file, ctx)) self.buffer.update(ctx, |b, ctx| b.save(new_file, ctx))
} }
@ -2516,17 +2511,13 @@ impl workspace::ItemView for BufferView {
mod tests { mod tests {
use super::*; use super::*;
use crate::{editor::Point, settings, test::sample_text}; use crate::{editor::Point, settings, test::sample_text};
use gpui::App;
use unindent::Unindent; use unindent::Unindent;
#[test] #[gpui::test]
fn test_selection_with_mouse() { fn test_selection_with_mouse(app: &mut gpui::MutableAppContext) {
App::test((), |app| { let buffer = app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx));
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 settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, buffer_view) = let (_, buffer_view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
buffer_view.update(app, |view, ctx| { buffer_view.update(app, |view, ctx| {
view.begin_selection(DisplayPoint::new(2, 2), false, ctx); view.begin_selection(DisplayPoint::new(2, 2), false, ctx);
@ -2628,14 +2619,11 @@ mod tests {
selections, selections,
[DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)] [DisplayPoint::new(3, 3)..DisplayPoint::new(0, 0)]
); );
});
} }
#[test] #[gpui::test]
fn test_canceling_pending_selection() { fn test_canceling_pending_selection(app: &mut gpui::MutableAppContext) {
App::test((), |app| { let buffer = app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx));
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 settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -2663,14 +2651,11 @@ mod tests {
view.read(app).selection_ranges(app.as_ref()), view.read(app).selection_ranges(app.as_ref()),
[DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
); );
});
} }
#[test] #[gpui::test]
fn test_cancel() { fn test_cancel(app: &mut gpui::MutableAppContext) {
App::test((), |app| { let buffer = app.add_model(|ctx| Buffer::new(0, "aaaaaa\nbbbbbb\ncccccc\ndddddd\n", ctx));
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 settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -2702,32 +2687,27 @@ mod tests {
view.read(app).selection_ranges(app.as_ref()), view.read(app).selection_ranges(app.as_ref()),
[DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)] [DisplayPoint::new(1, 1)..DisplayPoint::new(1, 1)]
); );
});
} }
#[test] #[gpui::test]
fn test_layout_line_numbers() { fn test_layout_line_numbers(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let layout_cache = TextLayoutCache::new(app.platform().fonts()); let layout_cache = TextLayoutCache::new(app.platform().fonts());
let font_cache = app.font_cache().clone(); 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 settings = settings::channel(&font_cache).unwrap().1;
let (_, view) = let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
let layouts = view let layouts = view
.read(app) .read(app)
.layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref()) .layout_line_numbers(1000.0, &font_cache, &layout_cache, app.as_ref())
.unwrap(); .unwrap();
assert_eq!(layouts.len(), 6); assert_eq!(layouts.len(), 6);
})
} }
#[test] #[gpui::test]
fn test_fold() { fn test_fold(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| { let buffer = app.add_model(|ctx| {
Buffer::new( Buffer::new(
0, 0,
@ -2753,14 +2733,10 @@ mod tests {
) )
}); });
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)],
ctx,
)
.unwrap(); .unwrap();
view.fold(&(), ctx); view.fold(&(), ctx);
assert_eq!( assert_eq!(
@ -2817,16 +2793,13 @@ mod tests {
view.unfold(&(), ctx); view.unfold(&(), ctx);
assert_eq!(view.text(ctx.as_ref()), buffer.read(ctx).text()); assert_eq!(view.text(ctx.as_ref()), buffer.read(ctx).text());
}); });
});
} }
#[test] #[gpui::test]
fn test_move_cursor() { fn test_move_cursor(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
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(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
buffer.update(app, |buffer, ctx| { buffer.update(app, |buffer, ctx| {
buffer buffer
@ -2878,10 +2851,7 @@ mod tests {
&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)] &[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
); );
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)], ctx)
&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)],
ctx,
)
.unwrap(); .unwrap();
view.select_to_beginning(&(), ctx); view.select_to_beginning(&(), ctx);
assert_eq!( assert_eq!(
@ -2895,12 +2865,10 @@ mod tests {
&[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)] &[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
); );
}); });
});
} }
#[test] #[gpui::test]
fn test_beginning_end_of_line() { fn test_beginning_end_of_line(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\n def", ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, "abc\n def", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -3023,14 +2991,12 @@ mod tests {
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0), DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
] ]
); );
});
} }
#[test] #[gpui::test]
fn test_prev_next_word_boundary() { fn test_prev_next_word_boundary(app: &mut gpui::MutableAppContext) {
App::test((), |app| { let buffer =
let buffer = app app.add_model(|ctx| Buffer::new(0, "use std::str::{foo, bar}\n\n {baz.qux()}", ctx));
.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 settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
@ -3205,12 +3171,10 @@ mod tests {
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2), DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
] ]
); );
});
} }
#[test] #[gpui::test]
fn test_backspace() { fn test_backspace(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| { let buffer = app.add_model(|ctx| {
Buffer::new( Buffer::new(
0, 0,
@ -3219,8 +3183,7 @@ mod tests {
) )
}); });
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(
@ -3242,12 +3205,10 @@ mod tests {
buffer.read(app).text(), buffer.read(app).text(),
"oe two three\nfou five six\nseven ten\n" "oe two three\nfou five six\nseven ten\n"
); );
})
} }
#[test] #[gpui::test]
fn test_delete() { fn test_delete(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| { let buffer = app.add_model(|ctx| {
Buffer::new( Buffer::new(
0, 0,
@ -3256,8 +3217,7 @@ mod tests {
) )
}); });
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(
@ -3279,12 +3239,10 @@ mod tests {
buffer.read(app).text(), buffer.read(app).text(),
"on two three\nfou five six\nseven ten\n" "on two three\nfou five six\nseven ten\n"
); );
})
} }
#[test] #[gpui::test]
fn test_delete_line() { fn test_delete_line(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().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 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)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -3313,10 +3271,7 @@ mod tests {
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, "abc\ndef\nghi\n", ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)], ctx)
&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)],
ctx,
)
.unwrap(); .unwrap();
view.delete_line(&(), ctx); view.delete_line(&(), ctx);
}); });
@ -3325,12 +3280,10 @@ mod tests {
view.read(app).selection_ranges(app.as_ref()), view.read(app).selection_ranges(app.as_ref()),
vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)] vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
); );
});
} }
#[test] #[gpui::test]
fn test_duplicate_line() { fn test_duplicate_line(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().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 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)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -3386,12 +3339,10 @@ mod tests {
DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1), DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
] ]
); );
});
} }
#[test] #[gpui::test]
fn test_move_line_up_down() { fn test_move_line_up_down(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(10, 5), ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(10, 5), ctx));
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -3479,12 +3430,10 @@ mod tests {
DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2) DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
] ]
); );
});
} }
#[test] #[gpui::test]
fn test_clipboard() { fn test_clipboard(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, "one two three four five six ", ctx)); 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 settings = settings::channel(&app.font_cache()).unwrap().1;
let view = app let view = app
@ -3586,10 +3535,7 @@ mod tests {
// Copy with a single cursor only, which writes the whole line into the clipboard. // Copy with a single cursor only, which writes the whole line into the clipboard.
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)], ctx)
&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)],
ctx,
)
.unwrap(); .unwrap();
view.copy(&(), ctx); view.copy(&(), ctx);
}); });
@ -3620,12 +3566,10 @@ mod tests {
DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1), DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1),
] ]
); );
});
} }
#[test] #[gpui::test]
fn test_select_all() { fn test_select_all(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, "abc\nde\nfgh", ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, "abc\nde\nfgh", ctx));
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -3634,12 +3578,10 @@ mod tests {
view.read(app).selection_ranges(app.as_ref()), view.read(app).selection_ranges(app.as_ref()),
&[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)] &[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
); );
});
} }
#[test] #[gpui::test]
fn test_select_line() { fn test_select_line(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(6, 5), ctx)); 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)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -3678,12 +3620,10 @@ mod tests {
view.read(app).selection_ranges(app.as_ref()), view.read(app).selection_ranges(app.as_ref()),
vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)] vec![DisplayPoint::new(0, 0)..DisplayPoint::new(5, 5)]
); );
});
} }
#[test] #[gpui::test]
fn test_split_selection_into_lines() { fn test_split_selection_into_lines(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(9, 5), ctx)); 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)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
@ -3728,10 +3668,7 @@ mod tests {
); );
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)], ctx)
&[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)],
ctx,
)
.unwrap(); .unwrap();
view.split_selection_into_lines(&(), ctx); view.split_selection_into_lines(&(), ctx);
}); });
@ -3752,21 +3689,16 @@ mod tests {
DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0) DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
] ]
); );
});
} }
#[test] #[gpui::test]
fn test_add_selection_above_below() { fn test_add_selection_above_below(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1; 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 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)); let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer, settings, ctx));
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)], ctx)
&[DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)],
ctx,
)
.unwrap(); .unwrap();
}); });
view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
@ -3812,10 +3744,7 @@ mod tests {
); );
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)], ctx)
&[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)],
ctx,
)
.unwrap(); .unwrap();
}); });
view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
@ -3849,10 +3778,7 @@ mod tests {
); );
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)], ctx)
&[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)],
ctx,
)
.unwrap(); .unwrap();
}); });
view.update(app, |view, ctx| view.add_selection_below(&(), ctx)); view.update(app, |view, ctx| view.add_selection_below(&(), ctx));
@ -3887,10 +3813,7 @@ mod tests {
); );
view.update(app, |view, ctx| { view.update(app, |view, ctx| {
view.select_display_ranges( view.select_display_ranges(&[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)], ctx)
&[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)],
ctx,
)
.unwrap(); .unwrap();
}); });
view.update(app, |view, ctx| view.add_selection_above(&(), ctx)); view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
@ -3913,7 +3836,6 @@ mod tests {
DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1), DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
] ]
); );
});
} }
impl BufferView { impl BufferView {

View file

@ -671,11 +671,9 @@ mod tests {
use super::*; use super::*;
use crate::test::sample_text; use crate::test::sample_text;
use buffer::ToPoint; use buffer::ToPoint;
use gpui::App;
#[test] #[gpui::test]
fn test_basic_folds() { fn test_basic_folds(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); 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 mut map = FoldMap::new(buffer.clone(), app.as_ref());
@ -715,12 +713,10 @@ mod tests {
map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), app.as_ref()) map.unfold(Some(Point::new(0, 4)..Point::new(0, 5)), app.as_ref())
.unwrap(); .unwrap();
assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee"); assert_eq!(map.text(app.as_ref()), "123aaaaa\nbbbbbb\nccc123456eee");
});
} }
#[test] #[gpui::test]
fn test_adjacent_folds() { fn test_adjacent_folds(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
{ {
@ -758,12 +754,10 @@ mod tests {
map.check_invariants(app.as_ref()); map.check_invariants(app.as_ref());
assert_eq!(map.text(app.as_ref()), "12345…fghijkl"); assert_eq!(map.text(app.as_ref()), "12345…fghijkl");
} }
});
} }
#[test] #[gpui::test]
fn test_overlapping_folds() { fn test_overlapping_folds(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); 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 mut map = FoldMap::new(buffer.clone(), app.as_ref());
map.fold( map.fold(
@ -777,12 +771,10 @@ mod tests {
) )
.unwrap(); .unwrap();
assert_eq!(map.text(app.as_ref()), "aa…eeeee"); assert_eq!(map.text(app.as_ref()), "aa…eeeee");
})
} }
#[test] #[gpui::test]
fn test_merging_folds_via_edit() { fn test_merging_folds_via_edit(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); 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 mut map = FoldMap::new(buffer.clone(), app.as_ref());
@ -802,12 +794,10 @@ mod tests {
.unwrap(); .unwrap();
}); });
assert_eq!(map.text(app.as_ref()), "aa…eeeee"); assert_eq!(map.text(app.as_ref()), "aa…eeeee");
});
} }
#[test] #[gpui::test]
fn test_folds_in_range() { fn test_folds_in_range(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, sample_text(5, 6), ctx)); 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 mut map = FoldMap::new(buffer.clone(), app.as_ref());
let buffer = buffer.read(app); let buffer = buffer.read(app);
@ -825,9 +815,7 @@ mod tests {
let fold_ranges = map let fold_ranges = map
.folds_in_range(Point::new(1, 0)..Point::new(1, 3), app.as_ref()) .folds_in_range(Point::new(1, 0)..Point::new(1, 3), app.as_ref())
.unwrap() .unwrap()
.map(|fold| { .map(|fold| fold.start.to_point(buffer).unwrap()..fold.end.to_point(buffer).unwrap())
fold.start.to_point(buffer).unwrap()..fold.end.to_point(buffer).unwrap()
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!( assert_eq!(
fold_ranges, fold_ranges,
@ -836,11 +824,10 @@ mod tests {
Point::new(1, 2)..Point::new(3, 2) Point::new(1, 2)..Point::new(3, 2)
] ]
); );
});
} }
#[test] #[gpui::test]
fn test_random_folds() { fn test_random_folds(app: &mut gpui::MutableAppContext) {
use crate::editor::ToPoint; use crate::editor::ToPoint;
use crate::util::RandomCharIter; use crate::util::RandomCharIter;
use rand::prelude::*; use rand::prelude::*;
@ -863,7 +850,6 @@ mod tests {
dbg!(seed); dbg!(seed);
let mut rng = StdRng::seed_from_u64(seed); let mut rng = StdRng::seed_from_u64(seed);
App::test((), |app| {
let buffer = app.add_model(|ctx| { let buffer = app.add_model(|ctx| {
let len = rng.gen_range(0..10); let len = rng.gen_range(0..10);
let text = RandomCharIter::new(&mut rng).take(len).collect::<String>(); let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
@ -991,10 +977,8 @@ mod tests {
} }
for fold_range in map.merged_fold_ranges(app.as_ref()) { for fold_range in map.merged_fold_ranges(app.as_ref()) {
let display_point = map.to_display_point( let display_point = map
fold_range.start.to_point(buffer).unwrap(), .to_display_point(fold_range.start.to_point(buffer).unwrap(), app.as_ref());
app.as_ref(),
);
assert!(map.is_line_folded(display_point.row(), app.as_ref())); assert!(map.is_line_folded(display_point.row(), app.as_ref()));
} }
@ -1023,13 +1007,11 @@ mod tests {
); );
} }
} }
});
} }
} }
#[test] #[gpui::test]
fn test_buffer_rows() { fn test_buffer_rows(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let text = sample_text(6, 6) + "\n"; let text = sample_text(6, 6) + "\n";
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
@ -1059,7 +1041,6 @@ mod tests {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
vec![6] vec![6]
); );
});
} }
impl FoldMap { impl FoldMap {

View file

@ -339,11 +339,9 @@ pub fn collapse_tabs(
mod tests { mod tests {
use super::*; use super::*;
use crate::test::*; use crate::test::*;
use gpui::App;
#[test] #[gpui::test]
fn test_chars_at() { fn test_chars_at(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let text = sample_text(6, 6); let text = sample_text(6, 6);
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref()); let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
@ -385,7 +383,6 @@ mod tests {
.collect::<String>(), .collect::<String>(),
" bbbbb\nc c" " bbbbb\nc c"
); );
});
} }
#[test] #[test]
@ -411,12 +408,10 @@ mod tests {
assert_eq!(collapse_tabs("\ta".chars(), 5, Bias::Right, 4), (2, 0)); assert_eq!(collapse_tabs("\ta".chars(), 5, Bias::Right, 4), (2, 0));
} }
#[test] #[gpui::test]
fn test_max_point() { fn test_max_point(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx)); let buffer = app.add_model(|ctx| Buffer::new(0, "aaa\n\t\tbbb", ctx));
let map = DisplayMap::new(buffer.clone(), 4, app.as_ref()); let map = DisplayMap::new(buffer.clone(), 4, app.as_ref());
assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11)) assert_eq!(map.max_point(app.as_ref()), DisplayPoint::new(1, 11))
});
} }
} }

View file

@ -399,7 +399,7 @@ impl FileFinder {
self.cancel_flag.store(true, atomic::Ordering::Relaxed); self.cancel_flag.store(true, atomic::Ordering::Relaxed);
self.cancel_flag = Arc::new(AtomicBool::new(false)); self.cancel_flag = Arc::new(AtomicBool::new(false));
let cancel_flag = self.cancel_flag.clone(); 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 include_root_name = snapshots.len() > 1;
let matches = match_paths( let matches = match_paths(
snapshots.iter(), snapshots.iter(),
@ -415,7 +415,11 @@ impl FileFinder {
(search_id, did_cancel, query, matches) (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(()) Some(())
} }
@ -453,14 +457,12 @@ impl FileFinder {
mod tests { mod tests {
use super::*; use super::*;
use crate::{editor, settings, test::temp_tree, workspace::Workspace}; use crate::{editor, settings, test::temp_tree, workspace::Workspace};
use gpui::App;
use serde_json::json; use serde_json::json;
use std::fs; use std::fs;
use tempdir::TempDir; use tempdir::TempDir;
#[test] #[gpui::test]
fn test_matching_paths() { async fn test_matching_paths(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let tmp_dir = TempDir::new("example").unwrap(); let tmp_dir = TempDir::new("example").unwrap();
fs::create_dir(tmp_dir.path().join("a")).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/banana"), "banana").unwrap();
@ -524,12 +526,10 @@ mod tests {
let active_item = active_pane.read(ctx).active_item().unwrap(); let active_item = active_pane.read(ctx).active_item().unwrap();
assert_eq!(active_item.title(ctx), "bandana"); assert_eq!(active_item.title(ctx), "bandana");
}); });
});
} }
#[test] #[gpui::test]
fn test_matching_cancellation() { async fn test_matching_cancellation(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let tmp_dir = temp_tree(json!({ let tmp_dir = temp_tree(json!({
"hello": "", "hello": "",
"goodbye": "", "goodbye": "",
@ -547,8 +547,7 @@ mod tests {
}); });
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await; .await;
let (_, finder) = let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
let query = "hi".to_string(); let query = "hi".to_string();
finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx)); finder.update(&mut app, |f, ctx| f.spawn_search(query.clone(), ctx));
@ -584,12 +583,10 @@ mod tests {
assert_eq!(finder.matches, matches[0..4]) assert_eq!(finder.matches, matches[0..4])
}); });
});
} }
#[test] #[gpui::test]
fn test_single_file_worktrees() { async fn test_single_file_worktrees(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let temp_dir = TempDir::new("test-single-file-worktrees").unwrap(); let temp_dir = TempDir::new("test-single-file-worktrees").unwrap();
let dir_path = temp_dir.path().join("the-parent-dir"); let dir_path = temp_dir.path().join("the-parent-dir");
let file_path = dir_path.join("the-file"); let file_path = dir_path.join("the-file");
@ -604,8 +601,7 @@ mod tests {
}); });
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await; .await;
let (_, finder) = let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
// Even though there is only one worktree, that worktree's filename // Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file. // is included in the matching, because the worktree is a single file.
@ -627,12 +623,10 @@ mod tests {
// not match anything. // not match anything.
finder.update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx)); finder.update(&mut app, |f, ctx| f.spawn_search("thf/".into(), ctx));
finder.condition(&app, |f, _| f.matches.len() == 0).await; finder.condition(&app, |f, _| f.matches.len() == 0).await;
});
} }
#[test] #[gpui::test]
fn test_multiple_matches_with_same_relative_path() { async fn test_multiple_matches_with_same_relative_path(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let tmp_dir = temp_tree(json!({ let tmp_dir = temp_tree(json!({
"dir1": { "a.txt": "" }, "dir1": { "a.txt": "" },
"dir2": { "a.txt": "" } "dir2": { "a.txt": "" }
@ -652,8 +646,7 @@ mod tests {
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
.await; .await;
let (_, finder) = let (_, finder) = app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
// Run a search that matches two files with the same relative path. // 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.update(&mut app, |f, ctx| f.spawn_search("a.t".into(), ctx));
@ -667,6 +660,5 @@ mod tests {
f.select_prev(&(), ctx); f.select_prev(&(), ctx);
assert_eq!(f.selected_index(), 0); assert_eq!(f.selected_index(), 0);
}); });
});
} }
} }

View file

@ -6,11 +6,11 @@ use crate::{
time::ReplicaId, time::ReplicaId,
worktree::{FileHandle, Worktree, WorktreeHandle}, worktree::{FileHandle, Worktree, WorktreeHandle},
}; };
use futures_core::{future::LocalBoxFuture, Future}; use futures_core::Future;
use gpui::{ use gpui::{
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext, color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, PathPromptOptions, ClipboardItem, Entity, ModelHandle, MutableAppContext, PathPromptOptions, PromptLevel, Task,
PromptLevel, View, ViewContext, ViewHandle, WeakModelHandle, View, ViewContext, ViewHandle, WeakModelHandle,
}; };
use log::error; use log::error;
pub use pane::*; pub use pane::*;
@ -126,7 +126,7 @@ pub trait ItemView: View {
&mut self, &mut self,
_: Option<FileHandle>, _: Option<FileHandle>,
_: &mut ViewContext<Self>, _: &mut ViewContext<Self>,
) -> LocalBoxFuture<'static, anyhow::Result<()>>; ) -> Task<anyhow::Result<()>>;
fn should_activate_item_on_event(_: &Self::Event) -> bool { fn should_activate_item_on_event(_: &Self::Event) -> bool {
false false
} }
@ -165,7 +165,7 @@ pub trait ItemViewHandle: Send + Sync {
&self, &self,
file: Option<FileHandle>, file: Option<FileHandle>,
ctx: &mut MutableAppContext, ctx: &mut MutableAppContext,
) -> LocalBoxFuture<'static, anyhow::Result<()>>; ) -> Task<anyhow::Result<()>>;
} }
impl<T: Item> ItemHandle for ModelHandle<T> { impl<T: Item> ItemHandle for ModelHandle<T> {
@ -243,7 +243,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
&self, &self,
file: Option<FileHandle>, file: Option<FileHandle>,
ctx: &mut MutableAppContext, ctx: &mut MutableAppContext,
) -> LocalBoxFuture<'static, anyhow::Result<()>> { ) -> Task<anyhow::Result<()>> {
self.update(ctx, |item, ctx| item.save(file, ctx)) self.update(ctx, |item, ctx| item.save(file, ctx))
} }
@ -367,16 +367,17 @@ impl Workspace {
.cloned() .cloned()
.zip(entries.into_iter()) .zip(entries.into_iter())
.map(|(abs_path, file)| { .map(|(abs_path, file)| {
ctx.spawn( let is_file = bg.spawn(async move { abs_path.is_file() });
bg.spawn(async move { abs_path.is_file() }), ctx.spawn(|this, mut ctx| async move {
move |me, is_file, ctx| { let is_file = is_file.await;
this.update(&mut ctx, |this, ctx| {
if is_file { if is_file {
me.open_entry(file.entry_id(), ctx) this.open_entry(file.entry_id(), ctx)
} else { } else {
None None
} }
}, })
) })
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
async move { async move {
@ -450,7 +451,7 @@ impl Workspace {
&mut self, &mut self,
entry: (usize, Arc<Path>), entry: (usize, Arc<Path>),
ctx: &mut ViewContext<Self>, ctx: &mut ViewContext<Self>,
) -> Option<EntityTask<()>> { ) -> Option<Task<()>> {
// If the active pane contains a view for this file, then activate // If the active pane contains a view for this file, then activate
// that item view. // that item view.
if self if self
@ -504,44 +505,46 @@ impl Workspace {
let history = ctx let history = ctx
.background_executor() .background_executor()
.spawn(file.load_history(ctx.as_ref())); .spawn(file.load_history(ctx.as_ref()));
ctx.spawn(history, move |_, history, ctx| {
*tx.borrow_mut() = Some(match history { ctx.as_mut()
.spawn(|mut ctx| async move {
*tx.borrow_mut() = Some(match history.await {
Ok(history) => Ok(Box::new(ctx.add_model(|ctx| { Ok(history) => Ok(Box::new(ctx.add_model(|ctx| {
Buffer::from_history(replica_id, history, Some(file), ctx) Buffer::from_history(replica_id, history, Some(file), ctx)
}))), }))),
Err(error) => Err(Arc::new(error)), Err(error) => Err(Arc::new(error)),
}) })
}) })
.detach() .detach();
} }
let mut watch = self.loading_items.get(&entry).unwrap().clone(); let mut watch = self.loading_items.get(&entry).unwrap().clone();
Some(ctx.spawn(
async move { Some(ctx.spawn(|this, mut ctx| async move {
loop { let load_result = loop {
if let Some(load_result) = watch.borrow().as_ref() { if let Some(load_result) = watch.borrow().as_ref() {
return load_result.clone(); break load_result.clone();
} }
watch.next().await; watch.next().await;
} };
},
move |me, load_result, ctx| { this.update(&mut ctx, |this, ctx| {
me.loading_items.remove(&entry); this.loading_items.remove(&entry);
match load_result { match load_result {
Ok(item) => { Ok(item) => {
let weak_item = item.downgrade(); let weak_item = item.downgrade();
let view = weak_item let view = weak_item
.add_view(ctx.window_id(), settings, ctx.as_mut()) .add_view(ctx.window_id(), settings, ctx.as_mut())
.unwrap(); .unwrap();
me.items.push(weak_item); this.items.push(weak_item);
me.add_item_view(view, ctx); this.add_item_view(view, ctx);
} }
Err(error) => { Err(error) => {
log::error!("error opening item: {}", error); log::error!("error opening item: {}", error);
} }
} }
}, })
)) }))
} }
pub fn active_item(&self, ctx: &ViewContext<Self>) -> Option<Box<dyn ItemViewHandle>> { 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>) { pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
if let Some(item) = self.active_item(ctx) { if let Some(item) = self.active_item(ctx) {
if item.entry_id(ctx.as_ref()).is_none() {
let handle = ctx.handle(); let handle = ctx.handle();
if item.entry_id(ctx.as_ref()).is_none() {
let start_path = self let start_path = self
.worktrees .worktrees
.iter() .iter()
@ -560,46 +563,39 @@ impl Workspace {
.to_path_buf(); .to_path_buf();
ctx.prompt_for_new_path(&start_path, move |path, ctx| { ctx.prompt_for_new_path(&start_path, move |path, ctx| {
if let Some(path) = path { if let Some(path) = path {
handle.update(ctx, move |this, ctx| { ctx.spawn(|mut ctx| async move {
let file = this.file_for_path(&path, ctx); let file =
let task = item.save(Some(file), ctx.as_mut()); handle.update(&mut ctx, |me, ctx| me.file_for_path(&path, ctx));
ctx.spawn(task, move |_, result, _| { if let Err(error) = ctx.update(|ctx| item.save(Some(file), ctx)).await {
if let Err(e) = result { error!("failed to save item: {:?}, ", error);
error!("failed to save item: {:?}, ", e);
} }
}) })
.detach() .detach()
})
} }
}); });
return; return;
} else if item.has_conflict(ctx.as_ref()) { } 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?"; 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( ctx.prompt(
PromptLevel::Warning, PromptLevel::Warning,
CONFLICT_MESSAGE, CONFLICT_MESSAGE,
&["Overwrite", "Cancel"], &["Overwrite", "Cancel"],
move |answer, ctx| { move |answer, ctx| {
if answer == 0 { if answer == 0 {
handle.update(ctx, move |_, ctx| { ctx.spawn(|mut ctx| async move {
let task = item.save(None, ctx.as_mut()); if let Err(error) = ctx.update(|ctx| item.save(None, ctx)).await {
ctx.spawn(task, |_, result, _| { error!("failed to save item: {:?}, ", error);
if let Err(e) = result {
error!("failed to save item: {:?}, ", e);
} }
}) })
.detach(); .detach();
});
} }
}, },
); );
} else { } else {
let task = item.save(None, ctx.as_mut()); ctx.spawn(|_, mut ctx| async move {
ctx.spawn(task, |_, result, _| { if let Err(error) = ctx.update(|ctx| item.save(None, ctx)).await {
if let Err(e) = result { error!("failed to save item: {:?}, ", error);
error!("failed to save item: {:?}, ", e);
} }
}) })
.detach(); .detach();
@ -759,14 +755,12 @@ impl WorkspaceHandle for ViewHandle<Workspace> {
mod tests { mod tests {
use super::*; use super::*;
use crate::{editor::BufferView, settings, test::temp_tree}; use crate::{editor::BufferView, settings, test::temp_tree};
use gpui::App;
use serde_json::json; use serde_json::json;
use std::{collections::HashSet, fs}; use std::{collections::HashSet, fs};
use tempdir::TempDir; use tempdir::TempDir;
#[test] #[gpui::test]
fn test_open_paths_action() { fn test_open_paths_action(app: &mut gpui::MutableAppContext) {
App::test((), |app| {
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
init(app); init(app);
@ -822,12 +816,10 @@ mod tests {
}, },
); );
assert_eq!(app.window_ids().count(), 2); assert_eq!(app.window_ids().count(), 2);
});
} }
#[test] #[gpui::test]
fn test_open_entry() { async fn test_open_entry(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"a": { "a": {
"file1": "contents 1", "file1": "contents 1",
@ -929,12 +921,10 @@ mod tests {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]); assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]);
}); });
});
} }
#[test] #[gpui::test]
fn test_open_paths() { async fn test_open_paths(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir1 = temp_tree(json!({ let dir1 = temp_tree(json!({
"a.txt": "", "a.txt": "",
})); }));
@ -1002,12 +992,59 @@ mod tests {
"b.txt" "b.txt"
); );
}); });
}
#[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;
app.read(|ctx| {
assert!(!editor.is_dirty(ctx));
assert!(!editor.has_conflict(ctx));
}); });
} }
#[test] #[gpui::test]
fn test_open_and_save_new_file() { async fn test_open_and_save_new_file(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir = TempDir::new("test-new-file").unwrap(); let dir = TempDir::new("test-new-file").unwrap();
let settings = settings::channel(&app.font_cache()).unwrap().1; let settings = settings::channel(&app.font_cache()).unwrap().1;
let (_, workspace) = app.add_window(|ctx| { let (_, workspace) = app.add_window(|ctx| {
@ -1098,65 +1135,10 @@ mod tests {
app.read(|ctx| { app.read(|ctx| {
assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer()); assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer());
}) })
});
} }
#[test] #[gpui::test]
fn test_save_conflicting_item() { async fn test_pane_actions(mut app: gpui::TestAppContext) {
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)
})
})
.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));
});
});
}
#[test]
fn test_pane_actions() {
App::test_async((), |mut app| async move {
app.update(|ctx| pane::init(ctx)); app.update(|ctx| pane::init(ctx));
let dir = temp_tree(json!({ let dir = temp_tree(json!({
@ -1204,6 +1186,5 @@ mod tests {
assert_eq!(workspace_view.panes.len(), 1); assert_eq!(workspace_view.panes.len(), 1);
assert_eq!(workspace_view.active_pane(), &pane_1); assert_eq!(workspace_view.active_pane(), &pane_1);
}); });
});
} }
} }

View file

@ -16,7 +16,7 @@ use postage::{
prelude::{Sink, Stream}, prelude::{Sink, Stream},
watch, watch,
}; };
use smol::{channel::Sender, Timer}; use smol::channel::Sender;
use std::{ use std::{
cmp, cmp,
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
@ -99,7 +99,26 @@ impl Worktree {
scanner.run(event_stream) scanner.run(event_stream)
}); });
ctx.spawn_stream(scan_state_rx, Self::observe_scan_state, |_, _| {}) 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(); .detach();
tree tree
@ -117,15 +136,16 @@ impl Worktree {
pub fn next_scan_complete(&self, ctx: &mut ModelContext<Self>) -> impl Future<Output = ()> { pub fn next_scan_complete(&self, ctx: &mut ModelContext<Self>) -> impl Future<Output = ()> {
let scan_id = self.snapshot.scan_id; let scan_id = self.snapshot.scan_id;
ctx.spawn_stream( let mut scan_state = self.scan_state.1.clone();
self.scan_state.1.clone(), ctx.spawn(|this, ctx| async move {
move |this, scan_state, ctx| { while let Some(scan_state) = scan_state.recv().await {
if matches!(scan_state, ScanState::Idle) && this.snapshot.scan_id > scan_id { if this.read_with(&ctx, |this, _| {
ctx.halt_stream(); 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>) { fn observe_scan_state(&mut self, scan_state: ScanState, ctx: &mut ModelContext<Self>) {
@ -138,10 +158,12 @@ impl Worktree {
ctx.notify(); ctx.notify();
if self.is_scanning() && !self.poll_scheduled { if self.is_scanning() && !self.poll_scheduled {
ctx.spawn(Timer::after(Duration::from_millis(100)), |this, _, ctx| { ctx.spawn(|this, mut ctx| async move {
this.update(&mut ctx, |this, ctx| {
this.poll_scheduled = false; this.poll_scheduled = false;
this.poll_entries(ctx); this.poll_entries(ctx);
}) })
})
.detach(); .detach();
self.poll_scheduled = true; self.poll_scheduled = true;
} }
@ -1394,7 +1416,6 @@ mod tests {
use crate::editor::Buffer; use crate::editor::Buffer;
use crate::test::*; use crate::test::*;
use anyhow::Result; use anyhow::Result;
use gpui::App;
use rand::prelude::*; use rand::prelude::*;
use serde_json::json; use serde_json::json;
use std::env; use std::env;
@ -1402,9 +1423,8 @@ mod tests {
use std::os::unix; use std::os::unix;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
#[test] #[gpui::test]
fn test_populate_and_search() { async fn test_populate_and_search(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"root": { "root": {
"apple": "", "apple": "",
@ -1461,12 +1481,10 @@ mod tests {
] ]
); );
}) })
});
} }
#[test] #[gpui::test]
fn test_save_file() { async fn test_save_file(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"file1": "the old contents", "file1": "the old contents",
})); }));
@ -1481,8 +1499,7 @@ mod tests {
let path = tree.update(&mut app, |tree, ctx| { let path = tree.update(&mut app, |tree, ctx| {
let path = tree.files(0).next().unwrap().path().clone(); let path = tree.files(0).next().unwrap().path().clone();
assert_eq!(path.file_name().unwrap(), "file1"); assert_eq!(path.file_name().unwrap(), "file1");
smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())) smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
.unwrap();
path path
}); });
@ -1493,12 +1510,10 @@ mod tests {
app.read(|ctx| { app.read(|ctx| {
assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text()); assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text());
}); });
});
} }
#[test] #[gpui::test]
fn test_save_in_single_file_worktree() { async fn test_save_in_single_file_worktree(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"file1": "the old contents", "file1": "the old contents",
})); }));
@ -1518,12 +1533,10 @@ mod tests {
let history = app.read(|ctx| file.load_history(ctx)).await.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())); app.read(|ctx| assert_eq!(history.base_text.as_ref(), buffer.read(ctx).text()));
});
} }
#[test] #[gpui::test]
fn test_rescan_simple() { async fn test_rescan_simple(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
"a": { "a": {
"file1": "", "file1": "",
@ -1566,10 +1579,10 @@ mod tests {
assert!(non_existent_file.is_deleted()); assert!(non_existent_file.is_deleted());
tree.flush_fs_events(&app).await; tree.flush_fs_events(&app).await;
fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap(); std::fs::rename(dir.path().join("a/file3"), dir.path().join("b/c/file3")).unwrap();
fs::remove_file(dir.path().join("b/c/file5")).unwrap(); std::fs::remove_file(dir.path().join("b/c/file5")).unwrap();
fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap(); std::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(); 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)) tree.update(&mut app, |tree, ctx| tree.next_scan_complete(ctx))
.await; .await;
@ -1603,12 +1616,10 @@ mod tests {
assert_eq!(file3.path().as_ref(), Path::new("a/file3")); assert_eq!(file3.path().as_ref(), Path::new("a/file3"));
assert!(file3.is_deleted()); assert!(file3.is_deleted());
}); });
});
} }
#[test] #[gpui::test]
fn test_rescan_with_gitignore() { async fn test_rescan_with_gitignore(mut app: gpui::TestAppContext) {
App::test_async((), |mut app| async move {
let dir = temp_tree(json!({ let dir = temp_tree(json!({
".git": {}, ".git": {},
".gitignore": "ignored-dir\n", ".gitignore": "ignored-dir\n",
@ -1644,7 +1655,6 @@ mod tests {
assert_eq!(ignored.is_ignored(), true); assert_eq!(ignored.is_ignored(), true);
assert_eq!(dot_git.is_ignored(), true); assert_eq!(dot_git.is_ignored(), true);
}); });
});
} }
#[test] #[test]