mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
commit
d433da1e70
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2112,7 +2112,7 @@ dependencies = [
|
||||
"lsp2",
|
||||
"node_runtime",
|
||||
"parking_lot 0.11.2",
|
||||
"rpc",
|
||||
"rpc2",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"settings2",
|
||||
|
@ -45,6 +45,6 @@ fs = { path = "../fs", features = ["test-support"] }
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
||||
language = { package = "language2", path = "../language2", features = ["test-support"] }
|
||||
lsp = { package = "lsp2", path = "../lsp2", features = ["test-support"] }
|
||||
rpc = { path = "../rpc", features = ["test-support"] }
|
||||
rpc = { package = "rpc2", path = "../rpc2", features = ["test-support"] }
|
||||
settings = { package = "settings2", path = "../settings2", features = ["test-support"] }
|
||||
util = { path = "../util", features = ["test-support"] }
|
||||
|
@ -1002,229 +1002,231 @@ async fn get_copilot_lsp(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use gpui::{executor::Deterministic, TestAppContext};
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::TestAppContext;
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_buffer_management(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
|
||||
// deterministic.forbid_parking();
|
||||
// let (copilot, mut lsp) = Copilot::fake(cx);
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_buffer_management(cx: &mut TestAppContext) {
|
||||
let (copilot, mut lsp) = Copilot::fake(cx);
|
||||
|
||||
// let buffer_1 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Hello"));
|
||||
// let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.id()).parse().unwrap();
|
||||
// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidOpenTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentItem::new(
|
||||
// buffer_1_uri.clone(),
|
||||
// "plaintext".into(),
|
||||
// 0,
|
||||
// "Hello".into()
|
||||
// ),
|
||||
// }
|
||||
// );
|
||||
let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Hello"));
|
||||
let buffer_1_uri: lsp::Url = format!("buffer://{}", buffer_1.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_1, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
lsp::DidOpenTextDocumentParams {
|
||||
text_document: lsp::TextDocumentItem::new(
|
||||
buffer_1_uri.clone(),
|
||||
"plaintext".into(),
|
||||
0,
|
||||
"Hello".into()
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
// let buffer_2 = cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "Goodbye"));
|
||||
// let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.id()).parse().unwrap();
|
||||
// copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidOpenTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentItem::new(
|
||||
// buffer_2_uri.clone(),
|
||||
// "plaintext".into(),
|
||||
// 0,
|
||||
// "Goodbye".into()
|
||||
// ),
|
||||
// }
|
||||
// );
|
||||
let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "Goodbye"));
|
||||
let buffer_2_uri: lsp::Url = format!("buffer://{}", buffer_2.entity_id().as_u64())
|
||||
.parse()
|
||||
.unwrap();
|
||||
copilot.update(cx, |copilot, cx| copilot.register_buffer(&buffer_2, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
lsp::DidOpenTextDocumentParams {
|
||||
text_document: lsp::TextDocumentItem::new(
|
||||
buffer_2_uri.clone(),
|
||||
"plaintext".into(),
|
||||
0,
|
||||
"Goodbye".into()
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
// buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidChangeTextDocumentParams {
|
||||
// text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
|
||||
// content_changes: vec![lsp::TextDocumentContentChangeEvent {
|
||||
// range: Some(lsp::Range::new(
|
||||
// lsp::Position::new(0, 5),
|
||||
// lsp::Position::new(0, 5)
|
||||
// )),
|
||||
// range_length: None,
|
||||
// text: " world".into(),
|
||||
// }],
|
||||
// }
|
||||
// );
|
||||
buffer_1.update(cx, |buffer, cx| buffer.edit([(5..5, " world")], None, cx));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidChangeTextDocument>()
|
||||
.await,
|
||||
lsp::DidChangeTextDocumentParams {
|
||||
text_document: lsp::VersionedTextDocumentIdentifier::new(buffer_1_uri.clone(), 1),
|
||||
content_changes: vec![lsp::TextDocumentContentChangeEvent {
|
||||
range: Some(lsp::Range::new(
|
||||
lsp::Position::new(0, 5),
|
||||
lsp::Position::new(0, 5)
|
||||
)),
|
||||
range_length: None,
|
||||
text: " world".into(),
|
||||
}],
|
||||
}
|
||||
);
|
||||
|
||||
// // Ensure updates to the file are reflected in the LSP.
|
||||
// buffer_1
|
||||
// .update(cx, |buffer, cx| {
|
||||
// buffer.file_updated(
|
||||
// Arc::new(File {
|
||||
// abs_path: "/root/child/buffer-1".into(),
|
||||
// path: Path::new("child/buffer-1").into(),
|
||||
// }),
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
// .await;
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidCloseTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
||||
// }
|
||||
// );
|
||||
// let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidOpenTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentItem::new(
|
||||
// buffer_1_uri.clone(),
|
||||
// "plaintext".into(),
|
||||
// 1,
|
||||
// "Hello world".into()
|
||||
// ),
|
||||
// }
|
||||
// );
|
||||
// Ensure updates to the file are reflected in the LSP.
|
||||
buffer_1.update(cx, |buffer, cx| {
|
||||
buffer.file_updated(
|
||||
Arc::new(File {
|
||||
abs_path: "/root/child/buffer-1".into(),
|
||||
path: Path::new("child/buffer-1").into(),
|
||||
}),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
.await,
|
||||
lsp::DidCloseTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri),
|
||||
}
|
||||
);
|
||||
let buffer_1_uri = lsp::Url::from_file_path("/root/child/buffer-1").unwrap();
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
lsp::DidOpenTextDocumentParams {
|
||||
text_document: lsp::TextDocumentItem::new(
|
||||
buffer_1_uri.clone(),
|
||||
"plaintext".into(),
|
||||
1,
|
||||
"Hello world".into()
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
// // Ensure all previously-registered buffers are closed when signing out.
|
||||
// lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
|
||||
// Ok(request::SignOutResult {})
|
||||
// });
|
||||
// copilot
|
||||
// .update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidCloseTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
|
||||
// }
|
||||
// );
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidCloseTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
|
||||
// }
|
||||
// );
|
||||
// Ensure all previously-registered buffers are closed when signing out.
|
||||
lsp.handle_request::<request::SignOut, _, _>(|_, _| async {
|
||||
Ok(request::SignOutResult {})
|
||||
});
|
||||
copilot
|
||||
.update(cx, |copilot, cx| copilot.sign_out(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
// todo!() po: these notifications now happen in reverse order?
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
.await,
|
||||
lsp::DidCloseTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(buffer_1_uri.clone()),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
.await,
|
||||
lsp::DidCloseTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri.clone()),
|
||||
}
|
||||
);
|
||||
|
||||
// // Ensure all previously-registered buffers are re-opened when signing in.
|
||||
// lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
|
||||
// Ok(request::SignInInitiateResult::AlreadySignedIn {
|
||||
// user: "user-1".into(),
|
||||
// })
|
||||
// });
|
||||
// copilot
|
||||
// .update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidOpenTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentItem::new(
|
||||
// buffer_2_uri.clone(),
|
||||
// "plaintext".into(),
|
||||
// 0,
|
||||
// "Goodbye".into()
|
||||
// ),
|
||||
// }
|
||||
// );
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidOpenTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentItem::new(
|
||||
// buffer_1_uri.clone(),
|
||||
// "plaintext".into(),
|
||||
// 0,
|
||||
// "Hello world".into()
|
||||
// ),
|
||||
// }
|
||||
// );
|
||||
// Ensure all previously-registered buffers are re-opened when signing in.
|
||||
lsp.handle_request::<request::SignInInitiate, _, _>(|_, _| async {
|
||||
Ok(request::SignInInitiateResult::AlreadySignedIn {
|
||||
user: "user-1".into(),
|
||||
})
|
||||
});
|
||||
copilot
|
||||
.update(cx, |copilot, cx| copilot.sign_in(cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// // Dropping a buffer causes it to be closed on the LSP side as well.
|
||||
// cx.update(|_| drop(buffer_2));
|
||||
// assert_eq!(
|
||||
// lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
// .await,
|
||||
// lsp::DidCloseTextDocumentParams {
|
||||
// text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
lsp::DidOpenTextDocumentParams {
|
||||
text_document: lsp::TextDocumentItem::new(
|
||||
buffer_1_uri.clone(),
|
||||
"plaintext".into(),
|
||||
0,
|
||||
"Hello world".into()
|
||||
),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidOpenTextDocument>()
|
||||
.await,
|
||||
lsp::DidOpenTextDocumentParams {
|
||||
text_document: lsp::TextDocumentItem::new(
|
||||
buffer_2_uri.clone(),
|
||||
"plaintext".into(),
|
||||
0,
|
||||
"Goodbye".into()
|
||||
),
|
||||
}
|
||||
);
|
||||
// Dropping a buffer causes it to be closed on the LSP side as well.
|
||||
cx.update(|_| drop(buffer_2));
|
||||
assert_eq!(
|
||||
lsp.receive_notification::<lsp::notification::DidCloseTextDocument>()
|
||||
.await,
|
||||
lsp::DidCloseTextDocumentParams {
|
||||
text_document: lsp::TextDocumentIdentifier::new(buffer_2_uri),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// struct File {
|
||||
// abs_path: PathBuf,
|
||||
// path: Arc<Path>,
|
||||
// }
|
||||
struct File {
|
||||
abs_path: PathBuf,
|
||||
path: Arc<Path>,
|
||||
}
|
||||
|
||||
// impl language2::File for File {
|
||||
// fn as_local(&self) -> Option<&dyn language2::LocalFile> {
|
||||
// Some(self)
|
||||
// }
|
||||
impl language::File for File {
|
||||
fn as_local(&self) -> Option<&dyn language::LocalFile> {
|
||||
Some(self)
|
||||
}
|
||||
|
||||
// fn mtime(&self) -> std::time::SystemTime {
|
||||
// unimplemented!()
|
||||
// }
|
||||
fn mtime(&self) -> std::time::SystemTime {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn path(&self) -> &Arc<Path> {
|
||||
// &self.path
|
||||
// }
|
||||
fn path(&self) -> &Arc<Path> {
|
||||
&self.path
|
||||
}
|
||||
|
||||
// fn full_path(&self, _: &AppContext) -> PathBuf {
|
||||
// unimplemented!()
|
||||
// }
|
||||
fn full_path(&self, _: &AppContext) -> PathBuf {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
|
||||
// unimplemented!()
|
||||
// }
|
||||
fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn is_deleted(&self) -> bool {
|
||||
// unimplemented!()
|
||||
// }
|
||||
fn is_deleted(&self) -> bool {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn as_any(&self) -> &dyn std::any::Any {
|
||||
// unimplemented!()
|
||||
// }
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn to_proto(&self) -> rpc::proto::File {
|
||||
// unimplemented!()
|
||||
// }
|
||||
fn to_proto(&self) -> rpc::proto::File {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn worktree_id(&self) -> usize {
|
||||
// 0
|
||||
// }
|
||||
// }
|
||||
fn worktree_id(&self) -> usize {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// impl language::LocalFile for File {
|
||||
// fn abs_path(&self, _: &AppContext) -> PathBuf {
|
||||
// self.abs_path.clone()
|
||||
// }
|
||||
impl language::LocalFile for File {
|
||||
fn abs_path(&self, _: &AppContext) -> PathBuf {
|
||||
self.abs_path.clone()
|
||||
}
|
||||
|
||||
// fn load(&self, _: &AppContext) -> Task<Result<String>> {
|
||||
// unimplemented!()
|
||||
// }
|
||||
fn load(&self, _: &AppContext) -> Task<Result<String>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
// fn buffer_reloaded(
|
||||
// &self,
|
||||
// _: u64,
|
||||
// _: &clock::Global,
|
||||
// _: language::RopeFingerprint,
|
||||
// _: language::LineEnding,
|
||||
// _: std::time::SystemTime,
|
||||
// _: &mut AppContext,
|
||||
// ) {
|
||||
// unimplemented!()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
fn buffer_reloaded(
|
||||
&self,
|
||||
_: u64,
|
||||
_: &clock::Global,
|
||||
_: language::RopeFingerprint,
|
||||
_: language::LineEnding,
|
||||
_: std::time::SystemTime,
|
||||
_: &mut AppContext,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -741,49 +741,48 @@ impl WrapSnapshot {
|
||||
}
|
||||
|
||||
fn check_invariants(&self) {
|
||||
// todo!()
|
||||
// #[cfg(test)]
|
||||
// {
|
||||
// assert_eq!(
|
||||
// TabPoint::from(self.transforms.summary().input.lines),
|
||||
// self.tab_snapshot.max_point()
|
||||
// );
|
||||
#[cfg(test)]
|
||||
{
|
||||
assert_eq!(
|
||||
TabPoint::from(self.transforms.summary().input.lines),
|
||||
self.tab_snapshot.max_point()
|
||||
);
|
||||
|
||||
// {
|
||||
// let mut transforms = self.transforms.cursor::<()>().peekable();
|
||||
// while let Some(transform) = transforms.next() {
|
||||
// if let Some(next_transform) = transforms.peek() {
|
||||
// assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
{
|
||||
let mut transforms = self.transforms.cursor::<()>().peekable();
|
||||
while let Some(transform) = transforms.next() {
|
||||
if let Some(next_transform) = transforms.peek() {
|
||||
assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let text = language::Rope::from(self.text().as_str());
|
||||
// let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
|
||||
// let mut expected_buffer_rows = Vec::new();
|
||||
// let mut prev_tab_row = 0;
|
||||
// for display_row in 0..=self.max_point().row() {
|
||||
// let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
|
||||
// if tab_point.row() == prev_tab_row && display_row != 0 {
|
||||
// expected_buffer_rows.push(None);
|
||||
// } else {
|
||||
// expected_buffer_rows.push(input_buffer_rows.next().unwrap());
|
||||
// }
|
||||
let text = language::Rope::from(self.text().as_str());
|
||||
let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0);
|
||||
let mut expected_buffer_rows = Vec::new();
|
||||
let mut prev_tab_row = 0;
|
||||
for display_row in 0..=self.max_point().row() {
|
||||
let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
|
||||
if tab_point.row() == prev_tab_row && display_row != 0 {
|
||||
expected_buffer_rows.push(None);
|
||||
} else {
|
||||
expected_buffer_rows.push(input_buffer_rows.next().unwrap());
|
||||
}
|
||||
|
||||
// prev_tab_row = tab_point.row();
|
||||
// assert_eq!(self.line_len(display_row), text.line_len(display_row));
|
||||
// }
|
||||
prev_tab_row = tab_point.row();
|
||||
assert_eq!(self.line_len(display_row), text.line_len(display_row));
|
||||
}
|
||||
|
||||
// for start_display_row in 0..expected_buffer_rows.len() {
|
||||
// assert_eq!(
|
||||
// self.buffer_rows(start_display_row as u32)
|
||||
// .collect::<Vec<_>>(),
|
||||
// &expected_buffer_rows[start_display_row..],
|
||||
// "invalid buffer_rows({}..)",
|
||||
// start_display_row
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
for start_display_row in 0..expected_buffer_rows.len() {
|
||||
assert_eq!(
|
||||
self.buffer_rows(start_display_row as u32)
|
||||
.collect::<Vec<_>>(),
|
||||
&expected_buffer_rows[start_display_row..],
|
||||
"invalid buffer_rows({}..)",
|
||||
start_display_row
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1026,337 +1025,334 @@ fn consolidate_wrap_edits(edits: &mut Vec<WrapEdit>) {
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::{
|
||||
// display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
// MultiBuffer,
|
||||
// };
|
||||
// use gpui::test::observe;
|
||||
// use rand::prelude::*;
|
||||
// use settings::SettingsStore;
|
||||
// use smol::stream::StreamExt;
|
||||
// use std::{cmp, env, num::NonZeroU32};
|
||||
// use text::Rope;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
|
||||
MultiBuffer,
|
||||
};
|
||||
use gpui::{font, px, test::observe, Platform};
|
||||
use rand::prelude::*;
|
||||
use settings::SettingsStore;
|
||||
use smol::stream::StreamExt;
|
||||
use std::{cmp, env, num::NonZeroU32};
|
||||
use text::Rope;
|
||||
use theme::LoadThemes;
|
||||
|
||||
// #[gpui::test(iterations = 100)]
|
||||
// async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
|
||||
// init_test(cx);
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
|
||||
// todo!() this test is flaky
|
||||
init_test(cx);
|
||||
|
||||
// cx.foreground().set_block_on_ticks(0..=50);
|
||||
// let operations = env::var("OPERATIONS")
|
||||
// .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
// .unwrap_or(10);
|
||||
cx.background_executor.set_block_on_ticks(0..=50);
|
||||
let operations = env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(10);
|
||||
|
||||
// let font_cache = cx.font_cache().clone();
|
||||
// let font_system = cx.platform().fonts();
|
||||
// let mut wrap_width = if rng.gen_bool(0.1) {
|
||||
// None
|
||||
// } else {
|
||||
// Some(rng.gen_range(0.0..=1000.0))
|
||||
// };
|
||||
// let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
|
||||
// let family_id = font_cache
|
||||
// .load_family(&["Helvetica"], &Default::default())
|
||||
// .unwrap();
|
||||
// let font_id = font_cache
|
||||
// .select_font(family_id, &Default::default())
|
||||
// .unwrap();
|
||||
// let font_size = 14.0;
|
||||
let text_system = cx.read(|cx| cx.text_system().clone());
|
||||
let mut wrap_width = if rng.gen_bool(0.1) {
|
||||
None
|
||||
} else {
|
||||
Some(px(rng.gen_range(0.0..=1000.0)))
|
||||
};
|
||||
let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
|
||||
let font = font("Helvetica");
|
||||
let font_id = text_system.font_id(&font).unwrap();
|
||||
let font_size = px(14.0);
|
||||
|
||||
// log::info!("Tab size: {}", tab_size);
|
||||
// log::info!("Wrap width: {:?}", wrap_width);
|
||||
log::info!("Tab size: {}", tab_size);
|
||||
log::info!("Wrap width: {:?}", wrap_width);
|
||||
|
||||
// let buffer = cx.update(|cx| {
|
||||
// if rng.gen() {
|
||||
// MultiBuffer::build_random(&mut rng, cx)
|
||||
// } else {
|
||||
// let len = rng.gen_range(0..10);
|
||||
// let text = util::RandomCharIter::new(&mut rng)
|
||||
// .take(len)
|
||||
// .collect::<String>();
|
||||
// MultiBuffer::build_simple(&text, cx)
|
||||
// }
|
||||
// });
|
||||
// let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
// log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
// let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
// log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
// let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
|
||||
// log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
// let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
// let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
// log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
let buffer = cx.update(|cx| {
|
||||
if rng.gen() {
|
||||
MultiBuffer::build_random(&mut rng, cx)
|
||||
} else {
|
||||
let len = rng.gen_range(0..10);
|
||||
let text = util::RandomCharIter::new(&mut rng)
|
||||
.take(len)
|
||||
.collect::<String>();
|
||||
MultiBuffer::build_simple(&text, cx)
|
||||
}
|
||||
});
|
||||
let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
|
||||
let tabs_snapshot = tab_map.set_max_expansion_column(32);
|
||||
log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
|
||||
// let mut line_wrapper = LineWrapper::new(font_id, font_size, font_system);
|
||||
// let unwrapped_text = tabs_snapshot.text();
|
||||
// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
|
||||
let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size).unwrap();
|
||||
let unwrapped_text = tabs_snapshot.text();
|
||||
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
|
||||
|
||||
// let (wrap_map, _) =
|
||||
// cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx));
|
||||
// let mut notifications = observe(&wrap_map, cx);
|
||||
let (wrap_map, _) =
|
||||
cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font, font_size, wrap_width, cx));
|
||||
let mut notifications = observe(&wrap_map, cx);
|
||||
|
||||
// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
// notifications.next().await.unwrap();
|
||||
// }
|
||||
if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
notifications.next().await.unwrap();
|
||||
}
|
||||
|
||||
// let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| {
|
||||
// assert!(!map.is_rewrapping());
|
||||
// map.sync(tabs_snapshot.clone(), Vec::new(), cx)
|
||||
// });
|
||||
let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| {
|
||||
assert!(!map.is_rewrapping());
|
||||
map.sync(tabs_snapshot.clone(), Vec::new(), cx)
|
||||
});
|
||||
|
||||
// let actual_text = initial_snapshot.text();
|
||||
// assert_eq!(
|
||||
// actual_text, expected_text,
|
||||
// "unwrapped text is: {:?}",
|
||||
// unwrapped_text
|
||||
// );
|
||||
// log::info!("Wrapped text: {:?}", actual_text);
|
||||
let actual_text = initial_snapshot.text();
|
||||
assert_eq!(
|
||||
actual_text, expected_text,
|
||||
"unwrapped text is: {:?}",
|
||||
unwrapped_text
|
||||
);
|
||||
log::info!("Wrapped text: {:?}", actual_text);
|
||||
|
||||
// let mut next_inlay_id = 0;
|
||||
// let mut edits = Vec::new();
|
||||
// for _i in 0..operations {
|
||||
// log::info!("{} ==============================================", _i);
|
||||
let mut next_inlay_id = 0;
|
||||
let mut edits = Vec::new();
|
||||
for _i in 0..operations {
|
||||
log::info!("{} ==============================================", _i);
|
||||
|
||||
// let mut buffer_edits = Vec::new();
|
||||
// match rng.gen_range(0..=100) {
|
||||
// 0..=19 => {
|
||||
// wrap_width = if rng.gen_bool(0.2) {
|
||||
// None
|
||||
// } else {
|
||||
// Some(rng.gen_range(0.0..=1000.0))
|
||||
// };
|
||||
// log::info!("Setting wrap width to {:?}", wrap_width);
|
||||
// wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
|
||||
// }
|
||||
// 20..=39 => {
|
||||
// for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
|
||||
// let (tabs_snapshot, tab_edits) =
|
||||
// tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
// let (mut snapshot, wrap_edits) =
|
||||
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
// snapshot.check_invariants();
|
||||
// snapshot.verify_chunks(&mut rng);
|
||||
// edits.push((snapshot, wrap_edits));
|
||||
// }
|
||||
// }
|
||||
// 40..=59 => {
|
||||
// let (inlay_snapshot, inlay_edits) =
|
||||
// inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
|
||||
// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
// let (tabs_snapshot, tab_edits) =
|
||||
// tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
// let (mut snapshot, wrap_edits) =
|
||||
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
// snapshot.check_invariants();
|
||||
// snapshot.verify_chunks(&mut rng);
|
||||
// edits.push((snapshot, wrap_edits));
|
||||
// }
|
||||
// _ => {
|
||||
// buffer.update(cx, |buffer, cx| {
|
||||
// let subscription = buffer.subscribe();
|
||||
// let edit_count = rng.gen_range(1..=5);
|
||||
// buffer.randomly_mutate(&mut rng, edit_count, cx);
|
||||
// buffer_snapshot = buffer.snapshot(cx);
|
||||
// buffer_edits.extend(subscription.consume());
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
let mut buffer_edits = Vec::new();
|
||||
match rng.gen_range(0..=100) {
|
||||
0..=19 => {
|
||||
wrap_width = if rng.gen_bool(0.2) {
|
||||
None
|
||||
} else {
|
||||
Some(px(rng.gen_range(0.0..=1000.0)))
|
||||
};
|
||||
log::info!("Setting wrap width to {:?}", wrap_width);
|
||||
wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
|
||||
}
|
||||
20..=39 => {
|
||||
for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
|
||||
let (tabs_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (mut snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
snapshot.check_invariants();
|
||||
snapshot.verify_chunks(&mut rng);
|
||||
edits.push((snapshot, wrap_edits));
|
||||
}
|
||||
}
|
||||
40..=59 => {
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
let (tabs_snapshot, tab_edits) =
|
||||
tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
let (mut snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
|
||||
snapshot.check_invariants();
|
||||
snapshot.verify_chunks(&mut rng);
|
||||
edits.push((snapshot, wrap_edits));
|
||||
}
|
||||
_ => {
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
let subscription = buffer.subscribe();
|
||||
let edit_count = rng.gen_range(1..=5);
|
||||
buffer.randomly_mutate(&mut rng, edit_count, cx);
|
||||
buffer_snapshot = buffer.snapshot(cx);
|
||||
buffer_edits.extend(subscription.consume());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
// let (inlay_snapshot, inlay_edits) =
|
||||
// inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
// log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
// let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
// log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
// let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
// log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
log::info!("Buffer text: {:?}", buffer_snapshot.text());
|
||||
let (inlay_snapshot, inlay_edits) =
|
||||
inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
|
||||
log::info!("InlayMap text: {:?}", inlay_snapshot.text());
|
||||
let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
|
||||
log::info!("FoldMap text: {:?}", fold_snapshot.text());
|
||||
let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
|
||||
log::info!("TabMap text: {:?}", tabs_snapshot.text());
|
||||
|
||||
// let unwrapped_text = tabs_snapshot.text();
|
||||
// let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
|
||||
// let (mut snapshot, wrap_edits) =
|
||||
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx));
|
||||
// snapshot.check_invariants();
|
||||
// snapshot.verify_chunks(&mut rng);
|
||||
// edits.push((snapshot, wrap_edits));
|
||||
let unwrapped_text = tabs_snapshot.text();
|
||||
let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper);
|
||||
let (mut snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx));
|
||||
snapshot.check_invariants();
|
||||
snapshot.verify_chunks(&mut rng);
|
||||
edits.push((snapshot, wrap_edits));
|
||||
|
||||
// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
|
||||
// log::info!("Waiting for wrapping to finish");
|
||||
// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
// notifications.next().await.unwrap();
|
||||
// }
|
||||
// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
|
||||
// }
|
||||
if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
|
||||
log::info!("Waiting for wrapping to finish");
|
||||
while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
notifications.next().await.unwrap();
|
||||
}
|
||||
wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
|
||||
}
|
||||
|
||||
// if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
// let (mut wrapped_snapshot, wrap_edits) =
|
||||
// wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
|
||||
// let actual_text = wrapped_snapshot.text();
|
||||
// let actual_longest_row = wrapped_snapshot.longest_row();
|
||||
// log::info!("Wrapping finished: {:?}", actual_text);
|
||||
// wrapped_snapshot.check_invariants();
|
||||
// wrapped_snapshot.verify_chunks(&mut rng);
|
||||
// edits.push((wrapped_snapshot.clone(), wrap_edits));
|
||||
// assert_eq!(
|
||||
// actual_text, expected_text,
|
||||
// "unwrapped text is: {:?}",
|
||||
// unwrapped_text
|
||||
// );
|
||||
if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
let (mut wrapped_snapshot, wrap_edits) =
|
||||
wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx));
|
||||
let actual_text = wrapped_snapshot.text();
|
||||
let actual_longest_row = wrapped_snapshot.longest_row();
|
||||
log::info!("Wrapping finished: {:?}", actual_text);
|
||||
wrapped_snapshot.check_invariants();
|
||||
wrapped_snapshot.verify_chunks(&mut rng);
|
||||
edits.push((wrapped_snapshot.clone(), wrap_edits));
|
||||
assert_eq!(
|
||||
actual_text, expected_text,
|
||||
"unwrapped text is: {:?}",
|
||||
unwrapped_text
|
||||
);
|
||||
|
||||
// let mut summary = TextSummary::default();
|
||||
// for (ix, item) in wrapped_snapshot
|
||||
// .transforms
|
||||
// .items(&())
|
||||
// .into_iter()
|
||||
// .enumerate()
|
||||
// {
|
||||
// summary += &item.summary.output;
|
||||
// log::info!("{} summary: {:?}", ix, item.summary.output,);
|
||||
// }
|
||||
let mut summary = TextSummary::default();
|
||||
for (ix, item) in wrapped_snapshot
|
||||
.transforms
|
||||
.items(&())
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
summary += &item.summary.output;
|
||||
log::info!("{} summary: {:?}", ix, item.summary.output,);
|
||||
}
|
||||
|
||||
// if tab_size.get() == 1
|
||||
// || !wrapped_snapshot
|
||||
// .tab_snapshot
|
||||
// .fold_snapshot
|
||||
// .text()
|
||||
// .contains('\t')
|
||||
// {
|
||||
// let mut expected_longest_rows = Vec::new();
|
||||
// let mut longest_line_len = -1;
|
||||
// for (row, line) in expected_text.split('\n').enumerate() {
|
||||
// let line_char_count = line.chars().count() as isize;
|
||||
// if line_char_count > longest_line_len {
|
||||
// expected_longest_rows.clear();
|
||||
// longest_line_len = line_char_count;
|
||||
// }
|
||||
// if line_char_count >= longest_line_len {
|
||||
// expected_longest_rows.push(row as u32);
|
||||
// }
|
||||
// }
|
||||
if tab_size.get() == 1
|
||||
|| !wrapped_snapshot
|
||||
.tab_snapshot
|
||||
.fold_snapshot
|
||||
.text()
|
||||
.contains('\t')
|
||||
{
|
||||
let mut expected_longest_rows = Vec::new();
|
||||
let mut longest_line_len = -1;
|
||||
for (row, line) in expected_text.split('\n').enumerate() {
|
||||
let line_char_count = line.chars().count() as isize;
|
||||
if line_char_count > longest_line_len {
|
||||
expected_longest_rows.clear();
|
||||
longest_line_len = line_char_count;
|
||||
}
|
||||
if line_char_count >= longest_line_len {
|
||||
expected_longest_rows.push(row as u32);
|
||||
}
|
||||
}
|
||||
|
||||
// assert!(
|
||||
// expected_longest_rows.contains(&actual_longest_row),
|
||||
// "incorrect longest row {}. expected {:?} with length {}",
|
||||
// actual_longest_row,
|
||||
// expected_longest_rows,
|
||||
// longest_line_len,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
assert!(
|
||||
expected_longest_rows.contains(&actual_longest_row),
|
||||
"incorrect longest row {}. expected {:?} with length {}",
|
||||
actual_longest_row,
|
||||
expected_longest_rows,
|
||||
longest_line_len,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let mut initial_text = Rope::from(initial_snapshot.text().as_str());
|
||||
// for (snapshot, patch) in edits {
|
||||
// let snapshot_text = Rope::from(snapshot.text().as_str());
|
||||
// for edit in &patch {
|
||||
// let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
|
||||
// let old_end = initial_text.point_to_offset(cmp::min(
|
||||
// Point::new(edit.new.start + edit.old.len() as u32, 0),
|
||||
// initial_text.max_point(),
|
||||
// ));
|
||||
// let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
|
||||
// let new_end = snapshot_text.point_to_offset(cmp::min(
|
||||
// Point::new(edit.new.end, 0),
|
||||
// snapshot_text.max_point(),
|
||||
// ));
|
||||
// let new_text = snapshot_text
|
||||
// .chunks_in_range(new_start..new_end)
|
||||
// .collect::<String>();
|
||||
let mut initial_text = Rope::from(initial_snapshot.text().as_str());
|
||||
for (snapshot, patch) in edits {
|
||||
let snapshot_text = Rope::from(snapshot.text().as_str());
|
||||
for edit in &patch {
|
||||
let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
|
||||
let old_end = initial_text.point_to_offset(cmp::min(
|
||||
Point::new(edit.new.start + edit.old.len() as u32, 0),
|
||||
initial_text.max_point(),
|
||||
));
|
||||
let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
|
||||
let new_end = snapshot_text.point_to_offset(cmp::min(
|
||||
Point::new(edit.new.end, 0),
|
||||
snapshot_text.max_point(),
|
||||
));
|
||||
let new_text = snapshot_text
|
||||
.chunks_in_range(new_start..new_end)
|
||||
.collect::<String>();
|
||||
|
||||
// initial_text.replace(old_start..old_end, &new_text);
|
||||
// }
|
||||
// assert_eq!(initial_text.to_string(), snapshot_text.to_string());
|
||||
// }
|
||||
initial_text.replace(old_start..old_end, &new_text);
|
||||
}
|
||||
assert_eq!(initial_text.to_string(), snapshot_text.to_string());
|
||||
}
|
||||
|
||||
// if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
// log::info!("Waiting for wrapping to finish");
|
||||
// while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
// notifications.next().await.unwrap();
|
||||
// }
|
||||
// }
|
||||
// wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
|
||||
// }
|
||||
if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
log::info!("Waiting for wrapping to finish");
|
||||
while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
|
||||
notifications.next().await.unwrap();
|
||||
}
|
||||
}
|
||||
wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
|
||||
}
|
||||
|
||||
// fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
// cx.foreground().forbid_parking();
|
||||
// cx.update(|cx| {
|
||||
// cx.set_global(SettingsStore::test(cx));
|
||||
// theme::init((), cx);
|
||||
// });
|
||||
// }
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
theme::init(LoadThemes::JustBase, cx);
|
||||
});
|
||||
}
|
||||
|
||||
// fn wrap_text(
|
||||
// unwrapped_text: &str,
|
||||
// wrap_width: Option<f32>,
|
||||
// line_wrapper: &mut LineWrapper,
|
||||
// ) -> String {
|
||||
// if let Some(wrap_width) = wrap_width {
|
||||
// let mut wrapped_text = String::new();
|
||||
// for (row, line) in unwrapped_text.split('\n').enumerate() {
|
||||
// if row > 0 {
|
||||
// wrapped_text.push('\n')
|
||||
// }
|
||||
fn wrap_text(
|
||||
unwrapped_text: &str,
|
||||
wrap_width: Option<Pixels>,
|
||||
line_wrapper: &mut LineWrapper,
|
||||
) -> String {
|
||||
if let Some(wrap_width) = wrap_width {
|
||||
let mut wrapped_text = String::new();
|
||||
for (row, line) in unwrapped_text.split('\n').enumerate() {
|
||||
if row > 0 {
|
||||
wrapped_text.push('\n')
|
||||
}
|
||||
|
||||
// let mut prev_ix = 0;
|
||||
// for boundary in line_wrapper.wrap_line(line, wrap_width) {
|
||||
// wrapped_text.push_str(&line[prev_ix..boundary.ix]);
|
||||
// wrapped_text.push('\n');
|
||||
// wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
|
||||
// prev_ix = boundary.ix;
|
||||
// }
|
||||
// wrapped_text.push_str(&line[prev_ix..]);
|
||||
// }
|
||||
// wrapped_text
|
||||
// } else {
|
||||
// unwrapped_text.to_string()
|
||||
// }
|
||||
// }
|
||||
let mut prev_ix = 0;
|
||||
for boundary in line_wrapper.wrap_line(line, wrap_width) {
|
||||
wrapped_text.push_str(&line[prev_ix..boundary.ix]);
|
||||
wrapped_text.push('\n');
|
||||
wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
|
||||
prev_ix = boundary.ix;
|
||||
}
|
||||
wrapped_text.push_str(&line[prev_ix..]);
|
||||
}
|
||||
wrapped_text
|
||||
} else {
|
||||
unwrapped_text.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// impl WrapSnapshot {
|
||||
// pub fn text(&self) -> String {
|
||||
// self.text_chunks(0).collect()
|
||||
// }
|
||||
impl WrapSnapshot {
|
||||
pub fn text(&self) -> String {
|
||||
self.text_chunks(0).collect()
|
||||
}
|
||||
|
||||
// pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
|
||||
// self.chunks(
|
||||
// wrap_row..self.max_point().row() + 1,
|
||||
// false,
|
||||
// Highlights::default(),
|
||||
// )
|
||||
// .map(|h| h.text)
|
||||
// }
|
||||
pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
|
||||
self.chunks(
|
||||
wrap_row..self.max_point().row() + 1,
|
||||
false,
|
||||
Highlights::default(),
|
||||
)
|
||||
.map(|h| h.text)
|
||||
}
|
||||
|
||||
// fn verify_chunks(&mut self, rng: &mut impl Rng) {
|
||||
// for _ in 0..5 {
|
||||
// let mut end_row = rng.gen_range(0..=self.max_point().row());
|
||||
// let start_row = rng.gen_range(0..=end_row);
|
||||
// end_row += 1;
|
||||
fn verify_chunks(&mut self, rng: &mut impl Rng) {
|
||||
for _ in 0..5 {
|
||||
let mut end_row = rng.gen_range(0..=self.max_point().row());
|
||||
let start_row = rng.gen_range(0..=end_row);
|
||||
end_row += 1;
|
||||
|
||||
// let mut expected_text = self.text_chunks(start_row).collect::<String>();
|
||||
// if expected_text.ends_with('\n') {
|
||||
// expected_text.push('\n');
|
||||
// }
|
||||
// let mut expected_text = expected_text
|
||||
// .lines()
|
||||
// .take((end_row - start_row) as usize)
|
||||
// .collect::<Vec<_>>()
|
||||
// .join("\n");
|
||||
// if end_row <= self.max_point().row() {
|
||||
// expected_text.push('\n');
|
||||
// }
|
||||
let mut expected_text = self.text_chunks(start_row).collect::<String>();
|
||||
if expected_text.ends_with('\n') {
|
||||
expected_text.push('\n');
|
||||
}
|
||||
let mut expected_text = expected_text
|
||||
.lines()
|
||||
.take((end_row - start_row) as usize)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
if end_row <= self.max_point().row() {
|
||||
expected_text.push('\n');
|
||||
}
|
||||
|
||||
// let actual_text = self
|
||||
// .chunks(start_row..end_row, true, Highlights::default())
|
||||
// .map(|c| c.text)
|
||||
// .collect::<String>();
|
||||
// assert_eq!(
|
||||
// expected_text,
|
||||
// actual_text,
|
||||
// "chunks != highlighted_chunks for rows {:?}",
|
||||
// start_row..end_row
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
let actual_text = self
|
||||
.chunks(start_row..end_row, true, Highlights::default())
|
||||
.map(|c| c.text)
|
||||
.collect::<String>();
|
||||
assert_eq!(
|
||||
expected_text,
|
||||
actual_text,
|
||||
"chunks != highlighted_chunks for rows {:?}",
|
||||
start_row..end_row
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3486,7 +3486,7 @@ impl Editor {
|
||||
drop(context_menu);
|
||||
this.discard_copilot_suggestion(cx);
|
||||
cx.notify();
|
||||
} else if this.completion_tasks.is_empty() {
|
||||
} else if this.completion_tasks.len() <= 1 {
|
||||
// If there are no more completion tasks and the last menu was
|
||||
// empty, we should hide it. If it was already hidden, we should
|
||||
// also show the copilot suggestion when available.
|
||||
@ -8240,6 +8240,11 @@ impl Editor {
|
||||
self.style = Some(style);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn style(&self) -> Option<&EditorStyle> {
|
||||
self.style.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut AppContext) -> bool {
|
||||
self.display_map
|
||||
.update(cx, |map, cx| map.set_wrap_width(width, cx))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -330,7 +330,7 @@ impl EditorElement {
|
||||
});
|
||||
}
|
||||
|
||||
fn modifiers_changed(
|
||||
pub(crate) fn modifiers_changed(
|
||||
editor: &mut Editor,
|
||||
event: &ModifiersChangedEvent,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
@ -3227,448 +3227,491 @@ fn scale_horizontal_mouse_autoscroll_delta(delta: Pixels) -> f32 {
|
||||
(delta.pow(1.2) / 300.0).into()
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::{
|
||||
// display_map::{BlockDisposition, BlockProperties},
|
||||
// editor_tests::{init_test, update_test_language_settings},
|
||||
// Editor, MultiBuffer,
|
||||
// };
|
||||
// use gpui::TestAppContext;
|
||||
// use language::language_settings;
|
||||
// use log::info;
|
||||
// use std::{num::NonZeroU32, sync::Arc};
|
||||
// use util::test::sample_text;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::{BlockDisposition, BlockProperties},
|
||||
editor_tests::{init_test, update_test_language_settings},
|
||||
Editor, MultiBuffer,
|
||||
};
|
||||
use gpui::{EmptyView, TestAppContext};
|
||||
use language::language_settings;
|
||||
use log::info;
|
||||
use std::{num::NonZeroU32, sync::Arc};
|
||||
use util::test::sample_text;
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_layout_line_numbers(cx: &mut TestAppContext) {
|
||||
// init_test(cx, |_| {});
|
||||
// let editor = cx
|
||||
// .add_window(|cx| {
|
||||
// let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
||||
// Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
// })
|
||||
// .root(cx);
|
||||
// let element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
#[gpui::test]
|
||||
fn test_shape_line_numbers(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, cx)
|
||||
});
|
||||
|
||||
// let layouts = editor.update(cx, |editor, cx| {
|
||||
// let snapshot = editor.snapshot(cx);
|
||||
// element
|
||||
// .layout_line_numbers(
|
||||
// 0..6,
|
||||
// &Default::default(),
|
||||
// DisplayPoint::new(0, 0),
|
||||
// false,
|
||||
// &snapshot,
|
||||
// cx,
|
||||
// )
|
||||
// .0
|
||||
// });
|
||||
// assert_eq!(layouts.len(), 6);
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
let element = EditorElement::new(&editor, style);
|
||||
|
||||
// let relative_rows = editor.update(cx, |editor, cx| {
|
||||
// let snapshot = editor.snapshot(cx);
|
||||
// element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
|
||||
// });
|
||||
// assert_eq!(relative_rows[&0], 3);
|
||||
// assert_eq!(relative_rows[&1], 2);
|
||||
// assert_eq!(relative_rows[&2], 1);
|
||||
// // current line has no relative number
|
||||
// assert_eq!(relative_rows[&4], 1);
|
||||
// assert_eq!(relative_rows[&5], 2);
|
||||
let layouts = window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
element
|
||||
.shape_line_numbers(
|
||||
0..6,
|
||||
&Default::default(),
|
||||
DisplayPoint::new(0, 0),
|
||||
false,
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
.0
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(layouts.len(), 6);
|
||||
|
||||
// // works if cursor is before screen
|
||||
// let relative_rows = editor.update(cx, |editor, cx| {
|
||||
// let snapshot = editor.snapshot(cx);
|
||||
let relative_rows = window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
element.calculate_relative_line_numbers(&snapshot, &(0..6), Some(3))
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(relative_rows[&0], 3);
|
||||
assert_eq!(relative_rows[&1], 2);
|
||||
assert_eq!(relative_rows[&2], 1);
|
||||
// current line has no relative number
|
||||
assert_eq!(relative_rows[&4], 1);
|
||||
assert_eq!(relative_rows[&5], 2);
|
||||
|
||||
// element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
|
||||
// });
|
||||
// assert_eq!(relative_rows.len(), 3);
|
||||
// assert_eq!(relative_rows[&3], 2);
|
||||
// assert_eq!(relative_rows[&4], 3);
|
||||
// assert_eq!(relative_rows[&5], 4);
|
||||
// works if cursor is before screen
|
||||
let relative_rows = window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
|
||||
// // works if cursor is after screen
|
||||
// let relative_rows = editor.update(cx, |editor, cx| {
|
||||
// let snapshot = editor.snapshot(cx);
|
||||
element.calculate_relative_line_numbers(&snapshot, &(3..6), Some(1))
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(relative_rows.len(), 3);
|
||||
assert_eq!(relative_rows[&3], 2);
|
||||
assert_eq!(relative_rows[&4], 3);
|
||||
assert_eq!(relative_rows[&5], 4);
|
||||
|
||||
// element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
|
||||
// });
|
||||
// assert_eq!(relative_rows.len(), 3);
|
||||
// assert_eq!(relative_rows[&0], 5);
|
||||
// assert_eq!(relative_rows[&1], 4);
|
||||
// assert_eq!(relative_rows[&2], 3);
|
||||
// }
|
||||
// works if cursor is after screen
|
||||
let relative_rows = window
|
||||
.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
|
||||
// #[gpui::test]
|
||||
// async fn test_vim_visual_selections(cx: &mut TestAppContext) {
|
||||
// init_test(cx, |_| {});
|
||||
element.calculate_relative_line_numbers(&snapshot, &(0..3), Some(6))
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(relative_rows.len(), 3);
|
||||
assert_eq!(relative_rows[&0], 5);
|
||||
assert_eq!(relative_rows[&1], 4);
|
||||
assert_eq!(relative_rows[&2], 3);
|
||||
}
|
||||
|
||||
// let editor = cx
|
||||
// .add_window(|cx| {
|
||||
// let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
|
||||
// Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
// })
|
||||
// .root(cx);
|
||||
// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
// let (_, state) = editor.update(cx, |editor, cx| {
|
||||
// editor.cursor_shape = CursorShape::Block;
|
||||
// editor.change_selections(None, cx, |s| {
|
||||
// s.select_ranges([
|
||||
// Point::new(0, 0)..Point::new(1, 0),
|
||||
// Point::new(3, 2)..Point::new(3, 3),
|
||||
// Point::new(5, 6)..Point::new(6, 0),
|
||||
// ]);
|
||||
// });
|
||||
// element.layout(
|
||||
// SizeConstraint::new(point(500., 500.), point(500., 500.)),
|
||||
// editor,
|
||||
// cx,
|
||||
// )
|
||||
// });
|
||||
// assert_eq!(state.selections.len(), 1);
|
||||
// let local_selections = &state.selections[0].1;
|
||||
// assert_eq!(local_selections.len(), 3);
|
||||
// // moves cursor back one line
|
||||
// assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
|
||||
// assert_eq!(
|
||||
// local_selections[0].range,
|
||||
// DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
|
||||
// );
|
||||
#[gpui::test]
|
||||
async fn test_vim_visual_selections(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// // moves cursor back one column
|
||||
// assert_eq!(
|
||||
// local_selections[1].range,
|
||||
// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
|
||||
// );
|
||||
// assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&(sample_text(6, 6, 'a') + "\n"), cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, cx)
|
||||
});
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
|
||||
// // leaves cursor on the max point
|
||||
// assert_eq!(
|
||||
// local_selections[2].range,
|
||||
// DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
|
||||
// );
|
||||
// assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
editor.cursor_shape = CursorShape::Block;
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([
|
||||
Point::new(0, 0)..Point::new(1, 0),
|
||||
Point::new(3, 2)..Point::new(3, 3),
|
||||
Point::new(5, 6)..Point::new(6, 0),
|
||||
]);
|
||||
});
|
||||
})
|
||||
.unwrap();
|
||||
let state = cx
|
||||
.update_window(window.into(), |_, cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// // active lines does not include 1 (even though the range of the selection does)
|
||||
// assert_eq!(
|
||||
// state.active_rows.keys().cloned().collect::<Vec<u32>>(),
|
||||
// vec![0, 3, 5, 6]
|
||||
// );
|
||||
assert_eq!(state.selections.len(), 1);
|
||||
let local_selections = &state.selections[0].1;
|
||||
assert_eq!(local_selections.len(), 3);
|
||||
// moves cursor back one line
|
||||
assert_eq!(local_selections[0].head, DisplayPoint::new(0, 6));
|
||||
assert_eq!(
|
||||
local_selections[0].range,
|
||||
DisplayPoint::new(0, 0)..DisplayPoint::new(1, 0)
|
||||
);
|
||||
|
||||
// // multi-buffer support
|
||||
// // in DisplayPoint co-ordinates, this is what we're dealing with:
|
||||
// // 0: [[file
|
||||
// // 1: header]]
|
||||
// // 2: aaaaaa
|
||||
// // 3: bbbbbb
|
||||
// // 4: cccccc
|
||||
// // 5:
|
||||
// // 6: ...
|
||||
// // 7: ffffff
|
||||
// // 8: gggggg
|
||||
// // 9: hhhhhh
|
||||
// // 10:
|
||||
// // 11: [[file
|
||||
// // 12: header]]
|
||||
// // 13: bbbbbb
|
||||
// // 14: cccccc
|
||||
// // 15: dddddd
|
||||
// let editor = cx
|
||||
// .add_window(|cx| {
|
||||
// let buffer = MultiBuffer::build_multi(
|
||||
// [
|
||||
// (
|
||||
// &(sample_text(8, 6, 'a') + "\n"),
|
||||
// vec![
|
||||
// Point::new(0, 0)..Point::new(3, 0),
|
||||
// Point::new(4, 0)..Point::new(7, 0),
|
||||
// ],
|
||||
// ),
|
||||
// (
|
||||
// &(sample_text(8, 6, 'a') + "\n"),
|
||||
// vec![Point::new(1, 0)..Point::new(3, 0)],
|
||||
// ),
|
||||
// ],
|
||||
// cx,
|
||||
// );
|
||||
// Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
// })
|
||||
// .root(cx);
|
||||
// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
// let (_, state) = editor.update(cx, |editor, cx| {
|
||||
// editor.cursor_shape = CursorShape::Block;
|
||||
// editor.change_selections(None, cx, |s| {
|
||||
// s.select_display_ranges([
|
||||
// DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
|
||||
// DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
|
||||
// ]);
|
||||
// });
|
||||
// element.layout(
|
||||
// SizeConstraint::new(point(500., 500.), point(500., 500.)),
|
||||
// editor,
|
||||
// cx,
|
||||
// )
|
||||
// });
|
||||
// moves cursor back one column
|
||||
assert_eq!(
|
||||
local_selections[1].range,
|
||||
DisplayPoint::new(3, 2)..DisplayPoint::new(3, 3)
|
||||
);
|
||||
assert_eq!(local_selections[1].head, DisplayPoint::new(3, 2));
|
||||
|
||||
// assert_eq!(state.selections.len(), 1);
|
||||
// let local_selections = &state.selections[0].1;
|
||||
// assert_eq!(local_selections.len(), 2);
|
||||
// leaves cursor on the max point
|
||||
assert_eq!(
|
||||
local_selections[2].range,
|
||||
DisplayPoint::new(5, 6)..DisplayPoint::new(6, 0)
|
||||
);
|
||||
assert_eq!(local_selections[2].head, DisplayPoint::new(6, 0));
|
||||
|
||||
// // moves cursor on excerpt boundary back a line
|
||||
// // and doesn't allow selection to bleed through
|
||||
// assert_eq!(
|
||||
// local_selections[0].range,
|
||||
// DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
|
||||
// );
|
||||
// assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
|
||||
// active lines does not include 1 (even though the range of the selection does)
|
||||
assert_eq!(
|
||||
state.active_rows.keys().cloned().collect::<Vec<u32>>(),
|
||||
vec![0, 3, 5, 6]
|
||||
);
|
||||
|
||||
// // moves cursor on buffer boundary back two lines
|
||||
// // and doesn't allow selection to bleed through
|
||||
// assert_eq!(
|
||||
// local_selections[1].range,
|
||||
// DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
|
||||
// );
|
||||
// assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
|
||||
// }
|
||||
// multi-buffer support
|
||||
// in DisplayPoint co-ordinates, this is what we're dealing with:
|
||||
// 0: [[file
|
||||
// 1: header]]
|
||||
// 2: aaaaaa
|
||||
// 3: bbbbbb
|
||||
// 4: cccccc
|
||||
// 5:
|
||||
// 6: ...
|
||||
// 7: ffffff
|
||||
// 8: gggggg
|
||||
// 9: hhhhhh
|
||||
// 10:
|
||||
// 11: [[file
|
||||
// 12: header]]
|
||||
// 13: bbbbbb
|
||||
// 14: cccccc
|
||||
// 15: dddddd
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_multi(
|
||||
[
|
||||
(
|
||||
&(sample_text(8, 6, 'a') + "\n"),
|
||||
vec![
|
||||
Point::new(0, 0)..Point::new(3, 0),
|
||||
Point::new(4, 0)..Point::new(7, 0),
|
||||
],
|
||||
),
|
||||
(
|
||||
&(sample_text(8, 6, 'a') + "\n"),
|
||||
vec![Point::new(1, 0)..Point::new(3, 0)],
|
||||
),
|
||||
],
|
||||
cx,
|
||||
);
|
||||
Editor::new(EditorMode::Full, buffer, None, cx)
|
||||
});
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
let state = window.update(cx, |editor, cx| {
|
||||
editor.cursor_shape = CursorShape::Block;
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_display_ranges([
|
||||
DisplayPoint::new(4, 0)..DisplayPoint::new(7, 0),
|
||||
DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
|
||||
// init_test(cx, |_| {});
|
||||
let state = cx
|
||||
.update_window(window.into(), |_, cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(state.selections.len(), 1);
|
||||
let local_selections = &state.selections[0].1;
|
||||
assert_eq!(local_selections.len(), 2);
|
||||
|
||||
// let editor = cx
|
||||
// .add_window(|cx| {
|
||||
// let buffer = MultiBuffer::build_simple("", cx);
|
||||
// Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
// })
|
||||
// .root(cx);
|
||||
// moves cursor on excerpt boundary back a line
|
||||
// and doesn't allow selection to bleed through
|
||||
assert_eq!(
|
||||
local_selections[0].range,
|
||||
DisplayPoint::new(4, 0)..DisplayPoint::new(6, 0)
|
||||
);
|
||||
assert_eq!(local_selections[0].head, DisplayPoint::new(5, 0));
|
||||
dbg!("Hi");
|
||||
// moves cursor on buffer boundary back two lines
|
||||
// and doesn't allow selection to bleed through
|
||||
assert_eq!(
|
||||
local_selections[1].range,
|
||||
DisplayPoint::new(10, 0)..DisplayPoint::new(11, 0)
|
||||
);
|
||||
assert_eq!(local_selections[1].head, DisplayPoint::new(10, 0));
|
||||
}
|
||||
|
||||
// editor.update(cx, |editor, cx| {
|
||||
// editor.set_placeholder_text("hello", cx);
|
||||
// editor.insert_blocks(
|
||||
// [BlockProperties {
|
||||
// style: BlockStyle::Fixed,
|
||||
// disposition: BlockDisposition::Above,
|
||||
// height: 3,
|
||||
// position: Anchor::min(),
|
||||
// render: Arc::new(|_| Empty::new().into_any),
|
||||
// }],
|
||||
// None,
|
||||
// cx,
|
||||
// );
|
||||
#[gpui::test]
|
||||
fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// // Blur the editor so that it displays placeholder text.
|
||||
// cx.blur();
|
||||
// });
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple("", cx);
|
||||
Editor::new(EditorMode::Full, buffer, None, cx)
|
||||
});
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
editor.set_placeholder_text("hello", cx);
|
||||
editor.insert_blocks(
|
||||
[BlockProperties {
|
||||
style: BlockStyle::Fixed,
|
||||
disposition: BlockDisposition::Above,
|
||||
height: 3,
|
||||
position: Anchor::min(),
|
||||
render: Arc::new(|_| div().into_any()),
|
||||
}],
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
|
||||
// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
// let (size, mut state) = editor.update(cx, |editor, cx| {
|
||||
// element.layout(
|
||||
// SizeConstraint::new(point(500., 500.), point(500., 500.)),
|
||||
// editor,
|
||||
// cx,
|
||||
// )
|
||||
// });
|
||||
// Blur the editor so that it displays placeholder text.
|
||||
cx.blur();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// assert_eq!(state.position_map.line_layouts.len(), 4);
|
||||
// assert_eq!(
|
||||
// state
|
||||
// .line_number_layouts
|
||||
// .iter()
|
||||
// .map(Option::is_some)
|
||||
// .collect::<Vec<_>>(),
|
||||
// &[false, false, false, true]
|
||||
// );
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
let mut state = cx
|
||||
.update_window(window.into(), |_, cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
let size = state.position_map.size;
|
||||
|
||||
// // Don't panic.
|
||||
// let bounds = Bounds::<Pixels>::new(Default::default(), size);
|
||||
// editor.update(cx, |editor, cx| {
|
||||
// element.paint(bounds, bounds, &mut state, editor, cx);
|
||||
// });
|
||||
// }
|
||||
assert_eq!(state.position_map.line_layouts.len(), 4);
|
||||
assert_eq!(
|
||||
state
|
||||
.line_numbers
|
||||
.iter()
|
||||
.map(Option::is_some)
|
||||
.collect::<Vec<_>>(),
|
||||
&[false, false, false, true]
|
||||
);
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
|
||||
// const TAB_SIZE: u32 = 4;
|
||||
// Don't panic.
|
||||
let bounds = Bounds::<Pixels>::new(Default::default(), size);
|
||||
cx.update_window(window.into(), |_, cx| {
|
||||
element.paint(bounds, &mut (), cx);
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// let input_text = "\t \t|\t| a b";
|
||||
// let expected_invisibles = vec![
|
||||
// Invisible::Tab {
|
||||
// line_start_offset: 0,
|
||||
// },
|
||||
// Invisible::Whitespace {
|
||||
// line_offset: TAB_SIZE as usize,
|
||||
// },
|
||||
// Invisible::Tab {
|
||||
// line_start_offset: TAB_SIZE as usize + 1,
|
||||
// },
|
||||
// Invisible::Tab {
|
||||
// line_start_offset: TAB_SIZE as usize * 2 + 1,
|
||||
// },
|
||||
// Invisible::Whitespace {
|
||||
// line_offset: TAB_SIZE as usize * 3 + 1,
|
||||
// },
|
||||
// Invisible::Whitespace {
|
||||
// line_offset: TAB_SIZE as usize * 3 + 3,
|
||||
// },
|
||||
// ];
|
||||
// assert_eq!(
|
||||
// expected_invisibles.len(),
|
||||
// input_text
|
||||
// .chars()
|
||||
// .filter(|initial_char| initial_char.is_whitespace())
|
||||
// .count(),
|
||||
// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
|
||||
// );
|
||||
#[gpui::test]
|
||||
fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
|
||||
const TAB_SIZE: u32 = 4;
|
||||
|
||||
// init_test(cx, |s| {
|
||||
// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
// s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
|
||||
// });
|
||||
let input_text = "\t \t|\t| a b";
|
||||
let expected_invisibles = vec![
|
||||
Invisible::Tab {
|
||||
line_start_offset: 0,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: TAB_SIZE as usize,
|
||||
},
|
||||
Invisible::Tab {
|
||||
line_start_offset: TAB_SIZE as usize + 1,
|
||||
},
|
||||
Invisible::Tab {
|
||||
line_start_offset: TAB_SIZE as usize * 2 + 1,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: TAB_SIZE as usize * 3 + 1,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: TAB_SIZE as usize * 3 + 3,
|
||||
},
|
||||
];
|
||||
assert_eq!(
|
||||
expected_invisibles.len(),
|
||||
input_text
|
||||
.chars()
|
||||
.filter(|initial_char| initial_char.is_whitespace())
|
||||
.count(),
|
||||
"Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
|
||||
);
|
||||
|
||||
// let actual_invisibles =
|
||||
// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
|
||||
init_test(cx, |s| {
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
|
||||
});
|
||||
|
||||
// assert_eq!(expected_invisibles, actual_invisibles);
|
||||
// }
|
||||
let actual_invisibles =
|
||||
collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, px(500.0));
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
|
||||
// init_test(cx, |s| {
|
||||
// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
// s.defaults.tab_size = NonZeroU32::new(4);
|
||||
// });
|
||||
assert_eq!(expected_invisibles, actual_invisibles);
|
||||
}
|
||||
|
||||
// for editor_mode_without_invisibles in [
|
||||
// EditorMode::SingleLine,
|
||||
// EditorMode::AutoHeight { max_lines: 100 },
|
||||
// ] {
|
||||
// let invisibles = collect_invisibles_from_new_editor(
|
||||
// cx,
|
||||
// editor_mode_without_invisibles,
|
||||
// "\t\t\t| | a b",
|
||||
// 500.0,
|
||||
// );
|
||||
// assert!(invisibles.is_empty,
|
||||
// "For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
|
||||
// }
|
||||
// }
|
||||
#[gpui::test]
|
||||
fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
|
||||
init_test(cx, |s| {
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.tab_size = NonZeroU32::new(4);
|
||||
});
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
|
||||
// let tab_size = 4;
|
||||
// let input_text = "a\tbcd ".repeat(9);
|
||||
// let repeated_invisibles = [
|
||||
// Invisible::Tab {
|
||||
// line_start_offset: 1,
|
||||
// },
|
||||
// Invisible::Whitespace {
|
||||
// line_offset: tab_size as usize + 3,
|
||||
// },
|
||||
// Invisible::Whitespace {
|
||||
// line_offset: tab_size as usize + 4,
|
||||
// },
|
||||
// Invisible::Whitespace {
|
||||
// line_offset: tab_size as usize + 5,
|
||||
// },
|
||||
// ];
|
||||
// let expected_invisibles = std::iter::once(repeated_invisibles)
|
||||
// .cycle()
|
||||
// .take(9)
|
||||
// .flatten()
|
||||
// .collect::<Vec<_>>();
|
||||
// assert_eq!(
|
||||
// expected_invisibles.len(),
|
||||
// input_text
|
||||
// .chars()
|
||||
// .filter(|initial_char| initial_char.is_whitespace())
|
||||
// .count(),
|
||||
// "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
|
||||
// );
|
||||
// info!("Expected invisibles: {expected_invisibles:?}");
|
||||
for editor_mode_without_invisibles in [
|
||||
EditorMode::SingleLine,
|
||||
EditorMode::AutoHeight { max_lines: 100 },
|
||||
] {
|
||||
let invisibles = collect_invisibles_from_new_editor(
|
||||
cx,
|
||||
editor_mode_without_invisibles,
|
||||
"\t\t\t| | a b",
|
||||
px(500.0),
|
||||
);
|
||||
assert!(invisibles.is_empty(),
|
||||
"For editor mode {editor_mode_without_invisibles:?} no invisibles was expected but got {invisibles:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// init_test(cx, |_| {});
|
||||
#[gpui::test]
|
||||
fn test_wrapped_invisibles_drawing(cx: &mut TestAppContext) {
|
||||
let tab_size = 4;
|
||||
let input_text = "a\tbcd ".repeat(9);
|
||||
let repeated_invisibles = [
|
||||
Invisible::Tab {
|
||||
line_start_offset: 1,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: tab_size as usize + 3,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: tab_size as usize + 4,
|
||||
},
|
||||
Invisible::Whitespace {
|
||||
line_offset: tab_size as usize + 5,
|
||||
},
|
||||
];
|
||||
let expected_invisibles = std::iter::once(repeated_invisibles)
|
||||
.cycle()
|
||||
.take(9)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
expected_invisibles.len(),
|
||||
input_text
|
||||
.chars()
|
||||
.filter(|initial_char| initial_char.is_whitespace())
|
||||
.count(),
|
||||
"Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
|
||||
);
|
||||
info!("Expected invisibles: {expected_invisibles:?}");
|
||||
|
||||
// // Put the same string with repeating whitespace pattern into editors of various size,
|
||||
// // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
|
||||
// let resize_step = 10.0;
|
||||
// let mut editor_width = 200.0;
|
||||
// while editor_width <= 1000.0 {
|
||||
// update_test_language_settings(cx, |s| {
|
||||
// s.defaults.tab_size = NonZeroU32::new(tab_size);
|
||||
// s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
// s.defaults.preferred_line_length = Some(editor_width as u32);
|
||||
// s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
|
||||
// });
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// let actual_invisibles =
|
||||
// collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, editor_width);
|
||||
// Put the same string with repeating whitespace pattern into editors of various size,
|
||||
// take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
|
||||
let resize_step = 10.0;
|
||||
let mut editor_width = 200.0;
|
||||
while editor_width <= 1000.0 {
|
||||
update_test_language_settings(cx, |s| {
|
||||
s.defaults.tab_size = NonZeroU32::new(tab_size);
|
||||
s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
|
||||
s.defaults.preferred_line_length = Some(editor_width as u32);
|
||||
s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
|
||||
});
|
||||
|
||||
// // Whatever the editor size is, ensure it has the same invisible kinds in the same order
|
||||
// // (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
|
||||
// let mut i = 0;
|
||||
// for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
|
||||
// i = actual_index;
|
||||
// match expected_invisibles.get(i) {
|
||||
// Some(expected_invisible) => match (expected_invisible, actual_invisible) {
|
||||
// (Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
|
||||
// | (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
|
||||
// _ => {
|
||||
// panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
|
||||
// }
|
||||
// },
|
||||
// None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
|
||||
// }
|
||||
// }
|
||||
// let missing_expected_invisibles = &expected_invisibles[i + 1..];
|
||||
// assert!(
|
||||
// missing_expected_invisibles.is_empty,
|
||||
// "Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
|
||||
// );
|
||||
let actual_invisibles = collect_invisibles_from_new_editor(
|
||||
cx,
|
||||
EditorMode::Full,
|
||||
&input_text,
|
||||
px(editor_width),
|
||||
);
|
||||
|
||||
// editor_width += resize_step;
|
||||
// }
|
||||
// }
|
||||
// Whatever the editor size is, ensure it has the same invisible kinds in the same order
|
||||
// (no good guarantees about the offsets: wrapping could trigger padding and its tests should check the offsets).
|
||||
let mut i = 0;
|
||||
for (actual_index, actual_invisible) in actual_invisibles.iter().enumerate() {
|
||||
i = actual_index;
|
||||
match expected_invisibles.get(i) {
|
||||
Some(expected_invisible) => match (expected_invisible, actual_invisible) {
|
||||
(Invisible::Whitespace { .. }, Invisible::Whitespace { .. })
|
||||
| (Invisible::Tab { .. }, Invisible::Tab { .. }) => {}
|
||||
_ => {
|
||||
panic!("At index {i}, expected invisible {expected_invisible:?} does not match actual {actual_invisible:?} by kind. Actual invisibles: {actual_invisibles:?}")
|
||||
}
|
||||
},
|
||||
None => panic!("Unexpected extra invisible {actual_invisible:?} at index {i}"),
|
||||
}
|
||||
}
|
||||
let missing_expected_invisibles = &expected_invisibles[i + 1..];
|
||||
assert!(
|
||||
missing_expected_invisibles.is_empty(),
|
||||
"Missing expected invisibles after index {i}: {missing_expected_invisibles:?}"
|
||||
);
|
||||
|
||||
// fn collect_invisibles_from_new_editor(
|
||||
// cx: &mut TestAppContext,
|
||||
// editor_mode: EditorMode,
|
||||
// input_text: &str,
|
||||
// editor_width: f32,
|
||||
// ) -> Vec<Invisible> {
|
||||
// info!(
|
||||
// "Creating editor with mode {editor_mode:?}, width {editor_width} and text '{input_text}'"
|
||||
// );
|
||||
// let editor = cx
|
||||
// .add_window(|cx| {
|
||||
// let buffer = MultiBuffer::build_simple(&input_text, cx);
|
||||
// Editor::new(editor_mode, buffer, None, None, cx)
|
||||
// })
|
||||
// .root(cx);
|
||||
editor_width += resize_step;
|
||||
}
|
||||
}
|
||||
|
||||
// let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
|
||||
// let (_, layout_state) = editor.update(cx, |editor, cx| {
|
||||
// editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
|
||||
// editor.set_wrap_width(Some(editor_width), cx);
|
||||
fn collect_invisibles_from_new_editor(
|
||||
cx: &mut TestAppContext,
|
||||
editor_mode: EditorMode,
|
||||
input_text: &str,
|
||||
editor_width: Pixels,
|
||||
) -> Vec<Invisible> {
|
||||
info!(
|
||||
"Creating editor with mode {editor_mode:?}, width {}px and text '{input_text}'",
|
||||
editor_width.0
|
||||
);
|
||||
let window = cx.add_window(|cx| {
|
||||
let buffer = MultiBuffer::build_simple(&input_text, cx);
|
||||
Editor::new(editor_mode, buffer, None, cx)
|
||||
});
|
||||
let editor = window.root(cx).unwrap();
|
||||
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
|
||||
let mut element = EditorElement::new(&editor, style);
|
||||
window
|
||||
.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor.set_wrap_width(Some(editor_width), cx);
|
||||
})
|
||||
.unwrap();
|
||||
let layout_state = cx
|
||||
.update_window(window.into(), |_, cx| {
|
||||
element.compute_layout(
|
||||
Bounds {
|
||||
origin: point(px(500.), px(500.)),
|
||||
size: size(px(500.), px(500.)),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// element.layout(
|
||||
// SizeConstraint::new(point(editor_width, 500.), point(editor_width, 500.)),
|
||||
// editor,
|
||||
// cx,
|
||||
// )
|
||||
// });
|
||||
|
||||
// layout_state
|
||||
// .position_map
|
||||
// .line_layouts
|
||||
// .iter()
|
||||
// .map(|line_with_invisibles| &line_with_invisibles.invisibles)
|
||||
// .flatten()
|
||||
// .cloned()
|
||||
// .collect()
|
||||
// }
|
||||
// }
|
||||
layout_state
|
||||
.position_map
|
||||
.line_layouts
|
||||
.iter()
|
||||
.map(|line_with_invisibles| &line_with_invisibles.invisibles)
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_action<T: Action>(
|
||||
view: &View<Editor>,
|
||||
|
@ -88,195 +88,195 @@ pub fn diff_hunk_to_display(hunk: DiffHunk<u32>, snapshot: &DisplaySnapshot) ->
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(any(test, feature = "test_support"))]
|
||||
// mod tests {
|
||||
// // use crate::editor_tests::init_test;
|
||||
// use crate::Point;
|
||||
// use gpui::TestAppContext;
|
||||
// use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||
// use project::{FakeFs, Project};
|
||||
// use unindent::Unindent;
|
||||
// #[gpui::test]
|
||||
// async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
|
||||
// use git::diff::DiffHunkStatus;
|
||||
// init_test(cx, |_| {});
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::editor_tests::init_test;
|
||||
use crate::Point;
|
||||
use gpui::{Context, TestAppContext};
|
||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||
use project::{FakeFs, Project};
|
||||
use unindent::Unindent;
|
||||
#[gpui::test]
|
||||
async fn test_diff_hunks_in_range(cx: &mut TestAppContext) {
|
||||
use git::diff::DiffHunkStatus;
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// let fs = FakeFs::new(cx.background());
|
||||
// let project = Project::test(fs, [], cx).await;
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
// // buffer has two modified hunks with two rows each
|
||||
// let buffer_1 = project
|
||||
// .update(cx, |project, cx| {
|
||||
// project.create_buffer(
|
||||
// "
|
||||
// 1.zero
|
||||
// 1.ONE
|
||||
// 1.TWO
|
||||
// 1.three
|
||||
// 1.FOUR
|
||||
// 1.FIVE
|
||||
// 1.six
|
||||
// "
|
||||
// .unindent()
|
||||
// .as_str(),
|
||||
// None,
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
// .unwrap();
|
||||
// buffer_1.update(cx, |buffer, cx| {
|
||||
// buffer.set_diff_base(
|
||||
// Some(
|
||||
// "
|
||||
// 1.zero
|
||||
// 1.one
|
||||
// 1.two
|
||||
// 1.three
|
||||
// 1.four
|
||||
// 1.five
|
||||
// 1.six
|
||||
// "
|
||||
// .unindent(),
|
||||
// ),
|
||||
// cx,
|
||||
// );
|
||||
// });
|
||||
// buffer has two modified hunks with two rows each
|
||||
let buffer_1 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_buffer(
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
1.TWO
|
||||
1.three
|
||||
1.FOUR
|
||||
1.FIVE
|
||||
1.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
buffer_1.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
1.zero
|
||||
1.one
|
||||
1.two
|
||||
1.three
|
||||
1.four
|
||||
1.five
|
||||
1.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// // buffer has a deletion hunk and an insertion hunk
|
||||
// let buffer_2 = project
|
||||
// .update(cx, |project, cx| {
|
||||
// project.create_buffer(
|
||||
// "
|
||||
// 2.zero
|
||||
// 2.one
|
||||
// 2.two
|
||||
// 2.three
|
||||
// 2.four
|
||||
// 2.five
|
||||
// 2.six
|
||||
// "
|
||||
// .unindent()
|
||||
// .as_str(),
|
||||
// None,
|
||||
// cx,
|
||||
// )
|
||||
// })
|
||||
// .unwrap();
|
||||
// buffer_2.update(cx, |buffer, cx| {
|
||||
// buffer.set_diff_base(
|
||||
// Some(
|
||||
// "
|
||||
// 2.zero
|
||||
// 2.one
|
||||
// 2.one-and-a-half
|
||||
// 2.two
|
||||
// 2.three
|
||||
// 2.four
|
||||
// 2.six
|
||||
// "
|
||||
// .unindent(),
|
||||
// ),
|
||||
// cx,
|
||||
// );
|
||||
// });
|
||||
// buffer has a deletion hunk and an insertion hunk
|
||||
let buffer_2 = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_buffer(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.five
|
||||
2.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
buffer_2.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.one-and-a-half
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// cx.foreground().run_until_parked();
|
||||
cx.background_executor.run_until_parked();
|
||||
|
||||
// let multibuffer = cx.add_model(|cx| {
|
||||
// let mut multibuffer = MultiBuffer::new(0);
|
||||
// multibuffer.push_excerpts(
|
||||
// buffer_1.clone(),
|
||||
// [
|
||||
// // excerpt ends in the middle of a modified hunk
|
||||
// ExcerptRange {
|
||||
// context: Point::new(0, 0)..Point::new(1, 5),
|
||||
// primary: Default::default(),
|
||||
// },
|
||||
// // excerpt begins in the middle of a modified hunk
|
||||
// ExcerptRange {
|
||||
// context: Point::new(5, 0)..Point::new(6, 5),
|
||||
// primary: Default::default(),
|
||||
// },
|
||||
// ],
|
||||
// cx,
|
||||
// );
|
||||
// multibuffer.push_excerpts(
|
||||
// buffer_2.clone(),
|
||||
// [
|
||||
// // excerpt ends at a deletion
|
||||
// ExcerptRange {
|
||||
// context: Point::new(0, 0)..Point::new(1, 5),
|
||||
// primary: Default::default(),
|
||||
// },
|
||||
// // excerpt starts at a deletion
|
||||
// ExcerptRange {
|
||||
// context: Point::new(2, 0)..Point::new(2, 5),
|
||||
// primary: Default::default(),
|
||||
// },
|
||||
// // excerpt fully contains a deletion hunk
|
||||
// ExcerptRange {
|
||||
// context: Point::new(1, 0)..Point::new(2, 5),
|
||||
// primary: Default::default(),
|
||||
// },
|
||||
// // excerpt fully contains an insertion hunk
|
||||
// ExcerptRange {
|
||||
// context: Point::new(4, 0)..Point::new(6, 5),
|
||||
// primary: Default::default(),
|
||||
// },
|
||||
// ],
|
||||
// cx,
|
||||
// );
|
||||
// multibuffer
|
||||
// });
|
||||
let multibuffer = cx.build_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_1.clone(),
|
||||
[
|
||||
// excerpt ends in the middle of a modified hunk
|
||||
ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(1, 5),
|
||||
primary: Default::default(),
|
||||
},
|
||||
// excerpt begins in the middle of a modified hunk
|
||||
ExcerptRange {
|
||||
context: Point::new(5, 0)..Point::new(6, 5),
|
||||
primary: Default::default(),
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
multibuffer.push_excerpts(
|
||||
buffer_2.clone(),
|
||||
[
|
||||
// excerpt ends at a deletion
|
||||
ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(1, 5),
|
||||
primary: Default::default(),
|
||||
},
|
||||
// excerpt starts at a deletion
|
||||
ExcerptRange {
|
||||
context: Point::new(2, 0)..Point::new(2, 5),
|
||||
primary: Default::default(),
|
||||
},
|
||||
// excerpt fully contains a deletion hunk
|
||||
ExcerptRange {
|
||||
context: Point::new(1, 0)..Point::new(2, 5),
|
||||
primary: Default::default(),
|
||||
},
|
||||
// excerpt fully contains an insertion hunk
|
||||
ExcerptRange {
|
||||
context: Point::new(4, 0)..Point::new(6, 5),
|
||||
primary: Default::default(),
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
|
||||
// let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
|
||||
let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
|
||||
|
||||
// assert_eq!(
|
||||
// snapshot.text(),
|
||||
// "
|
||||
// 1.zero
|
||||
// 1.ONE
|
||||
// 1.FIVE
|
||||
// 1.six
|
||||
// 2.zero
|
||||
// 2.one
|
||||
// 2.two
|
||||
// 2.one
|
||||
// 2.two
|
||||
// 2.four
|
||||
// 2.five
|
||||
// 2.six"
|
||||
// .unindent()
|
||||
// );
|
||||
assert_eq!(
|
||||
snapshot.text(),
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
1.FIVE
|
||||
1.six
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.one
|
||||
2.two
|
||||
2.four
|
||||
2.five
|
||||
2.six"
|
||||
.unindent()
|
||||
);
|
||||
|
||||
// let expected = [
|
||||
// (DiffHunkStatus::Modified, 1..2),
|
||||
// (DiffHunkStatus::Modified, 2..3),
|
||||
// //TODO: Define better when and where removed hunks show up at range extremities
|
||||
// (DiffHunkStatus::Removed, 6..6),
|
||||
// (DiffHunkStatus::Removed, 8..8),
|
||||
// (DiffHunkStatus::Added, 10..11),
|
||||
// ];
|
||||
let expected = [
|
||||
(DiffHunkStatus::Modified, 1..2),
|
||||
(DiffHunkStatus::Modified, 2..3),
|
||||
//TODO: Define better when and where removed hunks show up at range extremities
|
||||
(DiffHunkStatus::Removed, 6..6),
|
||||
(DiffHunkStatus::Removed, 8..8),
|
||||
(DiffHunkStatus::Added, 10..11),
|
||||
];
|
||||
|
||||
// assert_eq!(
|
||||
// snapshot
|
||||
// .git_diff_hunks_in_range(0..12)
|
||||
// .map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
// .collect::<Vec<_>>(),
|
||||
// &expected,
|
||||
// );
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range(0..12)
|
||||
.map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected,
|
||||
);
|
||||
|
||||
// assert_eq!(
|
||||
// snapshot
|
||||
// .git_diff_hunks_in_range_rev(0..12)
|
||||
// .map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
// .collect::<Vec<_>>(),
|
||||
// expected
|
||||
// .iter()
|
||||
// .rev()
|
||||
// .cloned()
|
||||
// .collect::<Vec<_>>()
|
||||
// .as_slice(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range_rev(0..12)
|
||||
.map(|hunk| (hunk.status(), hunk.buffer_range))
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
.iter()
|
||||
.rev()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::{Editor, RangeToAnchorExt};
|
||||
enum MatchingBracketHighlight {}
|
||||
|
||||
pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewContext<Editor>) {
|
||||
// editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
|
||||
editor.clear_background_highlights::<MatchingBracketHighlight>(cx);
|
||||
|
||||
let newest_selection = editor.selections.newest::<usize>(cx);
|
||||
// Don't highlight brackets if the selection isn't empty
|
||||
@ -30,109 +30,109 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
// use indoc::indoc;
|
||||
// use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
|
||||
|
||||
// #[gpui::test]
|
||||
// async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
|
||||
// init_test(cx, |_| {});
|
||||
#[gpui::test]
|
||||
async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// let mut cx = EditorLspTestContext::new(
|
||||
// Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// brackets: BracketPairConfig {
|
||||
// pairs: vec![
|
||||
// BracketPair {
|
||||
// start: "{".to_string(),
|
||||
// end: "}".to_string(),
|
||||
// close: false,
|
||||
// newline: true,
|
||||
// },
|
||||
// BracketPair {
|
||||
// start: "(".to_string(),
|
||||
// end: ")".to_string(),
|
||||
// close: false,
|
||||
// newline: true,
|
||||
// },
|
||||
// ],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// )
|
||||
// .with_brackets_query(indoc! {r#"
|
||||
// ("{" @open "}" @close)
|
||||
// ("(" @open ")" @close)
|
||||
// "#})
|
||||
// .unwrap(),
|
||||
// Default::default(),
|
||||
// cx,
|
||||
// )
|
||||
// .await;
|
||||
let mut cx = EditorLspTestContext::new(
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
brackets: BracketPairConfig {
|
||||
pairs: vec![
|
||||
BracketPair {
|
||||
start: "{".to_string(),
|
||||
end: "}".to_string(),
|
||||
close: false,
|
||||
newline: true,
|
||||
},
|
||||
BracketPair {
|
||||
start: "(".to_string(),
|
||||
end: ")".to_string(),
|
||||
close: false,
|
||||
newline: true,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_brackets_query(indoc! {r#"
|
||||
("{" @open "}" @close)
|
||||
("(" @open ")" @close)
|
||||
"#})
|
||||
.unwrap(),
|
||||
Default::default(),
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// // positioning cursor inside bracket highlights both
|
||||
// cx.set_state(indoc! {r#"
|
||||
// pub fn test("Test ˇargument") {
|
||||
// another_test(1, 2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
// pub fn test«(»"Test argument"«)» {
|
||||
// another_test(1, 2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// positioning cursor inside bracket highlights both
|
||||
cx.set_state(indoc! {r#"
|
||||
pub fn test("Test ˇargument") {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test«(»"Test argument"«)» {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
|
||||
// cx.set_state(indoc! {r#"
|
||||
// pub fn test("Test argument") {
|
||||
// another_test(1, ˇ2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
// pub fn test("Test argument") {
|
||||
// another_test«(»1, 2, 3«)»;
|
||||
// }
|
||||
// "#});
|
||||
cx.set_state(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
another_test(1, ˇ2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
another_test«(»1, 2, 3«)»;
|
||||
}
|
||||
"#});
|
||||
|
||||
// cx.set_state(indoc! {r#"
|
||||
// pub fn test("Test argument") {
|
||||
// anotherˇ_test(1, 2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
// pub fn test("Test argument") «{»
|
||||
// another_test(1, 2, 3);
|
||||
// «}»
|
||||
// "#});
|
||||
cx.set_state(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
anotherˇ_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") «{»
|
||||
another_test(1, 2, 3);
|
||||
«}»
|
||||
"#});
|
||||
|
||||
// // positioning outside of brackets removes highlight
|
||||
// cx.set_state(indoc! {r#"
|
||||
// pub fˇn test("Test argument") {
|
||||
// another_test(1, 2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
// pub fn test("Test argument") {
|
||||
// another_test(1, 2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// positioning outside of brackets removes highlight
|
||||
cx.set_state(indoc! {r#"
|
||||
pub fˇn test("Test argument") {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
|
||||
// // non empty selection dismisses highlight
|
||||
// cx.set_state(indoc! {r#"
|
||||
// pub fn test("Te«st argˇ»ument") {
|
||||
// another_test(1, 2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
// pub fn test("Test argument") {
|
||||
// another_test(1, 2, 3);
|
||||
// }
|
||||
// "#});
|
||||
// }
|
||||
// }
|
||||
// non empty selection dismisses highlight
|
||||
cx.set_state(indoc! {r#"
|
||||
pub fn test("Te«st argˇ»ument") {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
cx.assert_editor_background_highlights::<MatchingBracketHighlight>(indoc! {r#"
|
||||
pub fn test("Test argument") {
|
||||
another_test(1, 2, 3);
|
||||
}
|
||||
"#});
|
||||
}
|
||||
}
|
||||
|
@ -2432,13 +2432,13 @@ pub mod tests {
|
||||
let language = Arc::new(language);
|
||||
let fs = FakeFs::new(cx.background_executor.clone());
|
||||
fs.insert_tree(
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
|
||||
"other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
"/a",
|
||||
json!({
|
||||
"main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::<Vec<_>>().join("")),
|
||||
"other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::<Vec<_>>().join("")),
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/a".as_ref()], cx).await;
|
||||
project.update(cx, |project, _| {
|
||||
project.languages().add(Arc::clone(&language))
|
||||
@ -2598,24 +2598,22 @@ pub mod tests {
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
// todo!() there used to be no these hints, but new gpui2 presumably scrolls a bit farther
|
||||
// (or renders less?) note that tests below pass
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
];
|
||||
assert_eq!(
|
||||
expected_hints,
|
||||
cached_hint_labels(editor),
|
||||
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
|
||||
);
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
|
||||
});
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
];
|
||||
assert_eq!(
|
||||
expected_hints,
|
||||
cached_hint_labels(editor),
|
||||
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
|
||||
);
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::Next), cx, |s| {
|
||||
@ -2630,23 +2628,23 @@ pub mod tests {
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
editor.update(cx, |editor, cx| {
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
"other hint #0".to_string(),
|
||||
"other hint #1".to_string(),
|
||||
"other hint #2".to_string(),
|
||||
];
|
||||
assert_eq!(expected_hints, cached_hint_labels(editor),
|
||||
"With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
|
||||
"Due to every excerpt having one hint, we update cache per new excerpt scrolled");
|
||||
});
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
"other hint #0".to_string(),
|
||||
"other hint #1".to_string(),
|
||||
"other hint #2".to_string(),
|
||||
];
|
||||
assert_eq!(expected_hints, cached_hint_labels(editor),
|
||||
"With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
|
||||
"Due to every excerpt having one hint, we update cache per new excerpt scrolled");
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::Next), cx, |s| {
|
||||
@ -2658,26 +2656,26 @@ pub mod tests {
|
||||
));
|
||||
cx.executor().run_until_parked();
|
||||
let last_scroll_update_version = editor.update(cx, |editor, cx| {
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
"other hint #0".to_string(),
|
||||
"other hint #1".to_string(),
|
||||
"other hint #2".to_string(),
|
||||
"other hint #3".to_string(),
|
||||
"other hint #4".to_string(),
|
||||
"other hint #5".to_string(),
|
||||
];
|
||||
assert_eq!(expected_hints, cached_hint_labels(editor),
|
||||
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
|
||||
expected_hints.len()
|
||||
}).unwrap();
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
"other hint #0".to_string(),
|
||||
"other hint #1".to_string(),
|
||||
"other hint #2".to_string(),
|
||||
"other hint #3".to_string(),
|
||||
"other hint #4".to_string(),
|
||||
"other hint #5".to_string(),
|
||||
];
|
||||
assert_eq!(expected_hints, cached_hint_labels(editor),
|
||||
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
|
||||
expected_hints.len()
|
||||
}).unwrap();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::Next), cx, |s| {
|
||||
@ -2686,30 +2684,31 @@ pub mod tests {
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
editor.update(cx, |editor, cx| {
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
"other hint #0".to_string(),
|
||||
"other hint #1".to_string(),
|
||||
"other hint #2".to_string(),
|
||||
"other hint #3".to_string(),
|
||||
"other hint #4".to_string(),
|
||||
"other hint #5".to_string(),
|
||||
];
|
||||
assert_eq!(expected_hints, cached_hint_labels(editor),
|
||||
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
|
||||
});
|
||||
let expected_hints = vec![
|
||||
"main hint #0".to_string(),
|
||||
"main hint #1".to_string(),
|
||||
"main hint #2".to_string(),
|
||||
"main hint #3".to_string(),
|
||||
"main hint #4".to_string(),
|
||||
"main hint #5".to_string(),
|
||||
"other hint #0".to_string(),
|
||||
"other hint #1".to_string(),
|
||||
"other hint #2".to_string(),
|
||||
"other hint #3".to_string(),
|
||||
"other hint #4".to_string(),
|
||||
"other hint #5".to_string(),
|
||||
];
|
||||
assert_eq!(expected_hints, cached_hint_labels(editor),
|
||||
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
|
||||
});
|
||||
|
||||
editor_edited.store(true, Ordering::Release);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges([Point::new(56, 0)..Point::new(56, 0)])
|
||||
// TODO if this gets set to hint boundary (e.g. 56) we sometimes get an extra cache version bump, why?
|
||||
s.select_ranges([Point::new(57, 0)..Point::new(57, 0)])
|
||||
});
|
||||
editor.handle_input("++++more text++++", cx);
|
||||
});
|
||||
@ -2729,15 +2728,15 @@ pub mod tests {
|
||||
expected_hints,
|
||||
cached_hint_labels(editor),
|
||||
"After multibuffer edit, editor gets scolled back to the last selection; \
|
||||
all hints should be invalidated and requeried for all of its visible excerpts"
|
||||
all hints should be invalidated and requeried for all of its visible excerpts"
|
||||
);
|
||||
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
|
||||
|
||||
let current_cache_version = editor.inlay_hint_cache().version;
|
||||
let minimum_expected_version = last_scroll_update_version + expected_hints.len();
|
||||
assert!(
|
||||
current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1,
|
||||
"Due to every excerpt having one hint, cache should update per new excerpt received + 1 potential sporadic update"
|
||||
assert_eq!(
|
||||
current_cache_version,
|
||||
last_scroll_update_version + expected_hints.len(),
|
||||
"We should have updated cache N times == N of new hints arrived (separately from each excerpt)"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -68,42 +68,43 @@ pub fn deploy_context_menu(
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
// use indoc::indoc;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
|
||||
use indoc::indoc;
|
||||
|
||||
// #[gpui::test]
|
||||
// async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
|
||||
// init_test(cx, |_| {});
|
||||
#[gpui::test]
|
||||
async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
// let mut cx = EditorLspTestContext::new_rust(
|
||||
// lsp::ServerCapabilities {
|
||||
// hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// cx,
|
||||
// )
|
||||
// .await;
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
|
||||
// cx.set_state(indoc! {"
|
||||
// fn teˇst() {
|
||||
// do_work();
|
||||
// }
|
||||
// "});
|
||||
// let point = cx.display_point(indoc! {"
|
||||
// fn test() {
|
||||
// do_wˇork();
|
||||
// }
|
||||
// "});
|
||||
// cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx));
|
||||
cx.set_state(indoc! {"
|
||||
fn teˇst() {
|
||||
do_work();
|
||||
}
|
||||
"});
|
||||
let point = cx.display_point(indoc! {"
|
||||
fn test() {
|
||||
do_wˇork();
|
||||
}
|
||||
"});
|
||||
cx.editor(|editor, app| assert!(editor.mouse_context_menu.is_none()));
|
||||
cx.update_editor(|editor, cx| deploy_context_menu(editor, Default::default(), point, cx));
|
||||
|
||||
// cx.assert_editor_state(indoc! {"
|
||||
// fn test() {
|
||||
// do_wˇork();
|
||||
// }
|
||||
// "});
|
||||
// cx.editor(|editor, app| assert!(editor.mouse_context_menu.read(app).visible()));
|
||||
// }
|
||||
// }
|
||||
cx.assert_editor_state(indoc! {"
|
||||
fn test() {
|
||||
do_wˇork();
|
||||
}
|
||||
"});
|
||||
cx.editor(|editor, app| assert!(editor.mouse_context_menu.is_some()));
|
||||
}
|
||||
}
|
||||
|
@ -452,483 +452,475 @@ pub fn split_display_range_by_lines(
|
||||
result
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::{
|
||||
// display_map::Inlay,
|
||||
// test::{},
|
||||
// Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
|
||||
// };
|
||||
// use project::Project;
|
||||
// use settings::SettingsStore;
|
||||
// use util::post_inc;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
display_map::Inlay,
|
||||
test::{editor_test_context::EditorTestContext, marked_display_snapshot},
|
||||
Buffer, DisplayMap, ExcerptRange, InlayId, MultiBuffer,
|
||||
};
|
||||
use gpui::{font, Context as _};
|
||||
use project::Project;
|
||||
use settings::SettingsStore;
|
||||
use util::post_inc;
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_previous_word_start(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_previous_word_start(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
// assert_eq!(
|
||||
// previous_word_start(&snapshot, display_points[1]),
|
||||
// display_points[0]
|
||||
// );
|
||||
// }
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
previous_word_start(&snapshot, display_points[1]),
|
||||
display_points[0]
|
||||
);
|
||||
}
|
||||
|
||||
// assert("\nˇ ˇlorem", cx);
|
||||
// assert("ˇ\nˇ lorem", cx);
|
||||
// assert(" ˇloremˇ", cx);
|
||||
// assert("ˇ ˇlorem", cx);
|
||||
// assert(" ˇlorˇem", cx);
|
||||
// assert("\nlorem\nˇ ˇipsum", cx);
|
||||
// assert("\n\nˇ\nˇ", cx);
|
||||
// assert(" ˇlorem ˇipsum", cx);
|
||||
// assert("loremˇ-ˇipsum", cx);
|
||||
// assert("loremˇ-#$@ˇipsum", cx);
|
||||
// assert("ˇlorem_ˇipsum", cx);
|
||||
// assert(" ˇdefγˇ", cx);
|
||||
// assert(" ˇbcΔˇ", cx);
|
||||
// assert(" abˇ——ˇcd", cx);
|
||||
// }
|
||||
assert("\nˇ ˇlorem", cx);
|
||||
assert("ˇ\nˇ lorem", cx);
|
||||
assert(" ˇloremˇ", cx);
|
||||
assert("ˇ ˇlorem", cx);
|
||||
assert(" ˇlorˇem", cx);
|
||||
assert("\nlorem\nˇ ˇipsum", cx);
|
||||
assert("\n\nˇ\nˇ", cx);
|
||||
assert(" ˇlorem ˇipsum", cx);
|
||||
assert("loremˇ-ˇipsum", cx);
|
||||
assert("loremˇ-#$@ˇipsum", cx);
|
||||
assert("ˇlorem_ˇipsum", cx);
|
||||
assert(" ˇdefγˇ", cx);
|
||||
assert(" ˇbcΔˇ", cx);
|
||||
assert(" abˇ——ˇcd", cx);
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_previous_subword_start(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_previous_subword_start(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
// assert_eq!(
|
||||
// previous_subword_start(&snapshot, display_points[1]),
|
||||
// display_points[0]
|
||||
// );
|
||||
// }
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
previous_subword_start(&snapshot, display_points[1]),
|
||||
display_points[0]
|
||||
);
|
||||
}
|
||||
|
||||
// // Subword boundaries are respected
|
||||
// assert("lorem_ˇipˇsum", cx);
|
||||
// assert("lorem_ˇipsumˇ", cx);
|
||||
// assert("ˇlorem_ˇipsum", cx);
|
||||
// assert("lorem_ˇipsum_ˇdolor", cx);
|
||||
// assert("loremˇIpˇsum", cx);
|
||||
// assert("loremˇIpsumˇ", cx);
|
||||
// Subword boundaries are respected
|
||||
assert("lorem_ˇipˇsum", cx);
|
||||
assert("lorem_ˇipsumˇ", cx);
|
||||
assert("ˇlorem_ˇipsum", cx);
|
||||
assert("lorem_ˇipsum_ˇdolor", cx);
|
||||
assert("loremˇIpˇsum", cx);
|
||||
assert("loremˇIpsumˇ", cx);
|
||||
|
||||
// // Word boundaries are still respected
|
||||
// assert("\nˇ ˇlorem", cx);
|
||||
// assert(" ˇloremˇ", cx);
|
||||
// assert(" ˇlorˇem", cx);
|
||||
// assert("\nlorem\nˇ ˇipsum", cx);
|
||||
// assert("\n\nˇ\nˇ", cx);
|
||||
// assert(" ˇlorem ˇipsum", cx);
|
||||
// assert("loremˇ-ˇipsum", cx);
|
||||
// assert("loremˇ-#$@ˇipsum", cx);
|
||||
// assert(" ˇdefγˇ", cx);
|
||||
// assert(" bcˇΔˇ", cx);
|
||||
// assert(" ˇbcδˇ", cx);
|
||||
// assert(" abˇ——ˇcd", cx);
|
||||
// }
|
||||
// Word boundaries are still respected
|
||||
assert("\nˇ ˇlorem", cx);
|
||||
assert(" ˇloremˇ", cx);
|
||||
assert(" ˇlorˇem", cx);
|
||||
assert("\nlorem\nˇ ˇipsum", cx);
|
||||
assert("\n\nˇ\nˇ", cx);
|
||||
assert(" ˇlorem ˇipsum", cx);
|
||||
assert("loremˇ-ˇipsum", cx);
|
||||
assert("loremˇ-#$@ˇipsum", cx);
|
||||
assert(" ˇdefγˇ", cx);
|
||||
assert(" bcˇΔˇ", cx);
|
||||
assert(" ˇbcδˇ", cx);
|
||||
assert(" abˇ——ˇcd", cx);
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// fn assert(
|
||||
// marked_text: &str,
|
||||
// cx: &mut gpui::AppContext,
|
||||
// is_boundary: impl FnMut(char, char) -> bool,
|
||||
// ) {
|
||||
// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
// assert_eq!(
|
||||
// find_preceding_boundary(
|
||||
// &snapshot,
|
||||
// display_points[1],
|
||||
// FindRange::MultiLine,
|
||||
// is_boundary
|
||||
// ),
|
||||
// display_points[0]
|
||||
// );
|
||||
// }
|
||||
fn assert(
|
||||
marked_text: &str,
|
||||
cx: &mut gpui::AppContext,
|
||||
is_boundary: impl FnMut(char, char) -> bool,
|
||||
) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
find_preceding_boundary(
|
||||
&snapshot,
|
||||
display_points[1],
|
||||
FindRange::MultiLine,
|
||||
is_boundary
|
||||
),
|
||||
display_points[0]
|
||||
);
|
||||
}
|
||||
|
||||
// assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
|
||||
// left == 'c' && right == 'd'
|
||||
// });
|
||||
// assert("abcdef\nˇgh\nijˇk", cx, |left, right| {
|
||||
// left == '\n' && right == 'g'
|
||||
// });
|
||||
// let mut line_count = 0;
|
||||
// assert("abcdef\nˇgh\nijˇk", cx, |left, _| {
|
||||
// if left == '\n' {
|
||||
// line_count += 1;
|
||||
// line_count == 2
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
|
||||
left == 'c' && right == 'd'
|
||||
});
|
||||
assert("abcdef\nˇgh\nijˇk", cx, |left, right| {
|
||||
left == '\n' && right == 'g'
|
||||
});
|
||||
let mut line_count = 0;
|
||||
assert("abcdef\nˇgh\nijˇk", cx, |left, _| {
|
||||
if left == '\n' {
|
||||
line_count += 1;
|
||||
line_count == 2
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_find_preceding_boundary_with_inlays(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_find_preceding_boundary_with_inlays(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// let input_text = "abcdefghijklmnopqrstuvwxys";
|
||||
// let family_id = cx
|
||||
// .font_cache()
|
||||
// .load_family(&["Helvetica"], &Default::default())
|
||||
// .unwrap();
|
||||
// let font_id = cx
|
||||
// .font_cache()
|
||||
// .select_font(family_id, &Default::default())
|
||||
// .unwrap();
|
||||
// let font_size = 14.0;
|
||||
// let buffer = MultiBuffer::build_simple(input_text, cx);
|
||||
// let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
// let display_map =
|
||||
// cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
|
||||
let input_text = "abcdefghijklmnopqrstuvwxys";
|
||||
let font = font("Helvetica");
|
||||
let font_size = px(14.0);
|
||||
let buffer = MultiBuffer::build_simple(input_text, cx);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let display_map =
|
||||
cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
|
||||
|
||||
// // add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
|
||||
// let mut id = 0;
|
||||
// let inlays = (0..buffer_snapshot.len())
|
||||
// .map(|offset| {
|
||||
// [
|
||||
// Inlay {
|
||||
// id: InlayId::Suggestion(post_inc(&mut id)),
|
||||
// position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
// text: format!("test").into(),
|
||||
// },
|
||||
// Inlay {
|
||||
// id: InlayId::Suggestion(post_inc(&mut id)),
|
||||
// position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
// text: format!("test").into(),
|
||||
// },
|
||||
// Inlay {
|
||||
// id: InlayId::Hint(post_inc(&mut id)),
|
||||
// position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
// text: format!("test").into(),
|
||||
// },
|
||||
// Inlay {
|
||||
// id: InlayId::Hint(post_inc(&mut id)),
|
||||
// position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
// text: format!("test").into(),
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// .flatten()
|
||||
// .collect();
|
||||
// let snapshot = display_map.update(cx, |map, cx| {
|
||||
// map.splice_inlays(Vec::new(), inlays, cx);
|
||||
// map.snapshot(cx)
|
||||
// });
|
||||
// add all kinds of inlays between two word boundaries: we should be able to cross them all, when looking for another boundary
|
||||
let mut id = 0;
|
||||
let inlays = (0..buffer_snapshot.len())
|
||||
.map(|offset| {
|
||||
[
|
||||
Inlay {
|
||||
id: InlayId::Suggestion(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Suggestion(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Left),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
Inlay {
|
||||
id: InlayId::Hint(post_inc(&mut id)),
|
||||
position: buffer_snapshot.anchor_at(offset, Bias::Right),
|
||||
text: format!("test").into(),
|
||||
},
|
||||
]
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
let snapshot = display_map.update(cx, |map, cx| {
|
||||
map.splice_inlays(Vec::new(), inlays, cx);
|
||||
map.snapshot(cx)
|
||||
});
|
||||
|
||||
// assert_eq!(
|
||||
// find_preceding_boundary(
|
||||
// &snapshot,
|
||||
// buffer_snapshot.len().to_display_point(&snapshot),
|
||||
// FindRange::MultiLine,
|
||||
// |left, _| left == 'e',
|
||||
// ),
|
||||
// snapshot
|
||||
// .buffer_snapshot
|
||||
// .offset_to_point(5)
|
||||
// .to_display_point(&snapshot),
|
||||
// "Should not stop at inlays when looking for boundaries"
|
||||
// );
|
||||
// }
|
||||
assert_eq!(
|
||||
find_preceding_boundary(
|
||||
&snapshot,
|
||||
buffer_snapshot.len().to_display_point(&snapshot),
|
||||
FindRange::MultiLine,
|
||||
|left, _| left == 'e',
|
||||
),
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.offset_to_point(5)
|
||||
.to_display_point(&snapshot),
|
||||
"Should not stop at inlays when looking for boundaries"
|
||||
);
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_next_word_end(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_next_word_end(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
// assert_eq!(
|
||||
// next_word_end(&snapshot, display_points[0]),
|
||||
// display_points[1]
|
||||
// );
|
||||
// }
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
next_word_end(&snapshot, display_points[0]),
|
||||
display_points[1]
|
||||
);
|
||||
}
|
||||
|
||||
// assert("\nˇ loremˇ", cx);
|
||||
// assert(" ˇloremˇ", cx);
|
||||
// assert(" lorˇemˇ", cx);
|
||||
// assert(" loremˇ ˇ\nipsum\n", cx);
|
||||
// assert("\nˇ\nˇ\n\n", cx);
|
||||
// assert("loremˇ ipsumˇ ", cx);
|
||||
// assert("loremˇ-ˇipsum", cx);
|
||||
// assert("loremˇ#$@-ˇipsum", cx);
|
||||
// assert("loremˇ_ipsumˇ", cx);
|
||||
// assert(" ˇbcΔˇ", cx);
|
||||
// assert(" abˇ——ˇcd", cx);
|
||||
// }
|
||||
assert("\nˇ loremˇ", cx);
|
||||
assert(" ˇloremˇ", cx);
|
||||
assert(" lorˇemˇ", cx);
|
||||
assert(" loremˇ ˇ\nipsum\n", cx);
|
||||
assert("\nˇ\nˇ\n\n", cx);
|
||||
assert("loremˇ ipsumˇ ", cx);
|
||||
assert("loremˇ-ˇipsum", cx);
|
||||
assert("loremˇ#$@-ˇipsum", cx);
|
||||
assert("loremˇ_ipsumˇ", cx);
|
||||
assert(" ˇbcΔˇ", cx);
|
||||
assert(" abˇ——ˇcd", cx);
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_next_subword_end(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_next_subword_end(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
// assert_eq!(
|
||||
// next_subword_end(&snapshot, display_points[0]),
|
||||
// display_points[1]
|
||||
// );
|
||||
// }
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
next_subword_end(&snapshot, display_points[0]),
|
||||
display_points[1]
|
||||
);
|
||||
}
|
||||
|
||||
// // Subword boundaries are respected
|
||||
// assert("loˇremˇ_ipsum", cx);
|
||||
// assert("ˇloremˇ_ipsum", cx);
|
||||
// assert("loremˇ_ipsumˇ", cx);
|
||||
// assert("loremˇ_ipsumˇ_dolor", cx);
|
||||
// assert("loˇremˇIpsum", cx);
|
||||
// assert("loremˇIpsumˇDolor", cx);
|
||||
// Subword boundaries are respected
|
||||
assert("loˇremˇ_ipsum", cx);
|
||||
assert("ˇloremˇ_ipsum", cx);
|
||||
assert("loremˇ_ipsumˇ", cx);
|
||||
assert("loremˇ_ipsumˇ_dolor", cx);
|
||||
assert("loˇremˇIpsum", cx);
|
||||
assert("loremˇIpsumˇDolor", cx);
|
||||
|
||||
// // Word boundaries are still respected
|
||||
// assert("\nˇ loremˇ", cx);
|
||||
// assert(" ˇloremˇ", cx);
|
||||
// assert(" lorˇemˇ", cx);
|
||||
// assert(" loremˇ ˇ\nipsum\n", cx);
|
||||
// assert("\nˇ\nˇ\n\n", cx);
|
||||
// assert("loremˇ ipsumˇ ", cx);
|
||||
// assert("loremˇ-ˇipsum", cx);
|
||||
// assert("loremˇ#$@-ˇipsum", cx);
|
||||
// assert("loremˇ_ipsumˇ", cx);
|
||||
// assert(" ˇbcˇΔ", cx);
|
||||
// assert(" abˇ——ˇcd", cx);
|
||||
// }
|
||||
// Word boundaries are still respected
|
||||
assert("\nˇ loremˇ", cx);
|
||||
assert(" ˇloremˇ", cx);
|
||||
assert(" lorˇemˇ", cx);
|
||||
assert(" loremˇ ˇ\nipsum\n", cx);
|
||||
assert("\nˇ\nˇ\n\n", cx);
|
||||
assert("loremˇ ipsumˇ ", cx);
|
||||
assert("loremˇ-ˇipsum", cx);
|
||||
assert("loremˇ#$@-ˇipsum", cx);
|
||||
assert("loremˇ_ipsumˇ", cx);
|
||||
assert(" ˇbcˇΔ", cx);
|
||||
assert(" abˇ——ˇcd", cx);
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_find_boundary(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_find_boundary(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// fn assert(
|
||||
// marked_text: &str,
|
||||
// cx: &mut gpui::AppContext,
|
||||
// is_boundary: impl FnMut(char, char) -> bool,
|
||||
// ) {
|
||||
// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
// assert_eq!(
|
||||
// find_boundary(
|
||||
// &snapshot,
|
||||
// display_points[0],
|
||||
// FindRange::MultiLine,
|
||||
// is_boundary
|
||||
// ),
|
||||
// display_points[1]
|
||||
// );
|
||||
// }
|
||||
fn assert(
|
||||
marked_text: &str,
|
||||
cx: &mut gpui::AppContext,
|
||||
is_boundary: impl FnMut(char, char) -> bool,
|
||||
) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
find_boundary(
|
||||
&snapshot,
|
||||
display_points[0],
|
||||
FindRange::MultiLine,
|
||||
is_boundary
|
||||
),
|
||||
display_points[1]
|
||||
);
|
||||
}
|
||||
|
||||
// assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
|
||||
// left == 'j' && right == 'k'
|
||||
// });
|
||||
// assert("abˇcdef\ngh\nˇijk", cx, |left, right| {
|
||||
// left == '\n' && right == 'i'
|
||||
// });
|
||||
// let mut line_count = 0;
|
||||
// assert("abcˇdef\ngh\nˇijk", cx, |left, _| {
|
||||
// if left == '\n' {
|
||||
// line_count += 1;
|
||||
// line_count == 2
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
assert("abcˇdef\ngh\nijˇk", cx, |left, right| {
|
||||
left == 'j' && right == 'k'
|
||||
});
|
||||
assert("abˇcdef\ngh\nˇijk", cx, |left, right| {
|
||||
left == '\n' && right == 'i'
|
||||
});
|
||||
let mut line_count = 0;
|
||||
assert("abcˇdef\ngh\nˇijk", cx, |left, _| {
|
||||
if left == '\n' {
|
||||
line_count += 1;
|
||||
line_count == 2
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// fn test_surrounding_word(cx: &mut gpui::AppContext) {
|
||||
// init_test(cx);
|
||||
#[gpui::test]
|
||||
fn test_surrounding_word(cx: &mut gpui::AppContext) {
|
||||
init_test(cx);
|
||||
|
||||
// fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
// let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
// assert_eq!(
|
||||
// surrounding_word(&snapshot, display_points[1]),
|
||||
// display_points[0]..display_points[2],
|
||||
// "{}",
|
||||
// marked_text.to_string()
|
||||
// );
|
||||
// }
|
||||
fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
|
||||
let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
|
||||
assert_eq!(
|
||||
surrounding_word(&snapshot, display_points[1]),
|
||||
display_points[0]..display_points[2],
|
||||
"{}",
|
||||
marked_text.to_string()
|
||||
);
|
||||
}
|
||||
|
||||
// assert("ˇˇloremˇ ipsum", cx);
|
||||
// assert("ˇloˇremˇ ipsum", cx);
|
||||
// assert("ˇloremˇˇ ipsum", cx);
|
||||
// assert("loremˇ ˇ ˇipsum", cx);
|
||||
// assert("lorem\nˇˇˇ\nipsum", cx);
|
||||
// assert("lorem\nˇˇipsumˇ", cx);
|
||||
// assert("loremˇ,ˇˇ ipsum", cx);
|
||||
// assert("ˇloremˇˇ, ipsum", cx);
|
||||
// }
|
||||
assert("ˇˇloremˇ ipsum", cx);
|
||||
assert("ˇloˇremˇ ipsum", cx);
|
||||
assert("ˇloremˇˇ ipsum", cx);
|
||||
assert("loremˇ ˇ ˇipsum", cx);
|
||||
assert("lorem\nˇˇˇ\nipsum", cx);
|
||||
assert("lorem\nˇˇipsumˇ", cx);
|
||||
assert("loremˇ,ˇˇ ipsum", cx);
|
||||
assert("ˇloremˇˇ, ipsum", cx);
|
||||
}
|
||||
|
||||
// #[gpui::test]
|
||||
// async fn test_move_up_and_down_with_excerpts(cx: &mut gpui::TestAppContext) {
|
||||
// cx.update(|cx| {
|
||||
// init_test(cx);
|
||||
// });
|
||||
#[gpui::test]
|
||||
async fn test_move_up_and_down_with_excerpts(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
init_test(cx);
|
||||
});
|
||||
|
||||
// let mut cx = EditorTestContext::new(cx).await;
|
||||
// let editor = cx.editor.clone();
|
||||
// let window = cx.window.clone();
|
||||
// cx.update_window(window, |cx| {
|
||||
// let text_layout_details =
|
||||
// editor.read_with(cx, |editor, cx| editor.text_layout_details(cx));
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
let editor = cx.editor.clone();
|
||||
let window = cx.window.clone();
|
||||
cx.update_window(window, |_, cx| {
|
||||
let text_layout_details =
|
||||
editor.update(cx, |editor, cx| editor.text_layout_details(cx));
|
||||
|
||||
// let family_id = cx
|
||||
// .font_cache()
|
||||
// .load_family(&["Helvetica"], &Default::default())
|
||||
// .unwrap();
|
||||
// let font_id = cx
|
||||
// .font_cache()
|
||||
// .select_font(family_id, &Default::default())
|
||||
// .unwrap();
|
||||
let font = font("Helvetica");
|
||||
|
||||
// let buffer =
|
||||
// cx.add_model(|cx| Buffer::new(0, cx.model_id() as u64, "abc\ndefg\nhijkl\nmn"));
|
||||
// let multibuffer = cx.add_model(|cx| {
|
||||
// let mut multibuffer = MultiBuffer::new(0);
|
||||
// multibuffer.push_excerpts(
|
||||
// buffer.clone(),
|
||||
// [
|
||||
// ExcerptRange {
|
||||
// context: Point::new(0, 0)..Point::new(1, 4),
|
||||
// primary: None,
|
||||
// },
|
||||
// ExcerptRange {
|
||||
// context: Point::new(2, 0)..Point::new(3, 2),
|
||||
// primary: None,
|
||||
// },
|
||||
// ],
|
||||
// cx,
|
||||
// );
|
||||
// multibuffer
|
||||
// });
|
||||
// let display_map =
|
||||
// cx.add_model(|cx| DisplayMap::new(multibuffer, font_id, 14.0, None, 2, 2, cx));
|
||||
// let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let buffer = cx
|
||||
.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abc\ndefg\nhijkl\nmn"));
|
||||
let multibuffer = cx.build_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(0);
|
||||
multibuffer.push_excerpts(
|
||||
buffer.clone(),
|
||||
[
|
||||
ExcerptRange {
|
||||
context: Point::new(0, 0)..Point::new(1, 4),
|
||||
primary: None,
|
||||
},
|
||||
ExcerptRange {
|
||||
context: Point::new(2, 0)..Point::new(3, 2),
|
||||
primary: None,
|
||||
},
|
||||
],
|
||||
cx,
|
||||
);
|
||||
multibuffer
|
||||
});
|
||||
let display_map =
|
||||
cx.build_model(|cx| DisplayMap::new(multibuffer, font, px(14.0), None, 2, 2, cx));
|
||||
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
|
||||
// assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
|
||||
assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");
|
||||
|
||||
// let col_2_x = snapshot.x_for_point(DisplayPoint::new(2, 2), &text_layout_details);
|
||||
let col_2_x =
|
||||
snapshot.x_for_display_point(DisplayPoint::new(2, 2), &text_layout_details);
|
||||
|
||||
// // Can't move up into the first excerpt's header
|
||||
// assert_eq!(
|
||||
// up(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(2, 2),
|
||||
// SelectionGoal::HorizontalPosition(col_2_x),
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(2, 0),
|
||||
// SelectionGoal::HorizontalPosition(0.0)
|
||||
// ),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// up(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(2, 0),
|
||||
// SelectionGoal::None,
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(2, 0),
|
||||
// SelectionGoal::HorizontalPosition(0.0)
|
||||
// ),
|
||||
// );
|
||||
// Can't move up into the first excerpt's header
|
||||
assert_eq!(
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(2, 2),
|
||||
SelectionGoal::HorizontalPosition(col_2_x.0),
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(2, 0),
|
||||
SelectionGoal::HorizontalPosition(0.0)
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(2, 0),
|
||||
SelectionGoal::None,
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(2, 0),
|
||||
SelectionGoal::HorizontalPosition(0.0)
|
||||
),
|
||||
);
|
||||
|
||||
// let col_4_x = snapshot.x_for_point(DisplayPoint::new(3, 4), &text_layout_details);
|
||||
let col_4_x =
|
||||
snapshot.x_for_display_point(DisplayPoint::new(3, 4), &text_layout_details);
|
||||
|
||||
// // Move up and down within first excerpt
|
||||
// assert_eq!(
|
||||
// up(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(3, 4),
|
||||
// SelectionGoal::HorizontalPosition(col_4_x),
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(2, 3),
|
||||
// SelectionGoal::HorizontalPosition(col_4_x)
|
||||
// ),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// down(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(2, 3),
|
||||
// SelectionGoal::HorizontalPosition(col_4_x),
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(3, 4),
|
||||
// SelectionGoal::HorizontalPosition(col_4_x)
|
||||
// ),
|
||||
// );
|
||||
// Move up and down within first excerpt
|
||||
assert_eq!(
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(3, 4),
|
||||
SelectionGoal::HorizontalPosition(col_4_x.0),
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(2, 3),
|
||||
SelectionGoal::HorizontalPosition(col_4_x.0)
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(2, 3),
|
||||
SelectionGoal::HorizontalPosition(col_4_x.0),
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(3, 4),
|
||||
SelectionGoal::HorizontalPosition(col_4_x.0)
|
||||
),
|
||||
);
|
||||
|
||||
// let col_5_x = snapshot.x_for_point(DisplayPoint::new(6, 5), &text_layout_details);
|
||||
let col_5_x =
|
||||
snapshot.x_for_display_point(DisplayPoint::new(6, 5), &text_layout_details);
|
||||
|
||||
// // Move up and down across second excerpt's header
|
||||
// assert_eq!(
|
||||
// up(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(6, 5),
|
||||
// SelectionGoal::HorizontalPosition(col_5_x),
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(3, 4),
|
||||
// SelectionGoal::HorizontalPosition(col_5_x)
|
||||
// ),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// down(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(3, 4),
|
||||
// SelectionGoal::HorizontalPosition(col_5_x),
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(6, 5),
|
||||
// SelectionGoal::HorizontalPosition(col_5_x)
|
||||
// ),
|
||||
// );
|
||||
// Move up and down across second excerpt's header
|
||||
assert_eq!(
|
||||
up(
|
||||
&snapshot,
|
||||
DisplayPoint::new(6, 5),
|
||||
SelectionGoal::HorizontalPosition(col_5_x.0),
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(3, 4),
|
||||
SelectionGoal::HorizontalPosition(col_5_x.0)
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(3, 4),
|
||||
SelectionGoal::HorizontalPosition(col_5_x.0),
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(6, 5),
|
||||
SelectionGoal::HorizontalPosition(col_5_x.0)
|
||||
),
|
||||
);
|
||||
|
||||
// let max_point_x = snapshot.x_for_point(DisplayPoint::new(7, 2), &text_layout_details);
|
||||
let max_point_x =
|
||||
snapshot.x_for_display_point(DisplayPoint::new(7, 2), &text_layout_details);
|
||||
|
||||
// // Can't move down off the end
|
||||
// assert_eq!(
|
||||
// down(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(7, 0),
|
||||
// SelectionGoal::HorizontalPosition(0.0),
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(7, 2),
|
||||
// SelectionGoal::HorizontalPosition(max_point_x)
|
||||
// ),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// down(
|
||||
// &snapshot,
|
||||
// DisplayPoint::new(7, 2),
|
||||
// SelectionGoal::HorizontalPosition(max_point_x),
|
||||
// false,
|
||||
// &text_layout_details
|
||||
// ),
|
||||
// (
|
||||
// DisplayPoint::new(7, 2),
|
||||
// SelectionGoal::HorizontalPosition(max_point_x)
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
// Can't move down off the end
|
||||
assert_eq!(
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(7, 0),
|
||||
SelectionGoal::HorizontalPosition(0.0),
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(7, 2),
|
||||
SelectionGoal::HorizontalPosition(max_point_x.0)
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
down(
|
||||
&snapshot,
|
||||
DisplayPoint::new(7, 2),
|
||||
SelectionGoal::HorizontalPosition(max_point_x.0),
|
||||
false,
|
||||
&text_layout_details
|
||||
),
|
||||
(
|
||||
DisplayPoint::new(7, 2),
|
||||
SelectionGoal::HorizontalPosition(max_point_x.0)
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// fn init_test(cx: &mut gpui::AppContext) {
|
||||
// cx.set_global(SettingsStore::test(cx));
|
||||
// theme::init(cx);
|
||||
// language::init(cx);
|
||||
// crate::init(cx);
|
||||
// Project::init_settings(cx);
|
||||
// }
|
||||
// }
|
||||
fn init_test(cx: &mut gpui::AppContext) {
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
Project::init_settings(cx);
|
||||
}
|
||||
}
|
||||
|
@ -358,7 +358,7 @@ impl AppContext {
|
||||
{
|
||||
let entity_id = entity.entity_id();
|
||||
let handle = entity.downgrade();
|
||||
self.observers.insert(
|
||||
let (subscription, activate) = self.observers.insert(
|
||||
entity_id,
|
||||
Box::new(move |cx| {
|
||||
if let Some(handle) = E::upgrade_from(&handle) {
|
||||
@ -367,7 +367,9 @@ impl AppContext {
|
||||
false
|
||||
}
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn subscribe<T, E, Evt>(
|
||||
@ -398,8 +400,7 @@ impl AppContext {
|
||||
{
|
||||
let entity_id = entity.entity_id();
|
||||
let entity = entity.downgrade();
|
||||
|
||||
self.event_listeners.insert(
|
||||
let (subscription, activate) = self.event_listeners.insert(
|
||||
entity_id,
|
||||
(
|
||||
TypeId::of::<Evt>(),
|
||||
@ -412,7 +413,9 @@ impl AppContext {
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
);
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn windows(&self) -> Vec<AnyWindowHandle> {
|
||||
@ -873,13 +876,15 @@ impl AppContext {
|
||||
&mut self,
|
||||
mut f: impl FnMut(&mut Self) + 'static,
|
||||
) -> Subscription {
|
||||
self.global_observers.insert(
|
||||
let (subscription, activate) = self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
Box::new(move |cx| {
|
||||
f(cx);
|
||||
true
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Move the global of the given type to the stack.
|
||||
@ -903,7 +908,7 @@ impl AppContext {
|
||||
&mut self,
|
||||
on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
|
||||
) -> Subscription {
|
||||
self.new_view_observers.insert(
|
||||
let (subscription, activate) = self.new_view_observers.insert(
|
||||
TypeId::of::<V>(),
|
||||
Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
|
||||
any_view
|
||||
@ -913,7 +918,9 @@ impl AppContext {
|
||||
on_new(view_state, cx);
|
||||
})
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_release<E, T>(
|
||||
@ -925,13 +932,15 @@ impl AppContext {
|
||||
E: Entity<T>,
|
||||
T: 'static,
|
||||
{
|
||||
self.release_listeners.insert(
|
||||
let (subscription, activate) = self.release_listeners.insert(
|
||||
handle.entity_id(),
|
||||
Box::new(move |entity, cx| {
|
||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||
on_release(entity, cx)
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) {
|
||||
@ -996,13 +1005,15 @@ impl AppContext {
|
||||
where
|
||||
Fut: 'static + Future<Output = ()>,
|
||||
{
|
||||
self.quit_observers.insert(
|
||||
let (subscription, activate) = self.quit_observers.insert(
|
||||
(),
|
||||
Box::new(move |cx| {
|
||||
let future = on_quit(cx);
|
||||
async move { future.await }.boxed_local()
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,13 +88,15 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.app.release_listeners.insert(
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
self.model_state.entity_id,
|
||||
Box::new(move |this, cx| {
|
||||
let this = this.downcast_mut().expect("invalid entity type");
|
||||
on_release(this, cx);
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_release<T2, E>(
|
||||
@ -109,7 +111,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
{
|
||||
let entity_id = entity.entity_id();
|
||||
let this = self.weak_model();
|
||||
self.app.release_listeners.insert(
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
entity_id,
|
||||
Box::new(move |entity, cx| {
|
||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||
@ -117,7 +119,9 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
this.update(cx, |this, cx| on_release(this, entity, cx));
|
||||
}
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_global<G: 'static>(
|
||||
@ -128,10 +132,12 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_model();
|
||||
self.global_observers.insert(
|
||||
let (subscription, activate) = self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
Box::new(move |cx| handle.update(cx, |view, cx| f(view, cx)).is_ok()),
|
||||
)
|
||||
);
|
||||
self.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn on_app_quit<Fut>(
|
||||
@ -143,7 +149,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
T: 'static,
|
||||
{
|
||||
let handle = self.weak_model();
|
||||
self.app.quit_observers.insert(
|
||||
let (subscription, activate) = self.app.quit_observers.insert(
|
||||
(),
|
||||
Box::new(move |cx| {
|
||||
let future = handle.update(cx, |entity, cx| on_quit(entity, cx)).ok();
|
||||
@ -154,7 +160,9 @@ impl<'a, T: 'static> ModelContext<'a, T> {
|
||||
}
|
||||
.boxed_local()
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn notify(&mut self) {
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::{
|
||||
div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
|
||||
BackgroundExecutor, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
|
||||
KeyDownEvent, Keystroke, Model, ModelContext, Render, Result, Task, TestDispatcher,
|
||||
TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext, VisualContext, WindowContext,
|
||||
WindowHandle, WindowOptions,
|
||||
BackgroundExecutor, Bounds, Context, Div, Entity, EventEmitter, ForegroundExecutor, InputEvent,
|
||||
KeyDownEvent, Keystroke, Model, ModelContext, Pixels, PlatformWindow, Point, Render, Result,
|
||||
Size, Task, TestDispatcher, TestPlatform, TestWindow, TestWindowHandlers, View, ViewContext,
|
||||
VisualContext, WindowBounds, WindowContext, WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
|
||||
use std::{future::Future, mem, ops::Deref, rc::Rc, sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestAppContext {
|
||||
@ -170,6 +170,45 @@ impl TestAppContext {
|
||||
self.test_platform.has_pending_prompt()
|
||||
}
|
||||
|
||||
pub fn simulate_window_resize(&self, window_handle: AnyWindowHandle, size: Size<Pixels>) {
|
||||
let (mut handlers, scale_factor) = self
|
||||
.app
|
||||
.borrow_mut()
|
||||
.update_window(window_handle, |_, cx| {
|
||||
let platform_window = cx.window.platform_window.as_test().unwrap();
|
||||
let scale_factor = platform_window.scale_factor();
|
||||
match &mut platform_window.bounds {
|
||||
WindowBounds::Fullscreen | WindowBounds::Maximized => {
|
||||
platform_window.bounds = WindowBounds::Fixed(Bounds {
|
||||
origin: Point::default(),
|
||||
size: size.map(|pixels| f64::from(pixels).into()),
|
||||
});
|
||||
}
|
||||
WindowBounds::Fixed(bounds) => {
|
||||
bounds.size = size.map(|pixels| f64::from(pixels).into());
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
mem::take(&mut platform_window.handlers.lock().resize),
|
||||
scale_factor,
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
for handler in &mut handlers {
|
||||
handler(size, scale_factor);
|
||||
}
|
||||
|
||||
self.app
|
||||
.borrow_mut()
|
||||
.update_window(window_handle, |_, cx| {
|
||||
let platform_window = cx.window.platform_window.as_test().unwrap();
|
||||
platform_window.handlers.lock().resize = handlers;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
@ -343,12 +382,15 @@ impl TestAppContext {
|
||||
use smol::future::FutureExt as _;
|
||||
|
||||
async {
|
||||
while notifications.next().await.is_some() {
|
||||
loop {
|
||||
if model.update(self, &mut predicate) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if notifications.next().await.is_none() {
|
||||
bail!("model dropped")
|
||||
}
|
||||
}
|
||||
bail!("model dropped")
|
||||
}
|
||||
.race(timer.map(|_| Err(anyhow!("condition timed out"))))
|
||||
.await
|
||||
|
@ -128,11 +128,19 @@ impl BackgroundExecutor {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[track_caller]
|
||||
pub fn block_test<R>(&self, future: impl Future<Output = R>) -> R {
|
||||
self.block_internal(false, future)
|
||||
if let Ok(value) = self.block_internal(false, future, usize::MAX) {
|
||||
value
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn block<R>(&self, future: impl Future<Output = R>) -> R {
|
||||
self.block_internal(true, future)
|
||||
if let Ok(value) = self.block_internal(true, future, usize::MAX) {
|
||||
value
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@ -140,7 +148,8 @@ impl BackgroundExecutor {
|
||||
&self,
|
||||
background_only: bool,
|
||||
future: impl Future<Output = R>,
|
||||
) -> R {
|
||||
mut max_ticks: usize,
|
||||
) -> Result<R, ()> {
|
||||
pin_mut!(future);
|
||||
let unparker = self.dispatcher.unparker();
|
||||
let awoken = Arc::new(AtomicBool::new(false));
|
||||
@ -156,8 +165,13 @@ impl BackgroundExecutor {
|
||||
|
||||
loop {
|
||||
match future.as_mut().poll(&mut cx) {
|
||||
Poll::Ready(result) => return result,
|
||||
Poll::Ready(result) => return Ok(result),
|
||||
Poll::Pending => {
|
||||
if max_ticks == 0 {
|
||||
return Err(());
|
||||
}
|
||||
max_ticks -= 1;
|
||||
|
||||
if !self.dispatcher.tick(background_only) {
|
||||
if awoken.swap(false, SeqCst) {
|
||||
continue;
|
||||
@ -192,16 +206,25 @@ impl BackgroundExecutor {
|
||||
return Err(future);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let max_ticks = self
|
||||
.dispatcher
|
||||
.as_test()
|
||||
.map_or(usize::MAX, |dispatcher| dispatcher.gen_block_on_ticks());
|
||||
#[cfg(not(any(test, feature = "test-support")))]
|
||||
let max_ticks = usize::MAX;
|
||||
|
||||
let mut timer = self.timer(duration).fuse();
|
||||
|
||||
let timeout = async {
|
||||
futures::select_biased! {
|
||||
value = future => Ok(value),
|
||||
_ = timer => Err(()),
|
||||
}
|
||||
};
|
||||
match self.block(timeout) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(_) => Err(future),
|
||||
match self.block_internal(true, timeout, max_ticks) {
|
||||
Ok(Ok(value)) => Ok(value),
|
||||
_ => Err(future),
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,6 +304,11 @@ impl BackgroundExecutor {
|
||||
pub fn is_main_thread(&self) -> bool {
|
||||
self.dispatcher.is_main_thread()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
|
||||
self.dispatcher.as_test().unwrap().set_block_on_ticks(range);
|
||||
}
|
||||
}
|
||||
|
||||
impl ForegroundExecutor {
|
||||
|
@ -21,7 +21,7 @@ mod subscription;
|
||||
mod svg_renderer;
|
||||
mod taffy;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
mod test;
|
||||
pub mod test;
|
||||
mod text_system;
|
||||
mod util;
|
||||
mod view;
|
||||
|
@ -44,7 +44,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
|
||||
Rc::new(MacPlatform::new())
|
||||
}
|
||||
|
||||
pub(crate) trait Platform: 'static {
|
||||
pub trait Platform: 'static {
|
||||
fn background_executor(&self) -> BackgroundExecutor;
|
||||
fn foreground_executor(&self) -> ForegroundExecutor;
|
||||
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
||||
@ -128,7 +128,7 @@ impl Debug for DisplayId {
|
||||
|
||||
unsafe impl Send for DisplayId {}
|
||||
|
||||
pub(crate) trait PlatformWindow {
|
||||
pub trait PlatformWindow {
|
||||
fn bounds(&self) -> WindowBounds;
|
||||
fn content_size(&self) -> Size<Pixels>;
|
||||
fn scale_factor(&self) -> f32;
|
||||
@ -160,7 +160,7 @@ pub(crate) trait PlatformWindow {
|
||||
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn as_test(&self) -> Option<&TestWindow> {
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use parking_lot::Mutex;
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
future::Future,
|
||||
ops::RangeInclusive,
|
||||
pin::Pin,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
@ -36,6 +37,7 @@ struct TestDispatcherState {
|
||||
allow_parking: bool,
|
||||
waiting_backtrace: Option<Backtrace>,
|
||||
deprioritized_task_labels: HashSet<TaskLabel>,
|
||||
block_on_ticks: RangeInclusive<usize>,
|
||||
}
|
||||
|
||||
impl TestDispatcher {
|
||||
@ -53,6 +55,7 @@ impl TestDispatcher {
|
||||
allow_parking: false,
|
||||
waiting_backtrace: None,
|
||||
deprioritized_task_labels: Default::default(),
|
||||
block_on_ticks: 0..=1000,
|
||||
};
|
||||
|
||||
TestDispatcher {
|
||||
@ -82,8 +85,8 @@ impl TestDispatcher {
|
||||
}
|
||||
|
||||
pub fn simulate_random_delay(&self) -> impl 'static + Send + Future<Output = ()> {
|
||||
pub struct YieldNow {
|
||||
count: usize,
|
||||
struct YieldNow {
|
||||
pub(crate) count: usize,
|
||||
}
|
||||
|
||||
impl Future for YieldNow {
|
||||
@ -142,6 +145,16 @@ impl TestDispatcher {
|
||||
pub fn rng(&self) -> StdRng {
|
||||
self.state.lock().random.clone()
|
||||
}
|
||||
|
||||
pub fn set_block_on_ticks(&self, range: std::ops::RangeInclusive<usize>) {
|
||||
self.state.lock().block_on_ticks = range;
|
||||
}
|
||||
|
||||
pub fn gen_block_on_ticks(&self) -> usize {
|
||||
let mut lock = self.state.lock();
|
||||
let block_on_ticks = lock.block_on_ticks.clone();
|
||||
lock.random.gen_range(block_on_ticks)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for TestDispatcher {
|
||||
|
@ -19,7 +19,7 @@ pub(crate) struct TestWindowHandlers {
|
||||
}
|
||||
|
||||
pub struct TestWindow {
|
||||
bounds: WindowBounds,
|
||||
pub(crate) bounds: WindowBounds,
|
||||
current_scene: Mutex<Option<Scene>>,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
pub(crate) window_title: Option<String>,
|
||||
@ -170,7 +170,7 @@ impl PlatformWindow for TestWindow {
|
||||
self.sprite_atlas.clone()
|
||||
}
|
||||
|
||||
fn as_test(&self) -> Option<&TestWindow> {
|
||||
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ impl SceneBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Scene {
|
||||
pub struct Scene {
|
||||
pub shadows: Vec<Shadow>,
|
||||
pub quads: Vec<Quad>,
|
||||
pub paths: Vec<Path<ScaledPixels>>,
|
||||
@ -214,7 +214,7 @@ impl Scene {
|
||||
&self.paths
|
||||
}
|
||||
|
||||
pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
|
||||
BatchIterator {
|
||||
shadows: &self.shadows,
|
||||
shadows_start: 0,
|
||||
|
@ -208,8 +208,9 @@ impl TextStyle {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the rounded line height in pixels.
|
||||
pub fn line_height_in_pixels(&self, rem_size: Pixels) -> Pixels {
|
||||
self.line_height.to_pixels(self.font_size, rem_size)
|
||||
self.line_height.to_pixels(self.font_size, rem_size).round()
|
||||
}
|
||||
|
||||
pub fn to_run(&self, len: usize) -> TextRun {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use collections::{BTreeMap, BTreeSet};
|
||||
use parking_lot::Mutex;
|
||||
use std::{fmt::Debug, mem, sync::Arc};
|
||||
use std::{cell::Cell, fmt::Debug, mem, rc::Rc, sync::Arc};
|
||||
use util::post_inc;
|
||||
|
||||
pub(crate) struct SubscriberSet<EmitterKey, Callback>(
|
||||
@ -14,11 +14,16 @@ impl<EmitterKey, Callback> Clone for SubscriberSet<EmitterKey, Callback> {
|
||||
}
|
||||
|
||||
struct SubscriberSetState<EmitterKey, Callback> {
|
||||
subscribers: BTreeMap<EmitterKey, Option<BTreeMap<usize, Callback>>>,
|
||||
subscribers: BTreeMap<EmitterKey, Option<BTreeMap<usize, Subscriber<Callback>>>>,
|
||||
dropped_subscribers: BTreeSet<(EmitterKey, usize)>,
|
||||
next_subscriber_id: usize,
|
||||
}
|
||||
|
||||
struct Subscriber<Callback> {
|
||||
active: Rc<Cell<bool>>,
|
||||
callback: Callback,
|
||||
}
|
||||
|
||||
impl<EmitterKey, Callback> SubscriberSet<EmitterKey, Callback>
|
||||
where
|
||||
EmitterKey: 'static + Ord + Clone + Debug,
|
||||
@ -32,16 +37,33 @@ where
|
||||
})))
|
||||
}
|
||||
|
||||
pub fn insert(&self, emitter_key: EmitterKey, callback: Callback) -> Subscription {
|
||||
/// Inserts a new `[Subscription]` for the given `emitter_key`. By default, subscriptions
|
||||
/// are inert, meaning that they won't be listed when calling `[SubscriberSet::remove]` or `[SubscriberSet::retain]`.
|
||||
/// This method returns a tuple of a `[Subscription]` and an `impl FnOnce`, and you can use the latter
|
||||
/// to activate the `[Subscription]`.
|
||||
#[must_use]
|
||||
pub fn insert(
|
||||
&self,
|
||||
emitter_key: EmitterKey,
|
||||
callback: Callback,
|
||||
) -> (Subscription, impl FnOnce()) {
|
||||
let active = Rc::new(Cell::new(false));
|
||||
let mut lock = self.0.lock();
|
||||
let subscriber_id = post_inc(&mut lock.next_subscriber_id);
|
||||
lock.subscribers
|
||||
.entry(emitter_key.clone())
|
||||
.or_default()
|
||||
.get_or_insert_with(|| Default::default())
|
||||
.insert(subscriber_id, callback);
|
||||
.insert(
|
||||
subscriber_id,
|
||||
Subscriber {
|
||||
active: active.clone(),
|
||||
callback,
|
||||
},
|
||||
);
|
||||
let this = self.0.clone();
|
||||
Subscription {
|
||||
|
||||
let subscription = Subscription {
|
||||
unsubscribe: Some(Box::new(move || {
|
||||
let mut lock = this.lock();
|
||||
let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) else {
|
||||
@ -63,7 +85,8 @@ where
|
||||
lock.dropped_subscribers
|
||||
.insert((emitter_key, subscriber_id));
|
||||
})),
|
||||
}
|
||||
};
|
||||
(subscription, move || active.set(true))
|
||||
}
|
||||
|
||||
pub fn remove(&self, emitter: &EmitterKey) -> impl IntoIterator<Item = Callback> {
|
||||
@ -73,6 +96,13 @@ where
|
||||
.map(|s| s.into_values())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|subscriber| {
|
||||
if subscriber.active.get() {
|
||||
Some(subscriber.callback)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Call the given callback for each subscriber to the given emitter.
|
||||
@ -91,7 +121,13 @@ where
|
||||
return;
|
||||
};
|
||||
|
||||
subscribers.retain(|_, callback| f(callback));
|
||||
subscribers.retain(|_, subscriber| {
|
||||
if subscriber.active.get() {
|
||||
f(&mut subscriber.callback)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
let mut lock = self.0.lock();
|
||||
|
||||
// Add any new subscribers that were added while invoking the callback.
|
||||
|
@ -1,5 +1,7 @@
|
||||
use crate::TestDispatcher;
|
||||
use crate::{Entity, Subscription, TestAppContext, TestDispatcher};
|
||||
use futures::StreamExt as _;
|
||||
use rand::prelude::*;
|
||||
use smol::channel;
|
||||
use std::{
|
||||
env,
|
||||
panic::{self, RefUnwindSafe},
|
||||
@ -49,3 +51,30 @@ pub fn run_test(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Observation<T> {
|
||||
rx: channel::Receiver<T>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
impl<T: 'static> futures::Stream for Observation<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
self.rx.poll_next_unpin(cx)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn observe<T: 'static>(entity: &impl Entity<T>, cx: &mut TestAppContext) -> Observation<()> {
|
||||
let (tx, rx) = smol::channel::unbounded();
|
||||
let _subscription = cx.update(|cx| {
|
||||
cx.observe(entity, move |_, _| {
|
||||
let _ = smol::block_on(tx.send(()));
|
||||
})
|
||||
});
|
||||
|
||||
Observation { rx, _subscription }
|
||||
}
|
||||
|
@ -490,7 +490,7 @@ impl<'a> WindowContext<'a> {
|
||||
let entity_id = entity.entity_id();
|
||||
let entity = entity.downgrade();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.event_listeners.insert(
|
||||
let (subscription, activate) = self.app.event_listeners.insert(
|
||||
entity_id,
|
||||
(
|
||||
TypeId::of::<Evt>(),
|
||||
@ -508,7 +508,9 @@ impl<'a> WindowContext<'a> {
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Create an `AsyncWindowContext`, which has a static lifetime and can be held across
|
||||
@ -1458,10 +1460,12 @@ impl<'a> WindowContext<'a> {
|
||||
f: impl Fn(&mut WindowContext<'_>) + 'static,
|
||||
) -> Subscription {
|
||||
let window_handle = self.window.handle;
|
||||
self.global_observers.insert(
|
||||
let (subscription, activate) = self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
Box::new(move |cx| window_handle.update(cx, |_, cx| f(cx)).is_ok()),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn activate_window(&self) {
|
||||
@ -2122,7 +2126,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
let entity_id = entity.entity_id();
|
||||
let entity = entity.downgrade();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.observers.insert(
|
||||
let (subscription, activate) = self.app.observers.insert(
|
||||
entity_id,
|
||||
Box::new(move |cx| {
|
||||
window_handle
|
||||
@ -2136,7 +2140,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn subscribe<V2, E, Evt>(
|
||||
@ -2153,7 +2159,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
let entity_id = entity.entity_id();
|
||||
let handle = entity.downgrade();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.event_listeners.insert(
|
||||
let (subscription, activate) = self.app.event_listeners.insert(
|
||||
entity_id,
|
||||
(
|
||||
TypeId::of::<Evt>(),
|
||||
@ -2171,7 +2177,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn on_release(
|
||||
@ -2179,13 +2187,15 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
on_release: impl FnOnce(&mut V, &mut WindowContext) + 'static,
|
||||
) -> Subscription {
|
||||
let window_handle = self.window.handle;
|
||||
self.app.release_listeners.insert(
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
self.view.model.entity_id,
|
||||
Box::new(move |this, cx| {
|
||||
let this = this.downcast_mut().expect("invalid entity type");
|
||||
let _ = window_handle.update(cx, |_, cx| on_release(this, cx));
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_release<V2, E>(
|
||||
@ -2201,7 +2211,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
let view = self.view().downgrade();
|
||||
let entity_id = entity.entity_id();
|
||||
let window_handle = self.window.handle;
|
||||
self.app.release_listeners.insert(
|
||||
let (subscription, activate) = self.app.release_listeners.insert(
|
||||
entity_id,
|
||||
Box::new(move |entity, cx| {
|
||||
let entity = entity.downcast_mut().expect("invalid entity type");
|
||||
@ -2209,7 +2219,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
view.update(cx, |this, cx| on_release(this, entity, cx))
|
||||
});
|
||||
}),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn notify(&mut self) {
|
||||
@ -2224,10 +2236,12 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.view.downgrade();
|
||||
self.window.bounds_observers.insert(
|
||||
let (subscription, activate) = self.window.bounds_observers.insert(
|
||||
(),
|
||||
Box::new(move |cx| view.update(cx, |view, cx| callback(view, cx)).is_ok()),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn observe_window_activation(
|
||||
@ -2235,10 +2249,12 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
mut callback: impl FnMut(&mut V, &mut ViewContext<V>) + 'static,
|
||||
) -> Subscription {
|
||||
let view = self.view.downgrade();
|
||||
self.window.activation_observers.insert(
|
||||
let (subscription, activate) = self.window.activation_observers.insert(
|
||||
(),
|
||||
Box::new(move |cx| view.update(cx, |view, cx| callback(view, cx)).is_ok()),
|
||||
)
|
||||
);
|
||||
activate();
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle receives focus.
|
||||
@ -2251,7 +2267,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
) -> Subscription {
|
||||
let view = self.view.downgrade();
|
||||
let focus_id = handle.id;
|
||||
self.window.focus_listeners.insert(
|
||||
let (subscription, activate) = self.window.focus_listeners.insert(
|
||||
(),
|
||||
Box::new(move |event, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
@ -2261,7 +2277,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
})
|
||||
.is_ok()
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle or one of its descendants receives focus.
|
||||
@ -2274,7 +2292,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
) -> Subscription {
|
||||
let view = self.view.downgrade();
|
||||
let focus_id = handle.id;
|
||||
self.window.focus_listeners.insert(
|
||||
let (subscription, activate) = self.window.focus_listeners.insert(
|
||||
(),
|
||||
Box::new(move |event, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
@ -2288,7 +2306,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
})
|
||||
.is_ok()
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle loses focus.
|
||||
@ -2301,7 +2321,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
) -> Subscription {
|
||||
let view = self.view.downgrade();
|
||||
let focus_id = handle.id;
|
||||
self.window.focus_listeners.insert(
|
||||
let (subscription, activate) = self.window.focus_listeners.insert(
|
||||
(),
|
||||
Box::new(move |event, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
@ -2311,7 +2331,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
})
|
||||
.is_ok()
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
/// Register a listener to be called when the given focus handle or one of its descendants loses focus.
|
||||
@ -2324,7 +2346,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
) -> Subscription {
|
||||
let view = self.view.downgrade();
|
||||
let focus_id = handle.id;
|
||||
self.window.focus_listeners.insert(
|
||||
let (subscription, activate) = self.window.focus_listeners.insert(
|
||||
(),
|
||||
Box::new(move |event, cx| {
|
||||
view.update(cx, |view, cx| {
|
||||
@ -2338,7 +2360,9 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
})
|
||||
.is_ok()
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(
|
||||
@ -2369,14 +2393,16 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
) -> Subscription {
|
||||
let window_handle = self.window.handle;
|
||||
let view = self.view().downgrade();
|
||||
self.global_observers.insert(
|
||||
let (subscription, activate) = self.global_observers.insert(
|
||||
TypeId::of::<G>(),
|
||||
Box::new(move |cx| {
|
||||
window_handle
|
||||
.update(cx, |_, cx| view.update(cx, |view, cx| f(view, cx)).is_ok())
|
||||
.unwrap_or(false)
|
||||
}),
|
||||
)
|
||||
);
|
||||
self.app.defer(move |_| activate());
|
||||
subscription
|
||||
}
|
||||
|
||||
pub fn on_mouse_event<Event: 'static>(
|
||||
|
Loading…
Reference in New Issue
Block a user