Merge branch 'master' into file-changed-on-disk
This commit is contained in:
commit
4910bc50c6
14 changed files with 3718 additions and 4119 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -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"
|
||||
|
|
718
gpui/src/app.rs
718
gpui/src/app.rs
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||
|
|
11
gpui_macros/Cargo.toml
Normal file
11
gpui_macros/Cargo.toml
Normal 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
57
gpui_macros/src/lib.rs
Normal 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))
|
||||
}
|
|
@ -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,24 +383,28 @@ 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() {
|
||||
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)
|
||||
})
|
||||
.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()
|
||||
result
|
||||
} else {
|
||||
async { Ok(()) }.boxed_local()
|
||||
Ok(())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn did_save(
|
||||
|
@ -536,11 +539,13 @@ 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({
|
||||
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())
|
||||
|
@ -548,8 +553,9 @@ impl Buffer {
|
|||
.map(|c| (c.tag(), c.value().len()))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}),
|
||||
move |this, diff, ctx| {
|
||||
})
|
||||
.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,9 +2486,8 @@ mod tests {
|
|||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_edit() {
|
||||
App::test((), |ctx| {
|
||||
#[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");
|
||||
|
@ -2498,12 +2503,10 @@ mod tests {
|
|||
assert_eq!(buffer.text(), "ghiamnoef");
|
||||
buffer
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_events() {
|
||||
App::test((), |app| {
|
||||
#[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()));
|
||||
|
@ -2558,13 +2561,11 @@ mod tests {
|
|||
|
||||
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);
|
||||
|
||||
|
@ -2638,8 +2639,7 @@ mod tests {
|
|||
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();
|
||||
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();
|
||||
|
@ -2650,14 +2650,12 @@ mod tests {
|
|||
}
|
||||
|
||||
buffer
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_len() {
|
||||
App::test((), |ctx| {
|
||||
#[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();
|
||||
|
@ -2674,12 +2672,10 @@ mod tests {
|
|||
assert!(buffer.line_len(6).is_err());
|
||||
buffer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rightmost_point() {
|
||||
App::test((), |ctx| {
|
||||
#[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);
|
||||
|
@ -2695,12 +2691,10 @@ mod tests {
|
|||
assert_eq!(buffer.rightmost_point().row, 4);
|
||||
buffer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text_summary_for_range() {
|
||||
App::test((), |ctx| {
|
||||
#[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());
|
||||
|
@ -2726,12 +2720,10 @@ mod tests {
|
|||
);
|
||||
buffer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chars_at() {
|
||||
App::test((), |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();
|
||||
|
@ -2764,7 +2756,6 @@ mod tests {
|
|||
|
||||
buffer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// #[test]
|
||||
|
@ -2881,9 +2872,8 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anchors() {
|
||||
App::test((), |ctx| {
|
||||
#[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();
|
||||
|
@ -3043,12 +3033,10 @@ mod tests {
|
|||
);
|
||||
buffer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_anchors_at_start_and_end() {
|
||||
App::test((), |ctx| {
|
||||
#[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();
|
||||
|
@ -3071,7 +3059,6 @@ mod tests {
|
|||
assert_eq!(after_end_anchor.to_offset(&buffer).unwrap(), 9);
|
||||
buffer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -3190,9 +3177,8 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_changes_on_disk() {
|
||||
App::test_async((), |mut app| async move {
|
||||
#[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));
|
||||
|
@ -3279,12 +3265,10 @@ mod tests {
|
|||
buffer
|
||||
.condition(&app, |buffer, _| buffer.has_conflict())
|
||||
.await;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_text_via_diff() {
|
||||
App::test_async((), |mut app| async move {
|
||||
#[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));
|
||||
|
||||
|
@ -3299,12 +3283,10 @@ mod tests {
|
|||
.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| {
|
||||
#[gpui::test]
|
||||
fn test_undo_redo(app: &mut gpui::MutableAppContext) {
|
||||
app.add_model(|ctx| {
|
||||
let mut buffer = Buffer::new(0, "1234", ctx);
|
||||
|
||||
|
@ -3336,18 +3318,16 @@ mod tests {
|
|||
|
||||
buffer
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history() {
|
||||
App::test((), |app| {
|
||||
#[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);
|
||||
|
||||
let (set_id, _) = buffer
|
||||
.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), None);
|
||||
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();
|
||||
|
@ -3406,11 +3386,10 @@ mod tests {
|
|||
|
||||
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;
|
||||
|
@ -3419,7 +3398,6 @@ mod tests {
|
|||
println!("{:?}", 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 = RandomCharIter::new(&mut rng)
|
||||
.take(base_text_len)
|
||||
|
@ -3478,7 +3456,6 @@ mod tests {
|
|||
.collect::<HashMap<_, _>>()
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
ctx.spawn(|this, mut ctx| async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
epoch
|
||||
},
|
||||
Self::resume_cursor_blinking,
|
||||
)
|
||||
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 {
|
||||
ctx.spawn(|this, mut ctx| async move {
|
||||
Timer::after(CURSOR_BLINK_INTERVAL).await;
|
||||
epoch
|
||||
},
|
||||
Self::blink_cursors,
|
||||
)
|
||||
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,17 +2511,13 @@ 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));
|
||||
#[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 (_, 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);
|
||||
|
@ -2628,14 +2619,11 @@ mod tests {
|
|||
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));
|
||||
#[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));
|
||||
|
||||
|
@ -2663,14 +2651,11 @@ mod tests {
|
|||
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));
|
||||
#[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));
|
||||
|
||||
|
@ -2702,32 +2687,27 @@ mod tests {
|
|||
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| {
|
||||
#[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 settings = settings::channel(&font_cache).unwrap().1;
|
||||
let (_, view) =
|
||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
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);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold() {
|
||||
App::test((), |app| {
|
||||
#[gpui::test]
|
||||
fn test_fold(app: &mut gpui::MutableAppContext) {
|
||||
let buffer = app.add_model(|ctx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
|
@ -2753,14 +2733,10 @@ mod tests {
|
|||
)
|
||||
});
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, view) =
|
||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
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,
|
||||
)
|
||||
view.select_display_ranges(&[DisplayPoint::new(8, 0)..DisplayPoint::new(12, 0)], ctx)
|
||||
.unwrap();
|
||||
view.fold(&(), ctx);
|
||||
assert_eq!(
|
||||
|
@ -2817,16 +2793,13 @@ mod tests {
|
|||
view.unfold(&(), ctx);
|
||||
assert_eq!(view.text(ctx.as_ref()), buffer.read(ctx).text());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_cursor() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
|
||||
buffer.update(app, |buffer, ctx| {
|
||||
buffer
|
||||
|
@ -2878,10 +2851,7 @@ mod tests {
|
|||
&[DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)]
|
||||
);
|
||||
|
||||
view.select_display_ranges(
|
||||
&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)],
|
||||
ctx,
|
||||
)
|
||||
view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 2)], ctx)
|
||||
.unwrap();
|
||||
view.select_to_beginning(&(), ctx);
|
||||
assert_eq!(
|
||||
|
@ -2895,12 +2865,10 @@ mod tests {
|
|||
&[DisplayPoint::new(0, 1)..DisplayPoint::new(5, 6)]
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_beginning_end_of_line() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
|
@ -3023,14 +2991,12 @@ mod tests {
|
|||
DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[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));
|
||||
#[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| {
|
||||
|
@ -3205,12 +3171,10 @@ mod tests {
|
|||
DisplayPoint::new(2, 2)..DisplayPoint::new(2, 2),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_backspace() {
|
||||
App::test((), |app| {
|
||||
#[gpui::test]
|
||||
fn test_backspace(app: &mut gpui::MutableAppContext) {
|
||||
let buffer = app.add_model(|ctx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
|
@ -3219,8 +3183,7 @@ mod tests {
|
|||
)
|
||||
});
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, view) =
|
||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
|
@ -3242,12 +3205,10 @@ mod tests {
|
|||
buffer.read(app).text(),
|
||||
"oe two three\nfou five six\nseven ten\n"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete() {
|
||||
App::test((), |app| {
|
||||
#[gpui::test]
|
||||
fn test_delete(app: &mut gpui::MutableAppContext) {
|
||||
let buffer = app.add_model(|ctx| {
|
||||
Buffer::new(
|
||||
0,
|
||||
|
@ -3256,8 +3217,7 @@ mod tests {
|
|||
)
|
||||
});
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
let (_, view) =
|
||||
app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
let (_, view) = app.add_window(|ctx| BufferView::for_buffer(buffer.clone(), settings, ctx));
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
|
@ -3279,12 +3239,10 @@ mod tests {
|
|||
buffer.read(app).text(),
|
||||
"on two three\nfou five six\nseven ten\n"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_line() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
|
@ -3313,10 +3271,7 @@ mod tests {
|
|||
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,
|
||||
)
|
||||
view.select_display_ranges(&[DisplayPoint::new(2, 0)..DisplayPoint::new(0, 1)], ctx)
|
||||
.unwrap();
|
||||
view.delete_line(&(), ctx);
|
||||
});
|
||||
|
@ -3325,12 +3280,10 @@ mod tests {
|
|||
view.read(app).selection_ranges(app.as_ref()),
|
||||
vec![DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_line() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
|
@ -3386,12 +3339,10 @@ mod tests {
|
|||
DisplayPoint::new(4, 2)..DisplayPoint::new(5, 1),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_line_up_down() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
|
@ -3479,12 +3430,10 @@ mod tests {
|
|||
DisplayPoint::new(4, 0)..DisplayPoint::new(4, 2)
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clipboard() {
|
||||
App::test((), |app| {
|
||||
#[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
|
||||
|
@ -3586,10 +3535,7 @@ mod tests {
|
|||
|
||||
// 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,
|
||||
)
|
||||
view.select_display_ranges(&[DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)], ctx)
|
||||
.unwrap();
|
||||
view.copy(&(), ctx);
|
||||
});
|
||||
|
@ -3620,12 +3566,10 @@ mod tests {
|
|||
DisplayPoint::new(5, 1)..DisplayPoint::new(5, 1),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_all() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
|
@ -3634,12 +3578,10 @@ mod tests {
|
|||
view.read(app).selection_ranges(app.as_ref()),
|
||||
&[DisplayPoint::new(0, 0)..DisplayPoint::new(2, 3)]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_line() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
|
@ -3678,12 +3620,10 @@ mod tests {
|
|||
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| {
|
||||
#[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));
|
||||
|
@ -3728,10 +3668,7 @@ mod tests {
|
|||
);
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)],
|
||||
ctx,
|
||||
)
|
||||
view.select_display_ranges(&[DisplayPoint::new(4, 0)..DisplayPoint::new(0, 1)], ctx)
|
||||
.unwrap();
|
||||
view.split_selection_into_lines(&(), ctx);
|
||||
});
|
||||
|
@ -3752,21 +3689,16 @@ mod tests {
|
|||
DisplayPoint::new(7, 0)..DisplayPoint::new(7, 0)
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_selection_above_below() {
|
||||
App::test((), |app| {
|
||||
#[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.select_display_ranges(&[DisplayPoint::new(1, 3)..DisplayPoint::new(1, 3)], ctx)
|
||||
.unwrap();
|
||||
});
|
||||
view.update(app, |view, ctx| view.add_selection_above(&(), ctx));
|
||||
|
@ -3812,10 +3744,7 @@ mod tests {
|
|||
);
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[DisplayPoint::new(1, 4)..DisplayPoint::new(1, 3)],
|
||||
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));
|
||||
|
@ -3849,10 +3778,7 @@ mod tests {
|
|||
);
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[DisplayPoint::new(0, 1)..DisplayPoint::new(1, 4)],
|
||||
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));
|
||||
|
@ -3887,10 +3813,7 @@ mod tests {
|
|||
);
|
||||
|
||||
view.update(app, |view, ctx| {
|
||||
view.select_display_ranges(
|
||||
&[DisplayPoint::new(4, 3)..DisplayPoint::new(1, 1)],
|
||||
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));
|
||||
|
@ -3913,7 +3836,6 @@ mod tests {
|
|||
DisplayPoint::new(4, 3)..DisplayPoint::new(4, 1),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
impl BufferView {
|
||||
|
|
|
@ -671,11 +671,9 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::test::sample_text;
|
||||
use buffer::ToPoint;
|
||||
use gpui::App;
|
||||
|
||||
#[test]
|
||||
fn test_basic_folds() {
|
||||
App::test((), |app| {
|
||||
#[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());
|
||||
|
||||
|
@ -715,12 +713,10 @@ mod tests {
|
|||
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| {
|
||||
#[gpui::test]
|
||||
fn test_adjacent_folds(app: &mut gpui::MutableAppContext) {
|
||||
let buffer = app.add_model(|ctx| Buffer::new(0, "abcdefghijkl", ctx));
|
||||
|
||||
{
|
||||
|
@ -758,12 +754,10 @@ mod tests {
|
|||
map.check_invariants(app.as_ref());
|
||||
assert_eq!(map.text(app.as_ref()), "12345…fghijkl");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overlapping_folds() {
|
||||
App::test((), |app| {
|
||||
#[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(
|
||||
|
@ -777,12 +771,10 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
assert_eq!(map.text(app.as_ref()), "aa…eeeee");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merging_folds_via_edit() {
|
||||
App::test((), |app| {
|
||||
#[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());
|
||||
|
||||
|
@ -802,12 +794,10 @@ mod tests {
|
|||
.unwrap();
|
||||
});
|
||||
assert_eq!(map.text(app.as_ref()), "aa…eeeee");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_folds_in_range() {
|
||||
App::test((), |app| {
|
||||
#[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);
|
||||
|
@ -825,9 +815,7 @@ mod tests {
|
|||
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()
|
||||
})
|
||||
.map(|fold| fold.start.to_point(buffer).unwrap()..fold.end.to_point(buffer).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
fold_ranges,
|
||||
|
@ -836,11 +824,10 @@ mod tests {
|
|||
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,7 +850,6 @@ 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>();
|
||||
|
@ -991,10 +977,8 @@ mod tests {
|
|||
}
|
||||
|
||||
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(),
|
||||
);
|
||||
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()));
|
||||
}
|
||||
|
||||
|
@ -1023,13 +1007,11 @@ mod tests {
|
|||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_rows() {
|
||||
App::test((), |app| {
|
||||
#[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));
|
||||
|
||||
|
@ -1059,7 +1041,6 @@ mod tests {
|
|||
.collect::<Vec<_>>(),
|
||||
vec![6]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
impl FoldMap {
|
||||
|
|
|
@ -339,11 +339,9 @@ pub fn collapse_tabs(
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::*;
|
||||
use gpui::App;
|
||||
|
||||
#[test]
|
||||
fn test_chars_at() {
|
||||
App::test((), |app| {
|
||||
#[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());
|
||||
|
@ -385,7 +383,6 @@ mod tests {
|
|||
.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| {
|
||||
#[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,14 +457,12 @@ 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 {
|
||||
#[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();
|
||||
|
@ -524,12 +526,10 @@ mod tests {
|
|||
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 {
|
||||
#[gpui::test]
|
||||
async fn test_matching_cancellation(mut app: gpui::TestAppContext) {
|
||||
let tmp_dir = temp_tree(json!({
|
||||
"hello": "",
|
||||
"goodbye": "",
|
||||
|
@ -547,8 +547,7 @@ mod tests {
|
|||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
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));
|
||||
|
@ -584,12 +583,10 @@ mod tests {
|
|||
|
||||
assert_eq!(finder.matches, matches[0..4])
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_file_worktrees() {
|
||||
App::test_async((), |mut app| async move {
|
||||
#[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");
|
||||
|
@ -604,8 +601,7 @@ mod tests {
|
|||
});
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
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.
|
||||
|
@ -627,12 +623,10 @@ mod tests {
|
|||
// 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 {
|
||||
#[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": "" }
|
||||
|
@ -652,8 +646,7 @@ mod tests {
|
|||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
|
||||
let (_, finder) =
|
||||
app.add_window(|ctx| FileFinder::new(settings, workspace.clone(), ctx));
|
||||
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));
|
||||
|
@ -667,6 +660,5 @@ mod tests {
|
|||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
Some(ctx.spawn(|this, mut ctx| async move {
|
||||
let load_result = loop {
|
||||
if let Some(load_result) = watch.borrow().as_ref() {
|
||||
return load_result.clone();
|
||||
break load_result.clone();
|
||||
}
|
||||
watch.next().await;
|
||||
}
|
||||
},
|
||||
move |me, load_result, ctx| {
|
||||
me.loading_items.remove(&entry);
|
||||
};
|
||||
|
||||
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) {
|
||||
if item.entry_id(ctx.as_ref()).is_none() {
|
||||
let handle = ctx.handle();
|
||||
if item.entry_id(ctx.as_ref()).is_none() {
|
||||
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);
|
||||
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);
|
||||
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,14 +755,12 @@ 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| {
|
||||
#[gpui::test]
|
||||
fn test_open_paths_action(app: &mut gpui::MutableAppContext) {
|
||||
let settings = settings::channel(&app.font_cache()).unwrap().1;
|
||||
|
||||
init(app);
|
||||
|
@ -822,12 +816,10 @@ mod tests {
|
|||
},
|
||||
);
|
||||
assert_eq!(app.window_ids().count(), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_entry() {
|
||||
App::test_async((), |mut app| async move {
|
||||
#[gpui::test]
|
||||
async fn test_open_entry(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "contents 1",
|
||||
|
@ -929,12 +921,10 @@ mod tests {
|
|||
.collect::<Vec<_>>();
|
||||
assert_eq!(pane_entries, &[file1, file2, file3.clone(), file3]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_open_paths() {
|
||||
App::test_async((), |mut app| async move {
|
||||
#[gpui::test]
|
||||
async fn test_open_paths(mut app: gpui::TestAppContext) {
|
||||
let dir1 = temp_tree(json!({
|
||||
"a.txt": "",
|
||||
}));
|
||||
|
@ -1002,12 +992,59 @@ mod tests {
|
|||
"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]
|
||||
fn test_open_and_save_new_file() {
|
||||
App::test_async((), |mut app| async move {
|
||||
#[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| {
|
||||
|
@ -1098,65 +1135,10 @@ mod tests {
|
|||
app.read(|ctx| {
|
||||
assert_eq!(editor2.read(ctx).buffer(), editor.read(ctx).buffer());
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[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)
|
||||
})
|
||||
})
|
||||
.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 {
|
||||
#[gpui::test]
|
||||
async fn test_pane_actions(mut app: gpui::TestAppContext) {
|
||||
app.update(|ctx| pane::init(ctx));
|
||||
|
||||
let dir = temp_tree(json!({
|
||||
|
@ -1204,6 +1186,5 @@ mod tests {
|
|||
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,7 +99,26 @@ impl Worktree {
|
|||
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();
|
||||
|
||||
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,10 +158,12 @@ impl Worktree {
|
|||
ctx.notify();
|
||||
|
||||
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_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,9 +1423,8 @@ 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 {
|
||||
#[gpui::test]
|
||||
async fn test_populate_and_search(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"root": {
|
||||
"apple": "",
|
||||
|
@ -1461,12 +1481,10 @@ mod tests {
|
|||
]
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_file() {
|
||||
App::test_async((), |mut app| async move {
|
||||
#[gpui::test]
|
||||
async fn test_save_file(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"file1": "the old contents",
|
||||
}));
|
||||
|
@ -1481,8 +1499,7 @@ mod tests {
|
|||
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();
|
||||
smol::block_on(tree.save(&path, buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap();
|
||||
path
|
||||
});
|
||||
|
||||
|
@ -1493,12 +1510,10 @@ mod tests {
|
|||
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 {
|
||||
#[gpui::test]
|
||||
async fn test_save_in_single_file_worktree(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"file1": "the old contents",
|
||||
}));
|
||||
|
@ -1518,12 +1533,10 @@ mod tests {
|
|||
|
||||
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 {
|
||||
#[gpui::test]
|
||||
async fn test_rescan_simple(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"a": {
|
||||
"file1": "",
|
||||
|
@ -1566,10 +1579,10 @@ mod tests {
|
|||
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();
|
||||
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;
|
||||
|
||||
|
@ -1603,12 +1616,10 @@ mod tests {
|
|||
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 {
|
||||
#[gpui::test]
|
||||
async fn test_rescan_with_gitignore(mut app: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
".git": {},
|
||||
".gitignore": "ignored-dir\n",
|
||||
|
@ -1644,7 +1655,6 @@ mod tests {
|
|||
assert_eq!(ignored.is_ignored(), true);
|
||||
assert_eq!(dot_git.is_ignored(), true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue