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",
|
"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"
|
||||||
|
|
|
@ -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"}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
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 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
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;
|
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<_, _>>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue