WIP: Add some editor tests (#3285)
This is a continuation of https://github.com/zed-industries/zed/pull/3232, however as I started working on this it seemed a bit premature. There are a few GPUI changes to finish getting the editor green (mainly clipboard and simulate_resize), as well as several editor methods to port (several around changing selections), as well as several editor methods that are now fully broken (tagged with 'todo(finish editor tests)'). I am also comitting the partially ported integration tests, which where close to compiling. Release Notes: - N/A
This commit is contained in:
Generated
+1
-2
@@ -2012,7 +2012,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"settings2",
|
||||
"smol",
|
||||
"theme",
|
||||
"theme2",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -2768,7 +2768,6 @@ dependencies = [
|
||||
"copilot2",
|
||||
"ctor",
|
||||
"db2",
|
||||
"drag_and_drop",
|
||||
"env_logger 0.9.3",
|
||||
"futures 0.3.28",
|
||||
"fuzzy2",
|
||||
|
||||
@@ -36,7 +36,7 @@ impl FakeServer {
|
||||
peer: Peer::new(0),
|
||||
state: Default::default(),
|
||||
user_id: client_user_id,
|
||||
executor: cx.executor().clone(),
|
||||
executor: cx.executor(),
|
||||
};
|
||||
|
||||
client
|
||||
|
||||
@@ -510,7 +510,7 @@ fn test_fuzzy_like_string() {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
|
||||
let test_db = TestDb::postgres(cx.executor().clone());
|
||||
let test_db = TestDb::postgres(cx.executor());
|
||||
let db = test_db.db();
|
||||
for (i, github_login) in [
|
||||
"California",
|
||||
|
||||
@@ -4,6 +4,7 @@ use gpui::{Model, TestAppContext};
|
||||
mod channel_buffer_tests;
|
||||
mod channel_message_tests;
|
||||
mod channel_tests;
|
||||
mod editor_tests;
|
||||
mod following_tests;
|
||||
mod integration_tests;
|
||||
mod notification_tests;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,32 @@
|
||||
// use editor::{
|
||||
// test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
|
||||
// ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo,
|
||||
//todo(partially ported)
|
||||
// use std::{
|
||||
// path::Path,
|
||||
// sync::{
|
||||
// atomic::{self, AtomicBool, AtomicUsize},
|
||||
// Arc,
|
||||
// },
|
||||
// };
|
||||
|
||||
//todo!(editor)
|
||||
// use call::ActiveCall;
|
||||
// use editor::{
|
||||
// test::editor_test_context::{AssertionContextManager, EditorTestContext},
|
||||
// Anchor, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename,
|
||||
// ToggleCodeActions, Undo,
|
||||
// };
|
||||
// use gpui::{BackgroundExecutor, TestAppContext, VisualContext, VisualTestContext};
|
||||
// use indoc::indoc;
|
||||
// use language::{
|
||||
// language_settings::{AllLanguageSettings, InlayHintSettings},
|
||||
// tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
|
||||
// };
|
||||
// use rpc::RECEIVE_TIMEOUT;
|
||||
// use serde_json::json;
|
||||
// use settings::SettingsStore;
|
||||
// use text::Point;
|
||||
// use workspace::Workspace;
|
||||
|
||||
// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_host_disconnect(
|
||||
// executor: BackgroundExecutor,
|
||||
@@ -11,7 +34,7 @@
|
||||
// cx_b: &mut TestAppContext,
|
||||
// cx_c: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let mut server = TestServer::start(executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// let client_c = server.create_client(cx_c, "user_c").await;
|
||||
@@ -25,7 +48,7 @@
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// serde_json::json!({
|
||||
// "a.txt": "a-contents",
|
||||
// "b.txt": "b-contents",
|
||||
// }),
|
||||
@@ -35,7 +58,7 @@
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
|
||||
// let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
|
||||
// let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees().next().unwrap());
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
@@ -46,21 +69,25 @@
|
||||
|
||||
// assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
|
||||
|
||||
// let window_b =
|
||||
// let workspace_b =
|
||||
// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
|
||||
// let workspace_b = window_b.root(cx_b);
|
||||
// let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
|
||||
|
||||
// let editor_b = workspace_b
|
||||
// .update(cx_b, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "b.txt"), None, true, cx)
|
||||
// })
|
||||
// .unwrap()
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
|
||||
// //TODO: focus
|
||||
// assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
|
||||
// editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
|
||||
// assert!(window_b.is_edited(cx_b));
|
||||
// //todo(is_edited)
|
||||
// // assert!(workspace_b.is_edited(cx_b));
|
||||
|
||||
// // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
|
||||
// server.forbid_connections();
|
||||
@@ -77,10 +104,10 @@
|
||||
|
||||
// // Ensure client B's edited state is reset and that the whole window is blurred.
|
||||
|
||||
// window_b.read_with(cx_b, |cx| {
|
||||
// workspace_b.update(cx_b, |_, cx| {
|
||||
// assert_eq!(cx.focused_view_id(), None);
|
||||
// });
|
||||
// assert!(!window_b.is_edited(cx_b));
|
||||
// // assert!(!workspace_b.is_edited(cx_b));
|
||||
|
||||
// // Ensure client B is not prompted to save edits when closing window after disconnecting.
|
||||
// let can_close = workspace_b
|
||||
@@ -120,7 +147,6 @@
|
||||
// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
|
||||
// }
|
||||
|
||||
//todo!(editor)
|
||||
// #[gpui::test]
|
||||
// async fn test_newline_above_or_below_does_not_move_guest_cursor(
|
||||
// executor: BackgroundExecutor,
|
||||
@@ -152,12 +178,14 @@
|
||||
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window_a = cx_a.add_window(|_| EmptyView);
|
||||
// let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
|
||||
// let window_a = cx_a.add_empty_window();
|
||||
// let editor_a =
|
||||
// window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
|
||||
// let mut editor_cx_a = EditorTestContext {
|
||||
// cx: cx_a,
|
||||
// window: window_a.into(),
|
||||
// editor: editor_a,
|
||||
// assertion_cx: AssertionContextManager::new(),
|
||||
// };
|
||||
|
||||
// // Open a buffer as client B
|
||||
@@ -165,12 +193,14 @@
|
||||
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window_b = cx_b.add_window(|_| EmptyView);
|
||||
// let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
|
||||
// let window_b = cx_b.add_empty_window();
|
||||
// let editor_b =
|
||||
// window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
|
||||
// let mut editor_cx_b = EditorTestContext {
|
||||
// cx: cx_b,
|
||||
// window: window_b.into(),
|
||||
// editor: editor_b,
|
||||
// assertion_cx: AssertionContextManager::new(),
|
||||
// };
|
||||
|
||||
// // Test newline above
|
||||
@@ -214,7 +244,6 @@
|
||||
// "});
|
||||
// }
|
||||
|
||||
//todo!(editor)
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_collaborating_with_completion(
|
||||
// executor: BackgroundExecutor,
|
||||
@@ -275,8 +304,8 @@
|
||||
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window_b = cx_b.add_window(|_| EmptyView);
|
||||
// let editor_b = window_b.add_view(cx_b, |cx| {
|
||||
// let window_b = cx_b.add_empty_window();
|
||||
// let editor_b = window_b.build_view(cx_b, |cx| {
|
||||
// Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
|
||||
// });
|
||||
|
||||
@@ -384,7 +413,7 @@
|
||||
// );
|
||||
|
||||
// // The additional edit is applied.
|
||||
// cx_a.foreground().run_until_parked();
|
||||
// cx_a.executor().run_until_parked();
|
||||
|
||||
// buffer_a.read_with(cx_a, |buffer, _| {
|
||||
// assert_eq!(
|
||||
@@ -400,7 +429,7 @@
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
//todo!(editor)
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_collaborating_with_code_actions(
|
||||
// executor: BackgroundExecutor,
|
||||
@@ -619,7 +648,6 @@
|
||||
// });
|
||||
// }
|
||||
|
||||
//todo!(editor)
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_collaborating_with_renames(
|
||||
// executor: BackgroundExecutor,
|
||||
@@ -813,7 +841,6 @@
|
||||
// })
|
||||
// }
|
||||
|
||||
//todo!(editor)
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_language_server_statuses(
|
||||
// executor: BackgroundExecutor,
|
||||
@@ -937,8 +964,8 @@
|
||||
// cx_b: &mut TestAppContext,
|
||||
// cx_c: &mut TestAppContext,
|
||||
// ) {
|
||||
// let window_b = cx_b.add_window(|_| EmptyView);
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let window_b = cx_b.add_empty_window();
|
||||
// let mut server = TestServer::start(executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// let client_c = server.create_client(cx_c, "user_c").await;
|
||||
@@ -1052,7 +1079,7 @@
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
|
||||
// let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
|
||||
|
||||
// // Client A sees client B's selection
|
||||
// executor.run_until_parked();
|
||||
@@ -1106,3 +1133,757 @@
|
||||
// == 0
|
||||
// });
|
||||
// }
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_on_input_format_from_host_to_guest(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// // Set up a fake language server.
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
// first_trigger_character: ":".to_string(),
|
||||
// more_trigger_character: Some(vec![">".to_string()]),
|
||||
// }),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// client_a.language_registry().add(Arc::new(language));
|
||||
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a }",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// // Open a file in an editor as the host.
|
||||
// let buffer_a = project_a
|
||||
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window_a = cx_a.add_empty_window();
|
||||
// let editor_a = window_a
|
||||
// .update(cx_a, |_, cx| {
|
||||
// cx.build_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
|
||||
// })
|
||||
// .unwrap();
|
||||
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// executor.run_until_parked();
|
||||
|
||||
// // Receive an OnTypeFormatting request as the host's language server.
|
||||
// // Return some formattings from the host's language server.
|
||||
// fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
|
||||
// |params, _| async move {
|
||||
// assert_eq!(
|
||||
// params.text_document_position.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// params.text_document_position.position,
|
||||
// lsp::Position::new(0, 14),
|
||||
// );
|
||||
|
||||
// Ok(Some(vec![lsp::TextEdit {
|
||||
// new_text: "~<".to_string(),
|
||||
// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
// }]))
|
||||
// },
|
||||
// );
|
||||
|
||||
// // Open the buffer on the guest and see that the formattings worked
|
||||
// let buffer_b = project_b
|
||||
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// // Type a on type formatting trigger character as the guest.
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// cx.focus(&editor_a);
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
// editor.handle_input(">", cx);
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_b.read_with(cx_b, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a>~< }")
|
||||
// });
|
||||
|
||||
// // Undo should remove LSP edits first
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a>~< }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_b.read_with(cx_b, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a> }")
|
||||
// });
|
||||
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_b.read_with(cx_b, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a }")
|
||||
// });
|
||||
// }
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_on_input_format_from_guest_to_host(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// // Set up a fake language server.
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
// first_trigger_character: ":".to_string(),
|
||||
// more_trigger_character: Some(vec![">".to_string()]),
|
||||
// }),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// client_a.language_registry().add(Arc::new(language));
|
||||
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a }",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// // Open a file in an editor as the guest.
|
||||
// let buffer_b = project_b
|
||||
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window_b = cx_b.add_empty_window();
|
||||
// let editor_b = window_b.build_view(cx_b, |cx| {
|
||||
// Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
|
||||
// });
|
||||
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// executor.run_until_parked();
|
||||
// // Type a on type formatting trigger character as the guest.
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
// editor.handle_input(":", cx);
|
||||
// cx.focus(&editor_b);
|
||||
// });
|
||||
|
||||
// // Receive an OnTypeFormatting request as the host's language server.
|
||||
// // Return some formattings from the host's language server.
|
||||
// cx_a.foreground().start_waiting();
|
||||
// fake_language_server
|
||||
// .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
// assert_eq!(
|
||||
// params.text_document_position.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// params.text_document_position.position,
|
||||
// lsp::Position::new(0, 14),
|
||||
// );
|
||||
|
||||
// Ok(Some(vec![lsp::TextEdit {
|
||||
// new_text: "~:".to_string(),
|
||||
// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
// }]))
|
||||
// })
|
||||
// .next()
|
||||
// .await
|
||||
// .unwrap();
|
||||
// cx_a.foreground().finish_waiting();
|
||||
|
||||
// // Open the buffer on the host and see that the formattings worked
|
||||
// let buffer_a = project_a
|
||||
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_a.read_with(cx_a, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a:~: }")
|
||||
// });
|
||||
|
||||
// // Undo should remove LSP edits first
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a:~: }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_a.read_with(cx_a, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a: }")
|
||||
// });
|
||||
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_a.read_with(cx_a, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a }")
|
||||
// });
|
||||
// }
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
// let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
// cx_a.update(editor::init);
|
||||
// cx_b.update(editor::init);
|
||||
|
||||
// cx_a.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: true,
|
||||
// show_type_hints: true,
|
||||
// show_parameter_hints: false,
|
||||
// show_other_hints: true,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// cx_b.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: true,
|
||||
// show_type_hints: true,
|
||||
// show_parameter_hints: false,
|
||||
// show_other_hints: true,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// let language = Arc::new(language);
|
||||
// client_a.language_registry().add(Arc::clone(&language));
|
||||
// client_b.language_registry().add(language);
|
||||
|
||||
// // Client A opens a project.
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// active_call_a
|
||||
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// // Client B joins the project
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
// active_call_b
|
||||
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root_view(cx_a);
|
||||
// cx_a.foreground().start_waiting();
|
||||
|
||||
// // The host opens a rust file.
|
||||
// let _buffer_a = project_a
|
||||
// .update(cx_a, |project, cx| {
|
||||
// project.open_local_buffer("/a/main.rs", cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// let editor_a = workspace_a
|
||||
// .update(cx_a, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// // Set up the language server to return an additional inlay hint on each request.
|
||||
// let edits_made = Arc::new(AtomicUsize::new(0));
|
||||
// let closure_edits_made = Arc::clone(&edits_made);
|
||||
// fake_language_server
|
||||
// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
// let task_edits_made = Arc::clone(&closure_edits_made);
|
||||
// async move {
|
||||
// assert_eq!(
|
||||
// params.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
|
||||
// Ok(Some(vec![lsp::InlayHint {
|
||||
// position: lsp::Position::new(0, edits_made as u32),
|
||||
// label: lsp::InlayHintLabel::String(edits_made.to_string()),
|
||||
// kind: None,
|
||||
// text_edits: None,
|
||||
// tooltip: None,
|
||||
// padding_left: None,
|
||||
// padding_right: None,
|
||||
// data: None,
|
||||
// }]))
|
||||
// }
|
||||
// })
|
||||
// .next()
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// executor.run_until_parked();
|
||||
|
||||
// let initial_edit = edits_made.load(atomic::Ordering::Acquire);
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![initial_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Host should get its first hints when opens an editor"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 1,
|
||||
// "Host editor update the cache version after every cache/view change",
|
||||
// );
|
||||
// });
|
||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
||||
// let editor_b = workspace_b
|
||||
// .update(cx_b, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![initial_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Client should get its first hints when opens an editor"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 1,
|
||||
// "Guest editor update the cache version after every cache/view change"
|
||||
// );
|
||||
// });
|
||||
|
||||
// let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
|
||||
// editor.handle_input(":", cx);
|
||||
// cx.focus(&editor_b);
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_client_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 2);
|
||||
// });
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_client_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 2);
|
||||
// });
|
||||
|
||||
// let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
// editor.handle_input("a change to increment both buffers' versions", cx);
|
||||
// cx.focus(&editor_a);
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_host_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 3);
|
||||
// });
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_host_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 3);
|
||||
// });
|
||||
|
||||
// let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
// fake_language_server
|
||||
// .request::<lsp::request::InlayHintRefreshRequest>(())
|
||||
// .await
|
||||
// .expect("inlay refresh request failed");
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_special_edit_for_refresh.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Host should react to /refresh LSP request"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 4,
|
||||
// "Host should accepted all edits and bump its cache version every time"
|
||||
// );
|
||||
// });
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_special_edit_for_refresh.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Guest should get a /refresh LSP request propagated by host"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 4,
|
||||
// "Guest should accepted all edits and bump its cache version every time"
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_inlay_hint_refresh_is_forwarded(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
// let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
// cx_a.update(editor::init);
|
||||
// cx_b.update(editor::init);
|
||||
|
||||
// cx_a.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: false,
|
||||
// show_type_hints: false,
|
||||
// show_parameter_hints: false,
|
||||
// show_other_hints: false,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// cx_b.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: true,
|
||||
// show_type_hints: true,
|
||||
// show_parameter_hints: true,
|
||||
// show_other_hints: true,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// let language = Arc::new(language);
|
||||
// client_a.language_registry().add(Arc::clone(&language));
|
||||
// client_b.language_registry().add(language);
|
||||
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// active_call_a
|
||||
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
// active_call_b
|
||||
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
||||
// cx_a.foreground().start_waiting();
|
||||
// cx_b.foreground().start_waiting();
|
||||
|
||||
// let editor_a = workspace_a
|
||||
// .update(cx_a, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// let editor_b = workspace_b
|
||||
// .update(cx_b, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// let other_hints = Arc::new(AtomicBool::new(false));
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// let closure_other_hints = Arc::clone(&other_hints);
|
||||
// fake_language_server
|
||||
// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
// let task_other_hints = Arc::clone(&closure_other_hints);
|
||||
// async move {
|
||||
// assert_eq!(
|
||||
// params.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
|
||||
// let character = if other_hints { 0 } else { 2 };
|
||||
// let label = if other_hints {
|
||||
// "other hint"
|
||||
// } else {
|
||||
// "initial hint"
|
||||
// };
|
||||
// Ok(Some(vec![lsp::InlayHint {
|
||||
// position: lsp::Position::new(0, character),
|
||||
// label: lsp::InlayHintLabel::String(label.to_string()),
|
||||
// kind: None,
|
||||
// text_edits: None,
|
||||
// tooltip: None,
|
||||
// padding_left: None,
|
||||
// padding_right: None,
|
||||
// data: None,
|
||||
// }]))
|
||||
// }
|
||||
// })
|
||||
// .next()
|
||||
// .await
|
||||
// .unwrap();
|
||||
// cx_a.foreground().finish_waiting();
|
||||
// cx_b.foreground().finish_waiting();
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert!(
|
||||
// extract_hint_labels(editor).is_empty(),
|
||||
// "Host should get no hints due to them turned off"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 0,
|
||||
// "Turned off hints should not generate version updates"
|
||||
// );
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec!["initial hint".to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Client should get its first hints when opens an editor"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 1,
|
||||
// "Should update cache verison after first hints"
|
||||
// );
|
||||
// });
|
||||
|
||||
// other_hints.fetch_or(true, atomic::Ordering::Release);
|
||||
// fake_language_server
|
||||
// .request::<lsp::request::InlayHintRefreshRequest>(())
|
||||
// .await
|
||||
// .expect("inlay refresh request failed");
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert!(
|
||||
// extract_hint_labels(editor).is_empty(),
|
||||
// "Host should get nop hints due to them turned off, even after the /refresh"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 0,
|
||||
// "Turned off hints should not generate version updates, again"
|
||||
// );
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec!["other hint".to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Guest should get a /refresh LSP request propagated by host despite host hints are off"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 2,
|
||||
// "Guest should accepted all edits and bump its cache version every time"
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
// let mut labels = Vec::new();
|
||||
// for hint in editor.inlay_hint_cache().hints() {
|
||||
// match hint.label {
|
||||
// project::InlayHintLabel::String(s) => labels.push(s),
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
// }
|
||||
// labels
|
||||
// }
|
||||
|
||||
@@ -5717,758 +5717,3 @@ async fn test_join_call_after_screen_was_shared(
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//todo!(editor)
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_on_input_format_from_host_to_guest(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// // Set up a fake language server.
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
// first_trigger_character: ":".to_string(),
|
||||
// more_trigger_character: Some(vec![">".to_string()]),
|
||||
// }),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// client_a.language_registry().add(Arc::new(language));
|
||||
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a }",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// // Open a file in an editor as the host.
|
||||
// let buffer_a = project_a
|
||||
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window_a = cx_a.add_window(|_| EmptyView);
|
||||
// let editor_a = window_a.add_view(cx_a, |cx| {
|
||||
// Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
|
||||
// });
|
||||
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// executor.run_until_parked();
|
||||
|
||||
// // Receive an OnTypeFormatting request as the host's language server.
|
||||
// // Return some formattings from the host's language server.
|
||||
// fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
|
||||
// |params, _| async move {
|
||||
// assert_eq!(
|
||||
// params.text_document_position.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// params.text_document_position.position,
|
||||
// lsp::Position::new(0, 14),
|
||||
// );
|
||||
|
||||
// Ok(Some(vec![lsp::TextEdit {
|
||||
// new_text: "~<".to_string(),
|
||||
// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
// }]))
|
||||
// },
|
||||
// );
|
||||
|
||||
// // Open the buffer on the guest and see that the formattings worked
|
||||
// let buffer_b = project_b
|
||||
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// // Type a on type formatting trigger character as the guest.
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// cx.focus(&editor_a);
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
// editor.handle_input(">", cx);
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_b.read_with(cx_b, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a>~< }")
|
||||
// });
|
||||
|
||||
// // Undo should remove LSP edits first
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a>~< }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_b.read_with(cx_b, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a> }")
|
||||
// });
|
||||
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a> }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_b.read_with(cx_b, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a }")
|
||||
// });
|
||||
// }
|
||||
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_on_input_format_from_guest_to_host(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
|
||||
// // Set up a fake language server.
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
|
||||
// first_trigger_character: ":".to_string(),
|
||||
// more_trigger_character: Some(vec![">".to_string()]),
|
||||
// }),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// client_a.language_registry().add(Arc::new(language));
|
||||
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a }",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
|
||||
// // Open a file in an editor as the guest.
|
||||
// let buffer_b = project_b
|
||||
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window_b = cx_b.add_window(|_| EmptyView);
|
||||
// let editor_b = window_b.add_view(cx_b, |cx| {
|
||||
// Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
|
||||
// });
|
||||
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// executor.run_until_parked();
|
||||
// // Type a on type formatting trigger character as the guest.
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
// editor.handle_input(":", cx);
|
||||
// cx.focus(&editor_b);
|
||||
// });
|
||||
|
||||
// // Receive an OnTypeFormatting request as the host's language server.
|
||||
// // Return some formattings from the host's language server.
|
||||
// cx_a.foreground().start_waiting();
|
||||
// fake_language_server
|
||||
// .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
|
||||
// assert_eq!(
|
||||
// params.text_document_position.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// assert_eq!(
|
||||
// params.text_document_position.position,
|
||||
// lsp::Position::new(0, 14),
|
||||
// );
|
||||
|
||||
// Ok(Some(vec![lsp::TextEdit {
|
||||
// new_text: "~:".to_string(),
|
||||
// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
|
||||
// }]))
|
||||
// })
|
||||
// .next()
|
||||
// .await
|
||||
// .unwrap();
|
||||
// cx_a.foreground().finish_waiting();
|
||||
|
||||
// // Open the buffer on the host and see that the formattings worked
|
||||
// let buffer_a = project_a
|
||||
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_a.read_with(cx_a, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a:~: }")
|
||||
// });
|
||||
|
||||
// // Undo should remove LSP edits first
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a:~: }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_a.read_with(cx_a, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a: }")
|
||||
// });
|
||||
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), "fn main() { a: }");
|
||||
// editor.undo(&Undo, cx);
|
||||
// assert_eq!(editor.text(cx), "fn main() { a }");
|
||||
// });
|
||||
// executor.run_until_parked();
|
||||
|
||||
// buffer_a.read_with(cx_a, |buffer, _| {
|
||||
// assert_eq!(buffer.text(), "fn main() { a }")
|
||||
// });
|
||||
// }
|
||||
|
||||
//todo!(editor)
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_mutual_editor_inlay_hint_cache_update(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
// let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
// cx_a.update(editor::init);
|
||||
// cx_b.update(editor::init);
|
||||
|
||||
// cx_a.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: true,
|
||||
// show_type_hints: true,
|
||||
// show_parameter_hints: false,
|
||||
// show_other_hints: true,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// cx_b.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: true,
|
||||
// show_type_hints: true,
|
||||
// show_parameter_hints: false,
|
||||
// show_other_hints: true,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// let language = Arc::new(language);
|
||||
// client_a.language_registry().add(Arc::clone(&language));
|
||||
// client_b.language_registry().add(language);
|
||||
|
||||
// // Client A opens a project.
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// active_call_a
|
||||
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// // Client B joins the project
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
// active_call_b
|
||||
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
||||
// cx_a.foreground().start_waiting();
|
||||
|
||||
// // The host opens a rust file.
|
||||
// let _buffer_a = project_a
|
||||
// .update(cx_a, |project, cx| {
|
||||
// project.open_local_buffer("/a/main.rs", cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// let editor_a = workspace_a
|
||||
// .update(cx_a, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// // Set up the language server to return an additional inlay hint on each request.
|
||||
// let edits_made = Arc::new(AtomicUsize::new(0));
|
||||
// let closure_edits_made = Arc::clone(&edits_made);
|
||||
// fake_language_server
|
||||
// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
// let task_edits_made = Arc::clone(&closure_edits_made);
|
||||
// async move {
|
||||
// assert_eq!(
|
||||
// params.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
|
||||
// Ok(Some(vec![lsp::InlayHint {
|
||||
// position: lsp::Position::new(0, edits_made as u32),
|
||||
// label: lsp::InlayHintLabel::String(edits_made.to_string()),
|
||||
// kind: None,
|
||||
// text_edits: None,
|
||||
// tooltip: None,
|
||||
// padding_left: None,
|
||||
// padding_right: None,
|
||||
// data: None,
|
||||
// }]))
|
||||
// }
|
||||
// })
|
||||
// .next()
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// executor.run_until_parked();
|
||||
|
||||
// let initial_edit = edits_made.load(atomic::Ordering::Acquire);
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![initial_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Host should get its first hints when opens an editor"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 1,
|
||||
// "Host editor update the cache version after every cache/view change",
|
||||
// );
|
||||
// });
|
||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
||||
// let editor_b = workspace_b
|
||||
// .update(cx_b, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![initial_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Client should get its first hints when opens an editor"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 1,
|
||||
// "Guest editor update the cache version after every cache/view change"
|
||||
// );
|
||||
// });
|
||||
|
||||
// let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
// editor_b.update(cx_b, |editor, cx| {
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
|
||||
// editor.handle_input(":", cx);
|
||||
// cx.focus(&editor_b);
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_client_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 2);
|
||||
// });
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_client_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 2);
|
||||
// });
|
||||
|
||||
// let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
// editor_a.update(cx_a, |editor, cx| {
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||
// editor.handle_input("a change to increment both buffers' versions", cx);
|
||||
// cx.focus(&editor_a);
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_host_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 3);
|
||||
// });
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_host_edit.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(inlay_cache.version(), 3);
|
||||
// });
|
||||
|
||||
// let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
|
||||
// fake_language_server
|
||||
// .request::<lsp::request::InlayHintRefreshRequest>(())
|
||||
// .await
|
||||
// .expect("inlay refresh request failed");
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_special_edit_for_refresh.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Host should react to /refresh LSP request"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 4,
|
||||
// "Host should accepted all edits and bump its cache version every time"
|
||||
// );
|
||||
// });
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec![after_special_edit_for_refresh.to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Guest should get a /refresh LSP request propagated by host"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 4,
|
||||
// "Guest should accepted all edits and bump its cache version every time"
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
//todo!(editor)
|
||||
// #[gpui::test(iterations = 10)]
|
||||
// async fn test_inlay_hint_refresh_is_forwarded(
|
||||
// executor: BackgroundExecutor,
|
||||
// cx_a: &mut TestAppContext,
|
||||
// cx_b: &mut TestAppContext,
|
||||
// ) {
|
||||
// let mut server = TestServer::start(&executor).await;
|
||||
// let client_a = server.create_client(cx_a, "user_a").await;
|
||||
// let client_b = server.create_client(cx_b, "user_b").await;
|
||||
// server
|
||||
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||
// .await;
|
||||
// let active_call_a = cx_a.read(ActiveCall::global);
|
||||
// let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
// cx_a.update(editor::init);
|
||||
// cx_b.update(editor::init);
|
||||
|
||||
// cx_a.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: false,
|
||||
// show_type_hints: false,
|
||||
// show_parameter_hints: false,
|
||||
// show_other_hints: false,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// cx_b.update(|cx| {
|
||||
// cx.update_global(|store: &mut SettingsStore, cx| {
|
||||
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
|
||||
// settings.defaults.inlay_hints = Some(InlayHintSettings {
|
||||
// enabled: true,
|
||||
// show_type_hints: true,
|
||||
// show_parameter_hints: true,
|
||||
// show_other_hints: true,
|
||||
// })
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// let mut language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// );
|
||||
// let mut fake_language_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities: lsp::ServerCapabilities {
|
||||
// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
// let language = Arc::new(language);
|
||||
// client_a.language_registry().add(Arc::clone(&language));
|
||||
// client_b.language_registry().add(language);
|
||||
|
||||
// client_a
|
||||
// .fs()
|
||||
// .insert_tree(
|
||||
// "/a",
|
||||
// json!({
|
||||
// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
|
||||
// "other.rs": "// Test file",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
|
||||
// active_call_a
|
||||
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let project_id = active_call_a
|
||||
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
// active_call_b
|
||||
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
||||
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
||||
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
||||
// cx_a.foreground().start_waiting();
|
||||
// cx_b.foreground().start_waiting();
|
||||
|
||||
// let editor_a = workspace_a
|
||||
// .update(cx_a, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// let editor_b = workspace_b
|
||||
// .update(cx_b, |workspace, cx| {
|
||||
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap()
|
||||
// .downcast::<Editor>()
|
||||
// .unwrap();
|
||||
|
||||
// let other_hints = Arc::new(AtomicBool::new(false));
|
||||
// let fake_language_server = fake_language_servers.next().await.unwrap();
|
||||
// let closure_other_hints = Arc::clone(&other_hints);
|
||||
// fake_language_server
|
||||
// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
|
||||
// let task_other_hints = Arc::clone(&closure_other_hints);
|
||||
// async move {
|
||||
// assert_eq!(
|
||||
// params.text_document.uri,
|
||||
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
|
||||
// );
|
||||
// let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
|
||||
// let character = if other_hints { 0 } else { 2 };
|
||||
// let label = if other_hints {
|
||||
// "other hint"
|
||||
// } else {
|
||||
// "initial hint"
|
||||
// };
|
||||
// Ok(Some(vec![lsp::InlayHint {
|
||||
// position: lsp::Position::new(0, character),
|
||||
// label: lsp::InlayHintLabel::String(label.to_string()),
|
||||
// kind: None,
|
||||
// text_edits: None,
|
||||
// tooltip: None,
|
||||
// padding_left: None,
|
||||
// padding_right: None,
|
||||
// data: None,
|
||||
// }]))
|
||||
// }
|
||||
// })
|
||||
// .next()
|
||||
// .await
|
||||
// .unwrap();
|
||||
// cx_a.foreground().finish_waiting();
|
||||
// cx_b.foreground().finish_waiting();
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert!(
|
||||
// extract_hint_labels(editor).is_empty(),
|
||||
// "Host should get no hints due to them turned off"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 0,
|
||||
// "Turned off hints should not generate version updates"
|
||||
// );
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec!["initial hint".to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Client should get its first hints when opens an editor"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 1,
|
||||
// "Should update cache verison after first hints"
|
||||
// );
|
||||
// });
|
||||
|
||||
// other_hints.fetch_or(true, atomic::Ordering::Release);
|
||||
// fake_language_server
|
||||
// .request::<lsp::request::InlayHintRefreshRequest>(())
|
||||
// .await
|
||||
// .expect("inlay refresh request failed");
|
||||
// executor.run_until_parked();
|
||||
// editor_a.update(cx_a, |editor, _| {
|
||||
// assert!(
|
||||
// extract_hint_labels(editor).is_empty(),
|
||||
// "Host should get nop hints due to them turned off, even after the /refresh"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 0,
|
||||
// "Turned off hints should not generate version updates, again"
|
||||
// );
|
||||
// });
|
||||
|
||||
// executor.run_until_parked();
|
||||
// editor_b.update(cx_b, |editor, _| {
|
||||
// assert_eq!(
|
||||
// vec!["other hint".to_string()],
|
||||
// extract_hint_labels(editor),
|
||||
// "Guest should get a /refresh LSP request propagated by host despite host hints are off"
|
||||
// );
|
||||
// let inlay_cache = editor.inlay_hint_cache();
|
||||
// assert_eq!(
|
||||
// inlay_cache.version(),
|
||||
// 2,
|
||||
// "Guest should accepted all edits and bump its cache version every time"
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
|
||||
// let mut labels = Vec::new();
|
||||
// for hint in editor.inlay_hint_cache().hints() {
|
||||
// match hint.label {
|
||||
// project::InlayHintLabel::String(s) => labels.push(s),
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
// }
|
||||
// labels
|
||||
// }
|
||||
|
||||
@@ -208,11 +208,11 @@ impl TestServer {
|
||||
})
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http, cx));
|
||||
let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
|
||||
let mut language_registry = LanguageRegistry::test();
|
||||
language_registry.set_executor(cx.executor().clone());
|
||||
language_registry.set_executor(cx.executor());
|
||||
let app_state = Arc::new(workspace::AppState {
|
||||
client: client.clone(),
|
||||
user_store: user_store.clone(),
|
||||
|
||||
@@ -24,7 +24,7 @@ collections = { path = "../collections" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
language = { package = "language2", path = "../language2" }
|
||||
settings = { package = "settings2", path = "../settings2" }
|
||||
theme = { path = "../theme" }
|
||||
theme = { package = "theme2", path = "../theme2" }
|
||||
lsp = { package = "lsp2", path = "../lsp2" }
|
||||
node_runtime = { path = "../node_runtime"}
|
||||
util = { path = "../util" }
|
||||
|
||||
@@ -351,28 +351,29 @@ impl Copilot {
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
// pub fn fake(cx: &mut gpui::TestAppContext) -> (ModelHandle<Self>, lsp::FakeLanguageServer) {
|
||||
// use node_runtime::FakeNodeRuntime;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fake(cx: &mut gpui::TestAppContext) -> (Model<Self>, lsp::FakeLanguageServer) {
|
||||
use node_runtime::FakeNodeRuntime;
|
||||
|
||||
// let (server, fake_server) =
|
||||
// LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
|
||||
// let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
|
||||
// let node_runtime = FakeNodeRuntime::new();
|
||||
// let this = cx.add_model(|_| Self {
|
||||
// server_id: LanguageServerId(0),
|
||||
// http: http.clone(),
|
||||
// node_runtime,
|
||||
// server: CopilotServer::Running(RunningCopilotServer {
|
||||
// name: LanguageServerName(Arc::from("copilot")),
|
||||
// lsp: Arc::new(server),
|
||||
// sign_in_status: SignInStatus::Authorized,
|
||||
// registered_buffers: Default::default(),
|
||||
// }),
|
||||
// buffers: Default::default(),
|
||||
// });
|
||||
// (this, fake_server)
|
||||
// }
|
||||
let (server, fake_server) =
|
||||
LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
|
||||
let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
|
||||
let node_runtime = FakeNodeRuntime::new();
|
||||
let this = cx.build_model(|cx| Self {
|
||||
server_id: LanguageServerId(0),
|
||||
http: http.clone(),
|
||||
node_runtime,
|
||||
server: CopilotServer::Running(RunningCopilotServer {
|
||||
name: LanguageServerName(Arc::from("copilot")),
|
||||
lsp: Arc::new(server),
|
||||
sign_in_status: SignInStatus::Authorized,
|
||||
registered_buffers: Default::default(),
|
||||
}),
|
||||
_subscription: cx.on_app_quit(Self::shutdown_language_server),
|
||||
buffers: Default::default(),
|
||||
});
|
||||
(this, fake_server)
|
||||
}
|
||||
|
||||
fn start_language_server(
|
||||
new_server_id: LanguageServerId,
|
||||
|
||||
@@ -27,7 +27,6 @@ client = { package = "client2", path = "../client2" }
|
||||
clock = { path = "../clock" }
|
||||
copilot = { package="copilot2", path = "../copilot2" }
|
||||
db = { package="db2", path = "../db2" }
|
||||
drag_and_drop = { path = "../drag_and_drop" }
|
||||
collections = { path = "../collections" }
|
||||
# context_menu = { path = "../context_menu" }
|
||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||
|
||||
@@ -2023,24 +2023,24 @@ impl Editor {
|
||||
dispatch_context
|
||||
}
|
||||
|
||||
// pub fn new_file(
|
||||
// workspace: &mut Workspace,
|
||||
// _: &workspace::NewFile,
|
||||
// cx: &mut ViewContext<Workspace>,
|
||||
// ) {
|
||||
// let project = workspace.project().clone();
|
||||
// if project.read(cx).is_remote() {
|
||||
// cx.propagate();
|
||||
// } else if let Some(buffer) = project
|
||||
// .update(cx, |project, cx| project.create_buffer("", None, cx))
|
||||
// .log_err()
|
||||
// {
|
||||
// workspace.add_item(
|
||||
// Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
|
||||
// cx,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
pub fn new_file(
|
||||
workspace: &mut Workspace,
|
||||
_: &workspace::NewFile,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let project = workspace.project().clone();
|
||||
if project.read(cx).is_remote() {
|
||||
cx.propagate();
|
||||
} else if let Some(buffer) = project
|
||||
.update(cx, |project, cx| project.create_buffer("", None, cx))
|
||||
.log_err()
|
||||
{
|
||||
workspace.add_item(
|
||||
Box::new(cx.build_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn new_file_in_direction(
|
||||
// workspace: &mut Workspace,
|
||||
@@ -2124,17 +2124,17 @@ impl Editor {
|
||||
// )
|
||||
// }
|
||||
|
||||
// pub fn mode(&self) -> EditorMode {
|
||||
// self.mode
|
||||
// }
|
||||
pub fn mode(&self) -> EditorMode {
|
||||
self.mode
|
||||
}
|
||||
|
||||
// pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
|
||||
// self.collaboration_hub.as_deref()
|
||||
// }
|
||||
pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
|
||||
self.collaboration_hub.as_deref()
|
||||
}
|
||||
|
||||
// pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
|
||||
// self.collaboration_hub = Some(hub);
|
||||
// }
|
||||
pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
|
||||
self.collaboration_hub = Some(hub);
|
||||
}
|
||||
|
||||
pub fn set_placeholder_text(
|
||||
&mut self,
|
||||
@@ -10056,76 +10056,76 @@ pub fn diagnostic_style(
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn combine_syntax_and_fuzzy_match_highlights(
|
||||
// text: &str,
|
||||
// default_style: HighlightStyle,
|
||||
// syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
// match_indices: &[usize],
|
||||
// ) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||
// let mut result = Vec::new();
|
||||
// let mut match_indices = match_indices.iter().copied().peekable();
|
||||
pub fn combine_syntax_and_fuzzy_match_highlights(
|
||||
text: &str,
|
||||
default_style: HighlightStyle,
|
||||
syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
|
||||
match_indices: &[usize],
|
||||
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||
let mut result = Vec::new();
|
||||
let mut match_indices = match_indices.iter().copied().peekable();
|
||||
|
||||
// for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
|
||||
// {
|
||||
// syntax_highlight.weight = None;
|
||||
for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
|
||||
{
|
||||
syntax_highlight.font_weight = None;
|
||||
|
||||
// // Add highlights for any fuzzy match characters before the next
|
||||
// // syntax highlight range.
|
||||
// while let Some(&match_index) = match_indices.peek() {
|
||||
// if match_index >= range.start {
|
||||
// break;
|
||||
// }
|
||||
// match_indices.next();
|
||||
// let end_index = char_ix_after(match_index, text);
|
||||
// let mut match_style = default_style;
|
||||
// match_style.weight = Some(FontWeight::BOLD);
|
||||
// result.push((match_index..end_index, match_style));
|
||||
// }
|
||||
// Add highlights for any fuzzy match characters before the next
|
||||
// syntax highlight range.
|
||||
while let Some(&match_index) = match_indices.peek() {
|
||||
if match_index >= range.start {
|
||||
break;
|
||||
}
|
||||
match_indices.next();
|
||||
let end_index = char_ix_after(match_index, text);
|
||||
let mut match_style = default_style;
|
||||
match_style.font_weight = Some(FontWeight::BOLD);
|
||||
result.push((match_index..end_index, match_style));
|
||||
}
|
||||
|
||||
// if range.start == usize::MAX {
|
||||
// break;
|
||||
// }
|
||||
if range.start == usize::MAX {
|
||||
break;
|
||||
}
|
||||
|
||||
// // Add highlights for any fuzzy match characters within the
|
||||
// // syntax highlight range.
|
||||
// let mut offset = range.start;
|
||||
// while let Some(&match_index) = match_indices.peek() {
|
||||
// if match_index >= range.end {
|
||||
// break;
|
||||
// }
|
||||
// Add highlights for any fuzzy match characters within the
|
||||
// syntax highlight range.
|
||||
let mut offset = range.start;
|
||||
while let Some(&match_index) = match_indices.peek() {
|
||||
if match_index >= range.end {
|
||||
break;
|
||||
}
|
||||
|
||||
// match_indices.next();
|
||||
// if match_index > offset {
|
||||
// result.push((offset..match_index, syntax_highlight));
|
||||
// }
|
||||
match_indices.next();
|
||||
if match_index > offset {
|
||||
result.push((offset..match_index, syntax_highlight));
|
||||
}
|
||||
|
||||
// let mut end_index = char_ix_after(match_index, text);
|
||||
// while let Some(&next_match_index) = match_indices.peek() {
|
||||
// if next_match_index == end_index && next_match_index < range.end {
|
||||
// end_index = char_ix_after(next_match_index, text);
|
||||
// match_indices.next();
|
||||
// } else {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
let mut end_index = char_ix_after(match_index, text);
|
||||
while let Some(&next_match_index) = match_indices.peek() {
|
||||
if next_match_index == end_index && next_match_index < range.end {
|
||||
end_index = char_ix_after(next_match_index, text);
|
||||
match_indices.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// let mut match_style = syntax_highlight;
|
||||
// match_style.weight = Some(FontWeight::BOLD);
|
||||
// result.push((match_index..end_index, match_style));
|
||||
// offset = end_index;
|
||||
// }
|
||||
let mut match_style = syntax_highlight;
|
||||
match_style.font_weight = Some(FontWeight::BOLD);
|
||||
result.push((match_index..end_index, match_style));
|
||||
offset = end_index;
|
||||
}
|
||||
|
||||
// if offset < range.end {
|
||||
// result.push((offset..range.end, syntax_highlight));
|
||||
// }
|
||||
// }
|
||||
if offset < range.end {
|
||||
result.push((offset..range.end, syntax_highlight));
|
||||
}
|
||||
}
|
||||
|
||||
// fn char_ix_after(ix: usize, text: &str) -> usize {
|
||||
// ix + text[ix..].chars().next().unwrap().len_utf8()
|
||||
// }
|
||||
fn char_ix_after(ix: usize, text: &str) -> usize {
|
||||
ix + text[ix..].chars().next().unwrap().len_utf8()
|
||||
}
|
||||
|
||||
// result
|
||||
// }
|
||||
result
|
||||
}
|
||||
|
||||
// pub fn styled_runs_for_code_label<'a>(
|
||||
// label: &'a CodeLabel,
|
||||
|
||||
+5922
-5818
File diff suppressed because it is too large
Load Diff
@@ -1683,21 +1683,24 @@ impl EditorElement {
|
||||
ShowScrollbar::Never => false,
|
||||
};
|
||||
|
||||
let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
|
||||
.into_iter()
|
||||
.map(|(id, fold)| {
|
||||
todo!("folds!")
|
||||
// let color = self
|
||||
// .style
|
||||
// .folds
|
||||
// .ellipses
|
||||
// .background
|
||||
// .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
|
||||
// .color;
|
||||
let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = Vec::new();
|
||||
// todo!()
|
||||
|
||||
// (id, fold, color)
|
||||
})
|
||||
.collect();
|
||||
// fold_ranges
|
||||
// .into_iter()
|
||||
// .map(|(id, fold)| {
|
||||
// // todo!("folds!")
|
||||
// // let color = self
|
||||
// // .style
|
||||
// // .folds
|
||||
// // .ellipses
|
||||
// // .background
|
||||
// // .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
|
||||
// // .color;
|
||||
|
||||
// // (id, fold, color)
|
||||
// })
|
||||
// .collect();
|
||||
|
||||
let head_for_relative = newest_selection_head.unwrap_or_else(|| {
|
||||
let newest = editor.selections.newest::<Point>(cx);
|
||||
|
||||
@@ -315,11 +315,14 @@ impl SelectionsCollection {
|
||||
|
||||
let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details);
|
||||
|
||||
dbg!("****START COL****");
|
||||
let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
|
||||
if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
|
||||
let start = DisplayPoint::new(row, start_col);
|
||||
dbg!("****END COL****");
|
||||
let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
|
||||
let end = DisplayPoint::new(row, end_col);
|
||||
dbg!(start_col, end_col);
|
||||
|
||||
Some(Selection {
|
||||
id: post_inc(&mut self.next_selection_id),
|
||||
|
||||
+60
-67
@@ -1,81 +1,74 @@
|
||||
pub mod editor_lsp_test_context;
|
||||
pub mod editor_test_context;
|
||||
|
||||
// todo!()
|
||||
// use crate::{
|
||||
// display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||
// DisplayPoint, Editor, EditorMode, MultiBuffer,
|
||||
// };
|
||||
use crate::{
|
||||
display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
|
||||
DisplayPoint, Editor, EditorMode, MultiBuffer,
|
||||
};
|
||||
|
||||
// use gpui::{Model, ViewContext};
|
||||
use gpui::{Context, Model, Pixels, ViewContext};
|
||||
|
||||
// use project::Project;
|
||||
// use util::test::{marked_text_offsets, marked_text_ranges};
|
||||
use project::Project;
|
||||
use util::test::{marked_text_offsets, marked_text_ranges};
|
||||
|
||||
// #[cfg(test)]
|
||||
// #[ctor::ctor]
|
||||
// fn init_logger() {
|
||||
// if std::env::var("RUST_LOG").is_ok() {
|
||||
// env_logger::init();
|
||||
// }
|
||||
// }
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::init();
|
||||
}
|
||||
}
|
||||
|
||||
// // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
|
||||
// pub fn marked_display_snapshot(
|
||||
// text: &str,
|
||||
// cx: &mut gpui::AppContext,
|
||||
// ) -> (DisplaySnapshot, Vec<DisplayPoint>) {
|
||||
// let (unmarked_text, markers) = marked_text_offsets(text);
|
||||
// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
|
||||
pub fn marked_display_snapshot(
|
||||
text: &str,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> (DisplaySnapshot, Vec<DisplayPoint>) {
|
||||
let (unmarked_text, markers) = marked_text_offsets(text);
|
||||
|
||||
// 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 font = cx.text_style().font();
|
||||
let font_size: Pixels = 14.into();
|
||||
|
||||
// let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
||||
// let display_map =
|
||||
// cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
|
||||
// let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
// let markers = markers
|
||||
// .into_iter()
|
||||
// .map(|offset| offset.to_display_point(&snapshot))
|
||||
// .collect();
|
||||
let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
|
||||
let display_map = cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
|
||||
let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||
let markers = markers
|
||||
.into_iter()
|
||||
.map(|offset| offset.to_display_point(&snapshot))
|
||||
.collect();
|
||||
|
||||
// (snapshot, markers)
|
||||
// }
|
||||
(snapshot, markers)
|
||||
}
|
||||
|
||||
// pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
|
||||
// let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
|
||||
// assert_eq!(editor.text(cx), unmarked_text);
|
||||
// editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
|
||||
// }
|
||||
pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) {
|
||||
let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
|
||||
assert_eq!(editor.text(cx), unmarked_text);
|
||||
editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
|
||||
}
|
||||
|
||||
// pub fn assert_text_with_selections(
|
||||
// editor: &mut Editor,
|
||||
// marked_text: &str,
|
||||
// cx: &mut ViewContext<Editor>,
|
||||
// ) {
|
||||
// let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
|
||||
// assert_eq!(editor.text(cx), unmarked_text);
|
||||
// assert_eq!(editor.selections.ranges(cx), text_ranges);
|
||||
// }
|
||||
pub fn assert_text_with_selections(
|
||||
editor: &mut Editor,
|
||||
marked_text: &str,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
|
||||
assert_eq!(editor.text(cx), unmarked_text);
|
||||
assert_eq!(editor.selections.ranges(cx), text_ranges);
|
||||
}
|
||||
|
||||
// // RA thinks this is dead code even though it is used in a whole lot of tests
|
||||
// #[allow(dead_code)]
|
||||
// #[cfg(any(test, feature = "test-support"))]
|
||||
// pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
|
||||
// Editor::new(EditorMode::Full, buffer, None, None, cx)
|
||||
// }
|
||||
// RA thinks this is dead code even though it is used in a whole lot of tests
|
||||
#[allow(dead_code)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
|
||||
// todo!()
|
||||
Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
|
||||
}
|
||||
|
||||
// pub(crate) fn build_editor_with_project(
|
||||
// project: Model<Project>,
|
||||
// buffer: Model<MultiBuffer>,
|
||||
// cx: &mut ViewContext<Editor>,
|
||||
// ) -> Editor {
|
||||
// Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
|
||||
// }
|
||||
pub(crate) fn build_editor_with_project(
|
||||
project: Model<Project>,
|
||||
buffer: Model<MultiBuffer>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) -> Editor {
|
||||
// todo!()
|
||||
Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
|
||||
}
|
||||
|
||||
@@ -1,297 +1,298 @@
|
||||
// use std::{
|
||||
// borrow::Cow,
|
||||
// ops::{Deref, DerefMut, Range},
|
||||
// sync::Arc,
|
||||
// };
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
// use anyhow::Result;
|
||||
use anyhow::Result;
|
||||
use serde_json::json;
|
||||
|
||||
// use crate::{Editor, ToPoint};
|
||||
// use collections::HashSet;
|
||||
// use futures::Future;
|
||||
// use gpui::{json, View, ViewContext};
|
||||
// use indoc::indoc;
|
||||
// use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
|
||||
// use lsp::{notification, request};
|
||||
// use multi_buffer::ToPointUtf16;
|
||||
// use project::Project;
|
||||
// use smol::stream::StreamExt;
|
||||
// use workspace::{AppState, Workspace, WorkspaceHandle};
|
||||
use crate::{Editor, ToPoint};
|
||||
use collections::HashSet;
|
||||
use futures::Future;
|
||||
use gpui::{View, ViewContext, VisualTestContext};
|
||||
use indoc::indoc;
|
||||
use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
|
||||
use lsp::{notification, request};
|
||||
use multi_buffer::ToPointUtf16;
|
||||
use project::Project;
|
||||
use smol::stream::StreamExt;
|
||||
use workspace::{AppState, Workspace, WorkspaceHandle};
|
||||
|
||||
// use super::editor_test_context::EditorTestContext;
|
||||
use super::editor_test_context::{AssertionContextManager, EditorTestContext};
|
||||
|
||||
// pub struct EditorLspTestContext<'a> {
|
||||
// pub cx: EditorTestContext<'a>,
|
||||
// pub lsp: lsp::FakeLanguageServer,
|
||||
// pub workspace: View<Workspace>,
|
||||
// pub buffer_lsp_url: lsp::Url,
|
||||
// }
|
||||
pub struct EditorLspTestContext<'a> {
|
||||
pub cx: EditorTestContext<'a>,
|
||||
pub lsp: lsp::FakeLanguageServer,
|
||||
pub workspace: View<Workspace>,
|
||||
pub buffer_lsp_url: lsp::Url,
|
||||
}
|
||||
|
||||
// impl<'a> EditorLspTestContext<'a> {
|
||||
// pub async fn new(
|
||||
// mut language: Language,
|
||||
// capabilities: lsp::ServerCapabilities,
|
||||
// cx: &'a mut gpui::TestAppContext,
|
||||
// ) -> EditorLspTestContext<'a> {
|
||||
// use json::json;
|
||||
impl<'a> EditorLspTestContext<'a> {
|
||||
pub async fn new(
|
||||
mut language: Language,
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
cx: &'a mut gpui::TestAppContext,
|
||||
) -> EditorLspTestContext<'a> {
|
||||
let app_state = cx.update(AppState::test);
|
||||
|
||||
// let app_state = cx.update(AppState::test);
|
||||
cx.update(|cx| {
|
||||
language::init(cx);
|
||||
crate::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
Project::init_settings(cx);
|
||||
});
|
||||
|
||||
// cx.update(|cx| {
|
||||
// language::init(cx);
|
||||
// crate::init(cx);
|
||||
// workspace::init(app_state.clone(), cx);
|
||||
// Project::init_settings(cx);
|
||||
// });
|
||||
let file_name = format!(
|
||||
"file.{}",
|
||||
language
|
||||
.path_suffixes()
|
||||
.first()
|
||||
.expect("language must have a path suffix for EditorLspTestContext")
|
||||
);
|
||||
|
||||
// let file_name = format!(
|
||||
// "file.{}",
|
||||
// language
|
||||
// .path_suffixes()
|
||||
// .first()
|
||||
// .expect("language must have a path suffix for EditorLspTestContext")
|
||||
// );
|
||||
let mut fake_servers = language
|
||||
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
capabilities,
|
||||
..Default::default()
|
||||
}))
|
||||
.await;
|
||||
|
||||
// let mut fake_servers = language
|
||||
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||
// capabilities,
|
||||
// ..Default::default()
|
||||
// }))
|
||||
// .await;
|
||||
let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
|
||||
// let project = Project::test(app_state.fs.clone(), [], cx).await;
|
||||
// project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
|
||||
|
||||
// app_state
|
||||
// .fs
|
||||
// .as_fake()
|
||||
// .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
|
||||
// .await;
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
|
||||
.await;
|
||||
|
||||
// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
// let workspace = window.root(cx);
|
||||
// project
|
||||
// .update(cx, |project, cx| {
|
||||
// project.find_or_create_local_worktree("/root", true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap();
|
||||
// cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
// .await;
|
||||
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
// let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
// let item = workspace
|
||||
// .update(cx, |workspace, cx| {
|
||||
// workspace.open_path(file, None, true, cx)
|
||||
// })
|
||||
// .await
|
||||
// .expect("Could not open test file");
|
||||
let workspace = window.root_view(cx).unwrap();
|
||||
|
||||
// let editor = cx.update(|cx| {
|
||||
// item.act_as::<Editor>(cx)
|
||||
// .expect("Opened test file wasn't an editor")
|
||||
// });
|
||||
// editor.update(cx, |_, cx| cx.focus_self());
|
||||
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||
project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.find_or_create_local_worktree("/root", true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||
let item = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.open_path(file, None, true, cx)
|
||||
})
|
||||
.await
|
||||
.expect("Could not open test file");
|
||||
let editor = cx.update(|cx| {
|
||||
item.act_as::<Editor>(cx)
|
||||
.expect("Opened test file wasn't an editor")
|
||||
});
|
||||
editor.update(&mut cx, |editor, cx| editor.focus(cx));
|
||||
|
||||
// let lsp = fake_servers.next().await.unwrap();
|
||||
let lsp = fake_servers.next().await.unwrap();
|
||||
Self {
|
||||
cx: EditorTestContext {
|
||||
cx,
|
||||
window: window.into(),
|
||||
editor,
|
||||
assertion_cx: AssertionContextManager::new(),
|
||||
},
|
||||
lsp,
|
||||
workspace,
|
||||
buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
// Self {
|
||||
// cx: EditorTestContext {
|
||||
// cx,
|
||||
// window: window.into(),
|
||||
// editor,
|
||||
// },
|
||||
// lsp,
|
||||
// workspace,
|
||||
// buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
|
||||
// }
|
||||
// }
|
||||
pub async fn new_rust(
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
cx: &'a mut gpui::TestAppContext,
|
||||
) -> EditorLspTestContext<'a> {
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_rust::language()),
|
||||
)
|
||||
.with_queries(LanguageQueries {
|
||||
indents: Some(Cow::from(indoc! {r#"
|
||||
[
|
||||
((where_clause) _ @end)
|
||||
(field_expression)
|
||||
(call_expression)
|
||||
(assignment_expression)
|
||||
(let_declaration)
|
||||
(let_chain)
|
||||
(await_expression)
|
||||
] @indent
|
||||
|
||||
// pub async fn new_rust(
|
||||
// capabilities: lsp::ServerCapabilities,
|
||||
// cx: &'a mut gpui::TestAppContext,
|
||||
// ) -> EditorLspTestContext<'a> {
|
||||
// let language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Rust".into(),
|
||||
// path_suffixes: vec!["rs".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_rust::language()),
|
||||
// )
|
||||
// .with_queries(LanguageQueries {
|
||||
// indents: Some(Cow::from(indoc! {r#"
|
||||
// [
|
||||
// ((where_clause) _ @end)
|
||||
// (field_expression)
|
||||
// (call_expression)
|
||||
// (assignment_expression)
|
||||
// (let_declaration)
|
||||
// (let_chain)
|
||||
// (await_expression)
|
||||
// ] @indent
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "<" ">" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent"#})),
|
||||
brackets: Some(Cow::from(indoc! {r#"
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)
|
||||
(closure_parameters "|" @open "|" @close)"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
||||
// (_ "[" "]" @end) @indent
|
||||
// (_ "<" ">" @end) @indent
|
||||
// (_ "{" "}" @end) @indent
|
||||
// (_ "(" ")" @end) @indent"#})),
|
||||
// brackets: Some(Cow::from(indoc! {r#"
|
||||
// ("(" @open ")" @close)
|
||||
// ("[" @open "]" @close)
|
||||
// ("{" @open "}" @close)
|
||||
// ("<" @open ">" @close)
|
||||
// ("\"" @open "\"" @close)
|
||||
// (closure_parameters "|" @open "|" @close)"#})),
|
||||
// ..Default::default()
|
||||
// })
|
||||
// .expect("Could not parse queries");
|
||||
Self::new(language, capabilities, cx).await
|
||||
}
|
||||
|
||||
// Self::new(language, capabilities, cx).await
|
||||
// }
|
||||
pub async fn new_typescript(
|
||||
capabilities: lsp::ServerCapabilities,
|
||||
cx: &'a mut gpui::TestAppContext,
|
||||
) -> EditorLspTestContext<'a> {
|
||||
let mut word_characters: HashSet<char> = Default::default();
|
||||
word_characters.insert('$');
|
||||
word_characters.insert('#');
|
||||
let language = Language::new(
|
||||
LanguageConfig {
|
||||
name: "Typescript".into(),
|
||||
path_suffixes: vec!["ts".to_string()],
|
||||
brackets: language::BracketPairConfig {
|
||||
pairs: vec![language::BracketPair {
|
||||
start: "{".to_string(),
|
||||
end: "}".to_string(),
|
||||
close: true,
|
||||
newline: true,
|
||||
}],
|
||||
disabled_scopes_by_bracket_ix: Default::default(),
|
||||
},
|
||||
word_characters,
|
||||
..Default::default()
|
||||
},
|
||||
Some(tree_sitter_typescript::language_typescript()),
|
||||
)
|
||||
.with_queries(LanguageQueries {
|
||||
brackets: Some(Cow::from(indoc! {r#"
|
||||
("(" @open ")" @close)
|
||||
("[" @open "]" @close)
|
||||
("{" @open "}" @close)
|
||||
("<" @open ">" @close)
|
||||
("\"" @open "\"" @close)"#})),
|
||||
indents: Some(Cow::from(indoc! {r#"
|
||||
[
|
||||
(call_expression)
|
||||
(assignment_expression)
|
||||
(member_expression)
|
||||
(lexical_declaration)
|
||||
(variable_declaration)
|
||||
(assignment_expression)
|
||||
(if_statement)
|
||||
(for_statement)
|
||||
] @indent
|
||||
|
||||
// pub async fn new_typescript(
|
||||
// capabilities: lsp::ServerCapabilities,
|
||||
// cx: &'a mut gpui::TestAppContext,
|
||||
// ) -> EditorLspTestContext<'a> {
|
||||
// let mut word_characters: HashSet<char> = Default::default();
|
||||
// word_characters.insert('$');
|
||||
// word_characters.insert('#');
|
||||
// let language = Language::new(
|
||||
// LanguageConfig {
|
||||
// name: "Typescript".into(),
|
||||
// path_suffixes: vec!["ts".to_string()],
|
||||
// brackets: language::BracketPairConfig {
|
||||
// pairs: vec![language::BracketPair {
|
||||
// start: "{".to_string(),
|
||||
// end: "}".to_string(),
|
||||
// close: true,
|
||||
// newline: true,
|
||||
// }],
|
||||
// disabled_scopes_by_bracket_ix: Default::default(),
|
||||
// },
|
||||
// word_characters,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// Some(tree_sitter_typescript::language_typescript()),
|
||||
// )
|
||||
// .with_queries(LanguageQueries {
|
||||
// brackets: Some(Cow::from(indoc! {r#"
|
||||
// ("(" @open ")" @close)
|
||||
// ("[" @open "]" @close)
|
||||
// ("{" @open "}" @close)
|
||||
// ("<" @open ">" @close)
|
||||
// ("\"" @open "\"" @close)"#})),
|
||||
// indents: Some(Cow::from(indoc! {r#"
|
||||
// [
|
||||
// (call_expression)
|
||||
// (assignment_expression)
|
||||
// (member_expression)
|
||||
// (lexical_declaration)
|
||||
// (variable_declaration)
|
||||
// (assignment_expression)
|
||||
// (if_statement)
|
||||
// (for_statement)
|
||||
// ] @indent
|
||||
(_ "[" "]" @end) @indent
|
||||
(_ "<" ">" @end) @indent
|
||||
(_ "{" "}" @end) @indent
|
||||
(_ "(" ")" @end) @indent
|
||||
"#})),
|
||||
..Default::default()
|
||||
})
|
||||
.expect("Could not parse queries");
|
||||
|
||||
// (_ "[" "]" @end) @indent
|
||||
// (_ "<" ">" @end) @indent
|
||||
// (_ "{" "}" @end) @indent
|
||||
// (_ "(" ")" @end) @indent
|
||||
// "#})),
|
||||
// ..Default::default()
|
||||
// })
|
||||
// .expect("Could not parse queries");
|
||||
Self::new(language, capabilities, cx).await
|
||||
}
|
||||
|
||||
// Self::new(language, capabilities, cx).await
|
||||
// }
|
||||
// Constructs lsp range using a marked string with '[', ']' range delimiters
|
||||
pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
|
||||
let ranges = self.ranges(marked_text);
|
||||
self.to_lsp_range(ranges[0].clone())
|
||||
}
|
||||
|
||||
// // Constructs lsp range using a marked string with '[', ']' range delimiters
|
||||
// pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
|
||||
// let ranges = self.ranges(marked_text);
|
||||
// self.to_lsp_range(ranges[0].clone())
|
||||
// }
|
||||
pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
|
||||
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
let start_point = range.start.to_point(&snapshot.buffer_snapshot);
|
||||
let end_point = range.end.to_point(&snapshot.buffer_snapshot);
|
||||
|
||||
// pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
|
||||
// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
// let start_point = range.start.to_point(&snapshot.buffer_snapshot);
|
||||
// let end_point = range.end.to_point(&snapshot.buffer_snapshot);
|
||||
self.editor(|editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let start = point_to_lsp(
|
||||
buffer
|
||||
.point_to_buffer_offset(start_point, cx)
|
||||
.unwrap()
|
||||
.1
|
||||
.to_point_utf16(&buffer.read(cx)),
|
||||
);
|
||||
let end = point_to_lsp(
|
||||
buffer
|
||||
.point_to_buffer_offset(end_point, cx)
|
||||
.unwrap()
|
||||
.1
|
||||
.to_point_utf16(&buffer.read(cx)),
|
||||
);
|
||||
|
||||
// self.editor(|editor, cx| {
|
||||
// let buffer = editor.buffer().read(cx);
|
||||
// let start = point_to_lsp(
|
||||
// buffer
|
||||
// .point_to_buffer_offset(start_point, cx)
|
||||
// .unwrap()
|
||||
// .1
|
||||
// .to_point_utf16(&buffer.read(cx)),
|
||||
// );
|
||||
// let end = point_to_lsp(
|
||||
// buffer
|
||||
// .point_to_buffer_offset(end_point, cx)
|
||||
// .unwrap()
|
||||
// .1
|
||||
// .to_point_utf16(&buffer.read(cx)),
|
||||
// );
|
||||
lsp::Range { start, end }
|
||||
})
|
||||
}
|
||||
|
||||
// lsp::Range { start, end }
|
||||
// })
|
||||
// }
|
||||
pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
|
||||
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
let point = offset.to_point(&snapshot.buffer_snapshot);
|
||||
|
||||
// pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
|
||||
// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
// let point = offset.to_point(&snapshot.buffer_snapshot);
|
||||
self.editor(|editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
point_to_lsp(
|
||||
buffer
|
||||
.point_to_buffer_offset(point, cx)
|
||||
.unwrap()
|
||||
.1
|
||||
.to_point_utf16(&buffer.read(cx)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// self.editor(|editor, cx| {
|
||||
// let buffer = editor.buffer().read(cx);
|
||||
// point_to_lsp(
|
||||
// buffer
|
||||
// .point_to_buffer_offset(point, cx)
|
||||
// .unwrap()
|
||||
// .1
|
||||
// .to_point_utf16(&buffer.read(cx)),
|
||||
// )
|
||||
// })
|
||||
// }
|
||||
pub fn update_workspace<F, T>(&mut self, update: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
|
||||
{
|
||||
self.workspace.update(&mut self.cx.cx, update)
|
||||
}
|
||||
|
||||
// pub fn update_workspace<F, T>(&mut self, update: F) -> T
|
||||
// where
|
||||
// F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
|
||||
// {
|
||||
// self.workspace.update(self.cx.cx, update)
|
||||
// }
|
||||
pub fn handle_request<T, F, Fut>(
|
||||
&self,
|
||||
mut handler: F,
|
||||
) -> futures::channel::mpsc::UnboundedReceiver<()>
|
||||
where
|
||||
T: 'static + request::Request,
|
||||
T::Params: 'static + Send,
|
||||
F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
|
||||
Fut: 'static + Send + Future<Output = Result<T::Result>>,
|
||||
{
|
||||
let url = self.buffer_lsp_url.clone();
|
||||
self.lsp.handle_request::<T, _, _>(move |params, cx| {
|
||||
let url = url.clone();
|
||||
handler(url, params, cx)
|
||||
})
|
||||
}
|
||||
|
||||
// pub fn handle_request<T, F, Fut>(
|
||||
// &self,
|
||||
// mut handler: F,
|
||||
// ) -> futures::channel::mpsc::UnboundedReceiver<()>
|
||||
// where
|
||||
// T: 'static + request::Request,
|
||||
// T::Params: 'static + Send,
|
||||
// F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
|
||||
// Fut: 'static + Send + Future<Output = Result<T::Result>>,
|
||||
// {
|
||||
// let url = self.buffer_lsp_url.clone();
|
||||
// self.lsp.handle_request::<T, _, _>(move |params, cx| {
|
||||
// let url = url.clone();
|
||||
// handler(url, params, cx)
|
||||
// })
|
||||
// }
|
||||
pub fn notify<T: notification::Notification>(&self, params: T::Params) {
|
||||
self.lsp.notify::<T>(params);
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn notify<T: notification::Notification>(&self, params: T::Params) {
|
||||
// self.lsp.notify::<T>(params);
|
||||
// }
|
||||
// }
|
||||
impl<'a> Deref for EditorLspTestContext<'a> {
|
||||
type Target = EditorTestContext<'a>;
|
||||
|
||||
// impl<'a> Deref for EditorLspTestContext<'a> {
|
||||
// type Target = EditorTestContext<'a>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.cx
|
||||
}
|
||||
}
|
||||
|
||||
// fn deref(&self) -> &Self::Target {
|
||||
// &self.cx
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<'a> DerefMut for EditorLspTestContext<'a> {
|
||||
// fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// &mut self.cx
|
||||
// }
|
||||
// }
|
||||
impl<'a> DerefMut for EditorLspTestContext<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.cx
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,331 +1,400 @@
|
||||
use crate::{
|
||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||
};
|
||||
use collections::BTreeMap;
|
||||
use futures::Future;
|
||||
use gpui::{
|
||||
AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
|
||||
VisualTestContext, WindowHandle,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, BufferSnapshot};
|
||||
use parking_lot::RwLock;
|
||||
use project::{FakeFs, Project};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
ops::{Deref, DerefMut, Range},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use util::{
|
||||
assert_set_eq,
|
||||
test::{generate_marked_text, marked_text_ranges},
|
||||
};
|
||||
|
||||
// use super::build_editor_with_project;
|
||||
use super::build_editor_with_project;
|
||||
|
||||
// pub struct EditorTestContext<'a> {
|
||||
// pub cx: &'a mut gpui::TestAppContext,
|
||||
// pub window: AnyWindowHandle,
|
||||
// pub editor: View<Editor>,
|
||||
// }
|
||||
pub struct EditorTestContext<'a> {
|
||||
pub cx: gpui::VisualTestContext<'a>,
|
||||
pub window: AnyWindowHandle,
|
||||
pub editor: View<Editor>,
|
||||
pub assertion_cx: AssertionContextManager,
|
||||
}
|
||||
|
||||
// impl<'a> EditorTestContext<'a> {
|
||||
// pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
|
||||
// let fs = FakeFs::new(cx.background());
|
||||
// // fs.insert_file("/file", "".to_owned()).await;
|
||||
// fs.insert_tree(
|
||||
// "/root",
|
||||
// gpui::serde_json::json!({
|
||||
// "file": "",
|
||||
// }),
|
||||
// )
|
||||
// .await;
|
||||
// let project = Project::test(fs, ["/root".as_ref()], cx).await;
|
||||
// let buffer = project
|
||||
// .update(cx, |project, cx| {
|
||||
// project.open_local_buffer("/root/file", cx)
|
||||
// })
|
||||
// .await
|
||||
// .unwrap();
|
||||
// let window = cx.add_window(|cx| {
|
||||
// cx.focus_self();
|
||||
// build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx)
|
||||
// });
|
||||
// let editor = window.root(cx);
|
||||
// Self {
|
||||
// cx,
|
||||
// window: window.into(),
|
||||
// editor,
|
||||
// }
|
||||
// }
|
||||
impl<'a> EditorTestContext<'a> {
|
||||
pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
// fs.insert_file("/file", "".to_owned()).await;
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
gpui::serde_json::json!({
|
||||
"file": "",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, ["/root".as_ref()], cx).await;
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer("/root/file", cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let editor = cx.add_window(|cx| {
|
||||
let editor =
|
||||
build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx);
|
||||
editor.focus(cx);
|
||||
editor
|
||||
});
|
||||
let editor_view = editor.root_view(cx).unwrap();
|
||||
Self {
|
||||
cx: VisualTestContext::from_window(*editor.deref(), cx),
|
||||
window: editor.into(),
|
||||
editor: editor_view,
|
||||
assertion_cx: AssertionContextManager::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn condition(
|
||||
// &self,
|
||||
// predicate: impl FnMut(&Editor, &AppContext) -> bool,
|
||||
// ) -> impl Future<Output = ()> {
|
||||
// self.editor.condition(self.cx, predicate)
|
||||
// }
|
||||
pub fn condition(
|
||||
&self,
|
||||
predicate: impl FnMut(&Editor, &AppContext) -> bool,
|
||||
) -> impl Future<Output = ()> {
|
||||
self.editor.condition::<crate::Event>(&self.cx, predicate)
|
||||
}
|
||||
|
||||
// pub fn editor<F, T>(&self, read: F) -> T
|
||||
// where
|
||||
// F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
|
||||
// {
|
||||
// self.editor.update(self.cx, read)
|
||||
// }
|
||||
#[track_caller]
|
||||
pub fn editor<F, T>(&mut self, read: F) -> T
|
||||
where
|
||||
F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
|
||||
{
|
||||
self.editor
|
||||
.update(&mut self.cx, |this, cx| read(&this, &cx))
|
||||
}
|
||||
|
||||
// pub fn update_editor<F, T>(&mut self, update: F) -> T
|
||||
// where
|
||||
// F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
|
||||
// {
|
||||
// self.editor.update(self.cx, update)
|
||||
// }
|
||||
#[track_caller]
|
||||
pub fn update_editor<F, T>(&mut self, update: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
|
||||
{
|
||||
self.editor.update(&mut self.cx, update)
|
||||
}
|
||||
|
||||
// pub fn multibuffer<F, T>(&self, read: F) -> T
|
||||
// where
|
||||
// F: FnOnce(&MultiBuffer, &AppContext) -> T,
|
||||
// {
|
||||
// self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
|
||||
// }
|
||||
pub fn multibuffer<F, T>(&mut self, read: F) -> T
|
||||
where
|
||||
F: FnOnce(&MultiBuffer, &AppContext) -> T,
|
||||
{
|
||||
self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
|
||||
}
|
||||
|
||||
// pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
|
||||
// where
|
||||
// F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
|
||||
// {
|
||||
// self.update_editor(|editor, cx| editor.buffer().update(cx, update))
|
||||
// }
|
||||
pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
|
||||
{
|
||||
self.update_editor(|editor, cx| editor.buffer().update(cx, update))
|
||||
}
|
||||
|
||||
// pub fn buffer_text(&self) -> String {
|
||||
// self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
|
||||
// }
|
||||
pub fn buffer_text(&mut self) -> String {
|
||||
self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
|
||||
}
|
||||
|
||||
// pub fn buffer<F, T>(&self, read: F) -> T
|
||||
// where
|
||||
// F: FnOnce(&Buffer, &AppContext) -> T,
|
||||
// {
|
||||
// self.multibuffer(|multibuffer, cx| {
|
||||
// let buffer = multibuffer.as_singleton().unwrap().read(cx);
|
||||
// read(buffer, cx)
|
||||
// })
|
||||
// }
|
||||
pub fn buffer<F, T>(&mut self, read: F) -> T
|
||||
where
|
||||
F: FnOnce(&Buffer, &AppContext) -> T,
|
||||
{
|
||||
self.multibuffer(|multibuffer, cx| {
|
||||
let buffer = multibuffer.as_singleton().unwrap().read(cx);
|
||||
read(buffer, cx)
|
||||
})
|
||||
}
|
||||
|
||||
// pub fn update_buffer<F, T>(&mut self, update: F) -> T
|
||||
// where
|
||||
// F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
|
||||
// {
|
||||
// self.update_multibuffer(|multibuffer, cx| {
|
||||
// let buffer = multibuffer.as_singleton().unwrap();
|
||||
// buffer.update(cx, update)
|
||||
// })
|
||||
// }
|
||||
pub fn update_buffer<F, T>(&mut self, update: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
|
||||
{
|
||||
self.update_multibuffer(|multibuffer, cx| {
|
||||
let buffer = multibuffer.as_singleton().unwrap();
|
||||
buffer.update(cx, update)
|
||||
})
|
||||
}
|
||||
|
||||
// pub fn buffer_snapshot(&self) -> BufferSnapshot {
|
||||
// self.buffer(|buffer, _| buffer.snapshot())
|
||||
// }
|
||||
pub fn buffer_snapshot(&mut self) -> BufferSnapshot {
|
||||
self.buffer(|buffer, _| buffer.snapshot())
|
||||
}
|
||||
|
||||
// pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
|
||||
// let keystroke_under_test_handle =
|
||||
// self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
|
||||
// let keystroke = Keystroke::parse(keystroke_text).unwrap();
|
||||
pub fn add_assertion_context(&self, context: String) -> ContextHandle {
|
||||
self.assertion_cx.add_context(context)
|
||||
}
|
||||
|
||||
// self.cx.dispatch_keystroke(self.window, keystroke, false);
|
||||
pub fn assertion_context(&self) -> String {
|
||||
self.assertion_cx.context()
|
||||
}
|
||||
|
||||
// keystroke_under_test_handle
|
||||
// }
|
||||
pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
|
||||
let keystroke_under_test_handle =
|
||||
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
|
||||
let keystroke = Keystroke::parse(keystroke_text).unwrap();
|
||||
|
||||
// pub fn simulate_keystrokes<const COUNT: usize>(
|
||||
// &mut self,
|
||||
// keystroke_texts: [&str; COUNT],
|
||||
// ) -> ContextHandle {
|
||||
// let keystrokes_under_test_handle =
|
||||
// self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
|
||||
// for keystroke_text in keystroke_texts.into_iter() {
|
||||
// self.simulate_keystroke(keystroke_text);
|
||||
// }
|
||||
// // it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete
|
||||
// // before returning.
|
||||
// // NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
|
||||
// // quickly races with async actions.
|
||||
// if let Foreground::Deterministic { cx_id: _, executor } = self.cx.foreground().as_ref() {
|
||||
// executor.run_until_parked();
|
||||
// } else {
|
||||
// unreachable!();
|
||||
// }
|
||||
self.cx.dispatch_keystroke(self.window, keystroke, false);
|
||||
|
||||
// keystrokes_under_test_handle
|
||||
// }
|
||||
keystroke_under_test_handle
|
||||
}
|
||||
|
||||
// pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
|
||||
// let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
|
||||
// assert_eq!(self.buffer_text(), unmarked_text);
|
||||
// ranges
|
||||
// }
|
||||
pub fn simulate_keystrokes<const COUNT: usize>(
|
||||
&mut self,
|
||||
keystroke_texts: [&str; COUNT],
|
||||
) -> ContextHandle {
|
||||
let keystrokes_under_test_handle =
|
||||
self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
|
||||
for keystroke_text in keystroke_texts.into_iter() {
|
||||
self.simulate_keystroke(keystroke_text);
|
||||
}
|
||||
// it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete
|
||||
// before returning.
|
||||
// NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
|
||||
// quickly races with async actions.
|
||||
self.cx.background_executor.run_until_parked();
|
||||
|
||||
// pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
|
||||
// let ranges = self.ranges(marked_text);
|
||||
// let snapshot = self
|
||||
// .editor
|
||||
// .update(self.cx, |editor, cx| editor.snapshot(cx));
|
||||
// ranges[0].start.to_display_point(&snapshot)
|
||||
// }
|
||||
keystrokes_under_test_handle
|
||||
}
|
||||
|
||||
// // Returns anchors for the current buffer using `«` and `»`
|
||||
// pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
|
||||
// let ranges = self.ranges(marked_text);
|
||||
// let snapshot = self.buffer_snapshot();
|
||||
// snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
|
||||
// }
|
||||
pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
|
||||
let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
|
||||
assert_eq!(self.buffer_text(), unmarked_text);
|
||||
ranges
|
||||
}
|
||||
|
||||
// pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
|
||||
// let diff_base = diff_base.map(String::from);
|
||||
// self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
|
||||
// }
|
||||
pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
|
||||
let ranges = self.ranges(marked_text);
|
||||
let snapshot = self
|
||||
.editor
|
||||
.update(&mut self.cx, |editor, cx| editor.snapshot(cx));
|
||||
ranges[0].start.to_display_point(&snapshot)
|
||||
}
|
||||
|
||||
// /// Change the editor's text and selections using a string containing
|
||||
// /// embedded range markers that represent the ranges and directions of
|
||||
// /// each selection.
|
||||
// ///
|
||||
// /// Returns a context handle so that assertion failures can print what
|
||||
// /// editor state was needed to cause the failure.
|
||||
// ///
|
||||
// /// See the `util::test::marked_text_ranges` function for more information.
|
||||
// pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
// let state_context = self.add_assertion_context(format!(
|
||||
// "Initial Editor State: \"{}\"",
|
||||
// marked_text.escape_debug().to_string()
|
||||
// ));
|
||||
// let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||
// self.editor.update(self.cx, |editor, cx| {
|
||||
// editor.set_text(unmarked_text, cx);
|
||||
// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
// s.select_ranges(selection_ranges)
|
||||
// })
|
||||
// });
|
||||
// state_context
|
||||
// }
|
||||
// Returns anchors for the current buffer using `«` and `»`
|
||||
pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
|
||||
let ranges = self.ranges(marked_text);
|
||||
let snapshot = self.buffer_snapshot();
|
||||
snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
|
||||
}
|
||||
|
||||
// /// Only change the editor's selections
|
||||
// pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
// let state_context = self.add_assertion_context(format!(
|
||||
// "Initial Editor State: \"{}\"",
|
||||
// marked_text.escape_debug().to_string()
|
||||
// ));
|
||||
// let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||
// self.editor.update(self.cx, |editor, cx| {
|
||||
// assert_eq!(editor.text(cx), unmarked_text);
|
||||
// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
// s.select_ranges(selection_ranges)
|
||||
// })
|
||||
// });
|
||||
// state_context
|
||||
// }
|
||||
pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
|
||||
let diff_base = diff_base.map(String::from);
|
||||
self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
|
||||
}
|
||||
|
||||
// /// Make an assertion about the editor's text and the ranges and directions
|
||||
// /// of its selections using a string containing embedded range markers.
|
||||
// ///
|
||||
// /// See the `util::test::marked_text_ranges` function for more information.
|
||||
// #[track_caller]
|
||||
// pub fn assert_editor_state(&mut self, marked_text: &str) {
|
||||
// let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
|
||||
// let buffer_text = self.buffer_text();
|
||||
/// Change the editor's text and selections using a string containing
|
||||
/// embedded range markers that represent the ranges and directions of
|
||||
/// each selection.
|
||||
///
|
||||
/// Returns a context handle so that assertion failures can print what
|
||||
/// editor state was needed to cause the failure.
|
||||
///
|
||||
/// See the `util::test::marked_text_ranges` function for more information.
|
||||
pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
let state_context = self.add_assertion_context(format!(
|
||||
"Initial Editor State: \"{}\"",
|
||||
marked_text.escape_debug().to_string()
|
||||
));
|
||||
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||
self.editor.update(&mut self.cx, |editor, cx| {
|
||||
editor.set_text(unmarked_text, cx);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges(selection_ranges)
|
||||
})
|
||||
});
|
||||
state_context
|
||||
}
|
||||
|
||||
// if buffer_text != unmarked_text {
|
||||
// panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
|
||||
// }
|
||||
/// Only change the editor's selections
|
||||
pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
|
||||
let state_context = self.add_assertion_context(format!(
|
||||
"Initial Editor State: \"{}\"",
|
||||
marked_text.escape_debug().to_string()
|
||||
));
|
||||
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||
self.editor.update(&mut self.cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), unmarked_text);
|
||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||
s.select_ranges(selection_ranges)
|
||||
})
|
||||
});
|
||||
state_context
|
||||
}
|
||||
|
||||
// self.assert_selections(expected_selections, marked_text.to_string())
|
||||
// }
|
||||
/// Make an assertion about the editor's text and the ranges and directions
|
||||
/// of its selections using a string containing embedded range markers.
|
||||
///
|
||||
/// See the `util::test::marked_text_ranges` function for more information.
|
||||
#[track_caller]
|
||||
pub fn assert_editor_state(&mut self, marked_text: &str) {
|
||||
let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
|
||||
let buffer_text = self.buffer_text();
|
||||
|
||||
// pub fn editor_state(&mut self) -> String {
|
||||
// generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
|
||||
// }
|
||||
if buffer_text != unmarked_text {
|
||||
panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
|
||||
}
|
||||
|
||||
// #[track_caller]
|
||||
// pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
|
||||
// let expected_ranges = self.ranges(marked_text);
|
||||
// let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
|
||||
// let snapshot = editor.snapshot(cx);
|
||||
// editor
|
||||
// .background_highlights
|
||||
// .get(&TypeId::of::<Tag>())
|
||||
// .map(|h| h.1.clone())
|
||||
// .unwrap_or_default()
|
||||
// .into_iter()
|
||||
// .map(|range| range.to_offset(&snapshot.buffer_snapshot))
|
||||
// .collect()
|
||||
// });
|
||||
// assert_set_eq!(actual_ranges, expected_ranges);
|
||||
// }
|
||||
self.assert_selections(expected_selections, marked_text.to_string())
|
||||
}
|
||||
|
||||
// #[track_caller]
|
||||
// pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
|
||||
// let expected_ranges = self.ranges(marked_text);
|
||||
// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
// let actual_ranges: Vec<Range<usize>> = snapshot
|
||||
// .text_highlight_ranges::<Tag>()
|
||||
// .map(|ranges| ranges.as_ref().clone().1)
|
||||
// .unwrap_or_default()
|
||||
// .into_iter()
|
||||
// .map(|range| range.to_offset(&snapshot.buffer_snapshot))
|
||||
// .collect();
|
||||
// assert_set_eq!(actual_ranges, expected_ranges);
|
||||
// }
|
||||
pub fn editor_state(&mut self) -> String {
|
||||
generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
|
||||
}
|
||||
|
||||
// #[track_caller]
|
||||
// pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
|
||||
// let expected_marked_text =
|
||||
// generate_marked_text(&self.buffer_text(), &expected_selections, true);
|
||||
// self.assert_selections(expected_selections, expected_marked_text)
|
||||
// }
|
||||
#[track_caller]
|
||||
pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
|
||||
let expected_ranges = self.ranges(marked_text);
|
||||
let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
editor
|
||||
.background_highlights
|
||||
.get(&TypeId::of::<Tag>())
|
||||
.map(|h| h.1.clone())
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
|
||||
.collect()
|
||||
});
|
||||
assert_set_eq!(actual_ranges, expected_ranges);
|
||||
}
|
||||
|
||||
// fn editor_selections(&self) -> Vec<Range<usize>> {
|
||||
// self.editor
|
||||
// .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
|
||||
// .into_iter()
|
||||
// .map(|s| {
|
||||
// if s.reversed {
|
||||
// s.end..s.start
|
||||
// } else {
|
||||
// s.start..s.end
|
||||
// }
|
||||
// })
|
||||
// .collect::<Vec<_>>()
|
||||
// }
|
||||
#[track_caller]
|
||||
pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
|
||||
let expected_ranges = self.ranges(marked_text);
|
||||
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
|
||||
let actual_ranges: Vec<Range<usize>> = snapshot
|
||||
.text_highlight_ranges::<Tag>()
|
||||
.map(|ranges| ranges.as_ref().clone().1)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
|
||||
.collect();
|
||||
assert_set_eq!(actual_ranges, expected_ranges);
|
||||
}
|
||||
|
||||
// #[track_caller]
|
||||
// fn assert_selections(
|
||||
// &mut self,
|
||||
// expected_selections: Vec<Range<usize>>,
|
||||
// expected_marked_text: String,
|
||||
// ) {
|
||||
// let actual_selections = self.editor_selections();
|
||||
// let actual_marked_text =
|
||||
// generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
// if expected_selections != actual_selections {
|
||||
// panic!(
|
||||
// indoc! {"
|
||||
#[track_caller]
|
||||
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
|
||||
let expected_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &expected_selections, true);
|
||||
self.assert_selections(expected_selections, expected_marked_text)
|
||||
}
|
||||
|
||||
// {}Editor has unexpected selections.
|
||||
#[track_caller]
|
||||
fn editor_selections(&mut self) -> Vec<Range<usize>> {
|
||||
self.editor
|
||||
.update(&mut self.cx, |editor, cx| {
|
||||
editor.selections.all::<usize>(cx)
|
||||
})
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
if s.reversed {
|
||||
s.end..s.start
|
||||
} else {
|
||||
s.start..s.end
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
// Expected selections:
|
||||
// {}
|
||||
#[track_caller]
|
||||
fn assert_selections(
|
||||
&mut self,
|
||||
expected_selections: Vec<Range<usize>>,
|
||||
expected_marked_text: String,
|
||||
) {
|
||||
let actual_selections = self.editor_selections();
|
||||
let actual_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
if expected_selections != actual_selections {
|
||||
panic!(
|
||||
indoc! {"
|
||||
|
||||
// Actual selections:
|
||||
// {}
|
||||
// "},
|
||||
// self.assertion_context(),
|
||||
// expected_marked_text,
|
||||
// actual_marked_text,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl<'a> Deref for EditorTestContext<'a> {
|
||||
// type Target = gpui::TestAppContext;
|
||||
{}Editor has unexpected selections.
|
||||
|
||||
// fn deref(&self) -> &Self::Target {
|
||||
// self.cx
|
||||
// }
|
||||
// }
|
||||
Expected selections:
|
||||
{}
|
||||
|
||||
// impl<'a> DerefMut for EditorTestContext<'a> {
|
||||
// fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
// &mut self.cx
|
||||
// }
|
||||
// }
|
||||
Actual selections:
|
||||
{}
|
||||
"},
|
||||
self.assertion_context(),
|
||||
expected_marked_text,
|
||||
actual_marked_text,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for EditorTestContext<'a> {
|
||||
type Target = gpui::TestAppContext;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.cx
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for EditorTestContext<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.cx
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks string context to be printed when assertions fail.
|
||||
/// Often this is done by storing a context string in the manager and returning the handle.
|
||||
#[derive(Clone)]
|
||||
pub struct AssertionContextManager {
|
||||
id: Arc<AtomicUsize>,
|
||||
contexts: Arc<RwLock<BTreeMap<usize, String>>>,
|
||||
}
|
||||
|
||||
impl AssertionContextManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
id: Arc::new(AtomicUsize::new(0)),
|
||||
contexts: Arc::new(RwLock::new(BTreeMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_context(&self, context: String) -> ContextHandle {
|
||||
let id = self.id.fetch_add(1, Ordering::Relaxed);
|
||||
let mut contexts = self.contexts.write();
|
||||
contexts.insert(id, context);
|
||||
ContextHandle {
|
||||
id,
|
||||
manager: self.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn context(&self) -> String {
|
||||
let contexts = self.contexts.read();
|
||||
format!("\n{}\n", contexts.values().join("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
|
||||
/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
|
||||
/// the state that was set initially for the failure can be printed in the error message
|
||||
pub struct ContextHandle {
|
||||
id: usize,
|
||||
manager: AssertionContextManager,
|
||||
}
|
||||
|
||||
impl Drop for ContextHandle {
|
||||
fn drop(&mut self) {
|
||||
let mut contexts = self.manager.contexts.write();
|
||||
contexts.remove(&self.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1012,6 +1012,29 @@ impl Context for AppContext {
|
||||
let entity = self.entities.read(handle);
|
||||
read(entity, self)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let window = self
|
||||
.windows
|
||||
.get(window.id)
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
let root_view = window.root_view.clone().unwrap();
|
||||
let view = root_view
|
||||
.downcast::<T>()
|
||||
.map_err(|_| anyhow!("root view's type has changed"))?;
|
||||
|
||||
Ok(read(view, self))
|
||||
}
|
||||
}
|
||||
|
||||
/// These effects are processed at the end of each application update cycle.
|
||||
|
||||
@@ -66,6 +66,19 @@ impl Context for AsyncAppContext {
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.update_window(window, f)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
let lock = app.borrow();
|
||||
lock.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAppContext {
|
||||
@@ -250,6 +263,17 @@ impl Context for AsyncWindowContext {
|
||||
{
|
||||
self.app.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl VisualContext for AsyncWindowContext {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId,
|
||||
EventEmitter, Model, Subscription, Task, WeakModel, WindowContext,
|
||||
EventEmitter, Model, Subscription, Task, View, WeakModel, WindowContext, WindowHandle,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
@@ -239,6 +239,17 @@ impl<'a, T> Context for ModelContext<'a, T> {
|
||||
{
|
||||
self.app.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn read_window<U, R>(
|
||||
&self,
|
||||
window: &WindowHandle<U>,
|
||||
read: impl FnOnce(View<U>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
U: 'static,
|
||||
{
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Borrow<AppContext> for ModelContext<'_, T> {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::{
|
||||
AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
|
||||
EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
|
||||
Render, Result, Task, TestDispatcher, TestPlatform, ViewContext, VisualContext, WindowContext,
|
||||
WindowHandle, WindowOptions,
|
||||
div, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor,
|
||||
Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model,
|
||||
ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, View, ViewContext,
|
||||
VisualContext, WindowContext, WindowHandle, WindowOptions,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
|
||||
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TestAppContext {
|
||||
@@ -58,6 +58,18 @@ impl Context for TestAppContext {
|
||||
let app = self.app.borrow();
|
||||
app.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let app = self.app.borrow();
|
||||
app.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl TestAppContext {
|
||||
@@ -93,8 +105,8 @@ impl TestAppContext {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn executor(&self) -> &BackgroundExecutor {
|
||||
&self.background_executor
|
||||
pub fn executor(&self) -> BackgroundExecutor {
|
||||
self.background_executor.clone()
|
||||
}
|
||||
|
||||
pub fn foreground_executor(&self) -> &ForegroundExecutor {
|
||||
@@ -120,6 +132,26 @@ impl TestAppContext {
|
||||
cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
|
||||
}
|
||||
|
||||
pub fn add_empty_window(&mut self) -> AnyWindowHandle {
|
||||
let mut cx = self.app.borrow_mut();
|
||||
cx.open_window(WindowOptions::default(), |cx| {
|
||||
cx.build_view(|_| EmptyView {})
|
||||
})
|
||||
.any_handle
|
||||
}
|
||||
|
||||
pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, VisualTestContext)
|
||||
where
|
||||
F: FnOnce(&mut ViewContext<V>) -> V,
|
||||
V: Render,
|
||||
{
|
||||
let mut cx = self.app.borrow_mut();
|
||||
let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
|
||||
drop(cx);
|
||||
let view = window.root_view(self).unwrap();
|
||||
(view, VisualTestContext::from_window(*window.deref(), self))
|
||||
}
|
||||
|
||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||
where
|
||||
Fut: Future<Output = R> + 'static,
|
||||
@@ -146,6 +178,11 @@ impl TestAppContext {
|
||||
Some(read(lock.try_global()?, &lock))
|
||||
}
|
||||
|
||||
pub fn set_global<G: 'static>(&mut self, global: G) {
|
||||
let mut lock = self.app.borrow_mut();
|
||||
lock.set_global(global);
|
||||
}
|
||||
|
||||
pub fn update_global<G: 'static, R>(
|
||||
&mut self,
|
||||
update: impl FnOnce(&mut G, &mut AppContext) -> R,
|
||||
@@ -259,3 +296,191 @@ impl<T: Send> Model<T> {
|
||||
.expect("model was dropped")
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> View<V> {
|
||||
pub fn condition<Evt>(
|
||||
&self,
|
||||
cx: &TestAppContext,
|
||||
mut predicate: impl FnMut(&V, &AppContext) -> bool,
|
||||
) -> impl Future<Output = ()>
|
||||
where
|
||||
Evt: 'static,
|
||||
V: EventEmitter<Evt>,
|
||||
{
|
||||
use postage::prelude::{Sink as _, Stream as _};
|
||||
|
||||
let (tx, mut rx) = postage::mpsc::channel(1024);
|
||||
let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
|
||||
|
||||
let mut cx = cx.app.borrow_mut();
|
||||
let subscriptions = (
|
||||
cx.observe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
cx.subscribe(self, {
|
||||
let mut tx = tx.clone();
|
||||
move |_, _: &Evt, _| {
|
||||
tx.blocking_send(()).ok();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let cx = cx.this.upgrade().unwrap();
|
||||
let handle = self.downgrade();
|
||||
|
||||
async move {
|
||||
crate::util::timeout(timeout_duration, async move {
|
||||
loop {
|
||||
{
|
||||
let cx = cx.borrow();
|
||||
let cx = &*cx;
|
||||
if predicate(
|
||||
handle
|
||||
.upgrade()
|
||||
.expect("view dropped with pending condition")
|
||||
.read(cx),
|
||||
cx,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// todo!(start_waiting)
|
||||
// cx.borrow().foreground_executor().start_waiting();
|
||||
rx.recv()
|
||||
.await
|
||||
.expect("view dropped with pending condition");
|
||||
// cx.borrow().foreground_executor().finish_waiting();
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("condition timed out");
|
||||
drop(subscriptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct VisualTestContext<'a> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
cx: &'a mut TestAppContext,
|
||||
window: AnyWindowHandle,
|
||||
}
|
||||
|
||||
impl<'a> VisualTestContext<'a> {
|
||||
pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
|
||||
Self { cx, window }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Context for VisualTestContext<'a> {
|
||||
type Result<T> = <TestAppContext as Context>::Result<T>;
|
||||
|
||||
fn build_model<T: 'static>(
|
||||
&mut self,
|
||||
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
|
||||
) -> Self::Result<Model<T>> {
|
||||
self.cx.build_model(build_model)
|
||||
}
|
||||
|
||||
fn update_model<T, R>(
|
||||
&mut self,
|
||||
handle: &Model<T>,
|
||||
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.cx.update_model(handle, update)
|
||||
}
|
||||
|
||||
fn read_model<T, R>(
|
||||
&self,
|
||||
handle: &Model<T>,
|
||||
read: impl FnOnce(&T, &AppContext) -> R,
|
||||
) -> Self::Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.cx.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
self.cx.update_window(window, f)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.cx.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VisualContext for VisualTestContext<'a> {
|
||||
fn build_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: 'static + Render,
|
||||
{
|
||||
self.window
|
||||
.update(self.cx, |_, cx| cx.build_view(build_view))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn update_view<V: 'static, R>(
|
||||
&mut self,
|
||||
view: &View<V>,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Self::Result<R> {
|
||||
self.window
|
||||
.update(self.cx, |_, cx| cx.update_view(view, update))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn replace_root_view<V>(
|
||||
&mut self,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> Self::Result<View<V>>
|
||||
where
|
||||
V: Render,
|
||||
{
|
||||
self.window
|
||||
.update(self.cx, |_, cx| cx.replace_root_view(build_view))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyWindowHandle {
|
||||
pub fn build_view<V: Render + 'static>(
|
||||
&self,
|
||||
cx: &mut TestAppContext,
|
||||
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
|
||||
) -> View<V> {
|
||||
self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmptyView {}
|
||||
|
||||
impl Render for EmptyView {
|
||||
type Element = Div<Self>;
|
||||
|
||||
fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
|
||||
div()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ impl TryFrom<&'_ str> for Rgba {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Default, Copy, Clone, Debug)]
|
||||
#[repr(C)]
|
||||
pub struct Hsla {
|
||||
pub h: f32,
|
||||
@@ -176,10 +176,63 @@ pub struct Hsla {
|
||||
pub a: f32,
|
||||
}
|
||||
|
||||
impl PartialEq for Hsla {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.h
|
||||
.total_cmp(&other.h)
|
||||
.then(self.s.total_cmp(&other.s))
|
||||
.then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
|
||||
.is_eq()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Hsla {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
// SAFETY: The total ordering relies on this always being Some()
|
||||
Some(
|
||||
self.h
|
||||
.total_cmp(&other.h)
|
||||
.then(self.s.total_cmp(&other.s))
|
||||
.then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Hsla {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
// SAFETY: The partial comparison is a total comparison
|
||||
unsafe { self.partial_cmp(other).unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Hsla {
|
||||
pub fn to_rgb(self) -> Rgba {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn red() -> Self {
|
||||
red()
|
||||
}
|
||||
|
||||
pub fn green() -> Self {
|
||||
green()
|
||||
}
|
||||
|
||||
pub fn blue() -> Self {
|
||||
blue()
|
||||
}
|
||||
|
||||
pub fn black() -> Self {
|
||||
black()
|
||||
}
|
||||
|
||||
pub fn white() -> Self {
|
||||
white()
|
||||
}
|
||||
|
||||
pub fn transparent_black() -> Self {
|
||||
transparent_black()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Hsla {}
|
||||
@@ -238,6 +291,15 @@ pub fn blue() -> Hsla {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn green() -> Hsla {
|
||||
Hsla {
|
||||
h: 0.3,
|
||||
s: 1.,
|
||||
l: 0.5,
|
||||
a: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
impl Hsla {
|
||||
/// Returns true if the HSLA color is fully transparent, false otherwise.
|
||||
pub fn is_transparent(&self) -> bool {
|
||||
|
||||
@@ -81,6 +81,7 @@ impl<V: 'static> Element<V> for Text<V> {
|
||||
let text = self.text.clone();
|
||||
|
||||
let rem_size = cx.rem_size();
|
||||
|
||||
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
|
||||
let element_state = element_state.clone();
|
||||
move |known_dimensions, _| {
|
||||
@@ -93,6 +94,10 @@ impl<V: 'static> Element<V> for Text<V> {
|
||||
)
|
||||
.log_err()
|
||||
else {
|
||||
element_state.lock().replace(TextElementState {
|
||||
lines: Default::default(),
|
||||
line_height,
|
||||
});
|
||||
return Size::default();
|
||||
};
|
||||
|
||||
@@ -131,7 +136,8 @@ impl<V: 'static> Element<V> for Text<V> {
|
||||
let element_state = element_state.lock();
|
||||
let element_state = element_state
|
||||
.as_ref()
|
||||
.expect("measurement has not been performed");
|
||||
.ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))
|
||||
.unwrap();
|
||||
|
||||
let line_height = element_state.line_height;
|
||||
let mut line_origin = bounds.origin;
|
||||
|
||||
@@ -105,6 +105,14 @@ pub trait Context {
|
||||
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
|
||||
where
|
||||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static;
|
||||
}
|
||||
|
||||
pub trait VisualContext: Context {
|
||||
|
||||
@@ -45,7 +45,7 @@ impl<V: 'static> ElementInputHandler<V> {
|
||||
/// containing view.
|
||||
pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self {
|
||||
ElementInputHandler {
|
||||
view: cx.view(),
|
||||
view: cx.view().clone(),
|
||||
element_bounds,
|
||||
cx: cx.to_async(),
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ use parking_lot::Mutex;
|
||||
|
||||
use crate::{
|
||||
px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay,
|
||||
PlatformWindow, Point, Scene, Size, TileId, WindowAppearance, WindowBounds, WindowOptions,
|
||||
PlatformInputHandler, PlatformWindow, Point, Scene, Size, TileId, WindowAppearance,
|
||||
WindowBounds, WindowOptions,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -23,6 +24,7 @@ pub struct TestWindow {
|
||||
bounds: WindowBounds,
|
||||
current_scene: Mutex<Option<Scene>>,
|
||||
display: Rc<dyn PlatformDisplay>,
|
||||
input_handler: Option<Box<dyn PlatformInputHandler>>,
|
||||
|
||||
handlers: Mutex<Handlers>,
|
||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||
@@ -33,7 +35,7 @@ impl TestWindow {
|
||||
bounds: options.bounds,
|
||||
current_scene: Default::default(),
|
||||
display,
|
||||
|
||||
input_handler: None,
|
||||
sprite_atlas: Arc::new(TestAtlas::new()),
|
||||
handlers: Default::default(),
|
||||
}
|
||||
@@ -77,8 +79,8 @@ impl PlatformWindow for TestWindow {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_input_handler(&mut self, _input_handler: Box<dyn crate::PlatformInputHandler>) {
|
||||
todo!()
|
||||
fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
|
||||
self.input_handler = Some(input_handler);
|
||||
}
|
||||
|
||||
fn prompt(
|
||||
|
||||
@@ -54,9 +54,9 @@ impl LineLayout {
|
||||
pub fn closest_index_for_x(&self, x: Pixels) -> usize {
|
||||
let mut prev_index = 0;
|
||||
let mut prev_x = px(0.);
|
||||
|
||||
for run in self.runs.iter() {
|
||||
for glyph in run.glyphs.iter() {
|
||||
glyph.index;
|
||||
if glyph.position.x >= x {
|
||||
if glyph.position.x - x < x - prev_x {
|
||||
return glyph.index;
|
||||
@@ -68,7 +68,7 @@ impl LineLayout {
|
||||
prev_x = glyph.position.x;
|
||||
}
|
||||
}
|
||||
prev_index
|
||||
prev_index + 1
|
||||
}
|
||||
|
||||
pub fn x_for_index(&self, index: usize) -> Pixels {
|
||||
|
||||
+21
-11
@@ -1,16 +1,26 @@
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use futures::Future;
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use smol::future::FutureExt;
|
||||
|
||||
pub use util::*;
|
||||
|
||||
// pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
|
||||
// where
|
||||
// F: Future<Output = T>,
|
||||
// {
|
||||
// let timer = async {
|
||||
// smol::Timer::after(timeout).await;
|
||||
// Err(())
|
||||
// };
|
||||
// let future = async move { Ok(f.await) };
|
||||
// timer.race(future).await
|
||||
// }
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
|
||||
where
|
||||
F: Future<Output = T>,
|
||||
{
|
||||
let timer = async {
|
||||
smol::Timer::after(timeout).await;
|
||||
Err(())
|
||||
};
|
||||
let future = async move { Ok(f.await) };
|
||||
timer.race(future).await
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
|
||||
WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use collections::HashMap;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures::{
|
||||
@@ -1438,6 +1438,28 @@ impl Context for WindowContext<'_> {
|
||||
let entity = self.entities.read(handle);
|
||||
read(&*entity, &*self.app)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
if window.any_handle == self.window.handle {
|
||||
let root_view = self
|
||||
.window
|
||||
.root_view
|
||||
.clone()
|
||||
.unwrap()
|
||||
.downcast::<T>()
|
||||
.map_err(|_| anyhow!("the type of the window's root view has changed"))?;
|
||||
Ok(read(root_view, self))
|
||||
} else {
|
||||
self.app.read_window(window, read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VisualContext for WindowContext<'_> {
|
||||
@@ -1744,9 +1766,12 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
}
|
||||
}
|
||||
|
||||
// todo!("change this to return a reference");
|
||||
pub fn view(&self) -> View<V> {
|
||||
self.view.clone()
|
||||
pub fn entity_id(&self) -> EntityId {
|
||||
self.view.entity_id()
|
||||
}
|
||||
|
||||
pub fn view(&self) -> &View<V> {
|
||||
self.view
|
||||
}
|
||||
|
||||
pub fn model(&self) -> Model<V> {
|
||||
@@ -1769,7 +1794,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
where
|
||||
V: 'static,
|
||||
{
|
||||
let view = self.view();
|
||||
let view = self.view().clone();
|
||||
self.window_cx.on_next_frame(move |cx| view.update(cx, f));
|
||||
}
|
||||
|
||||
@@ -2101,7 +2126,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
&mut self,
|
||||
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
) {
|
||||
let handle = self.view();
|
||||
let handle = self.view().clone();
|
||||
self.window_cx.on_mouse_event(move |event, phase, cx| {
|
||||
handle.update(cx, |view, cx| {
|
||||
handler(view, event, phase, cx);
|
||||
@@ -2113,7 +2138,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
&mut self,
|
||||
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
) {
|
||||
let handle = self.view();
|
||||
let handle = self.view().clone();
|
||||
self.window_cx.on_key_event(move |event, phase, cx| {
|
||||
handle.update(cx, |view, cx| {
|
||||
handler(view, event, phase, cx);
|
||||
@@ -2126,7 +2151,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
|
||||
action_type: TypeId,
|
||||
handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
|
||||
) {
|
||||
let handle = self.view();
|
||||
let handle = self.view().clone();
|
||||
self.window_cx
|
||||
.on_action(action_type, move |action, phase, cx| {
|
||||
handle.update(cx, |view, cx| {
|
||||
@@ -2201,6 +2226,17 @@ impl<V> Context for ViewContext<'_, V> {
|
||||
{
|
||||
self.window_cx.read_model(handle, read)
|
||||
}
|
||||
|
||||
fn read_window<T, R>(
|
||||
&self,
|
||||
window: &WindowHandle<T>,
|
||||
read: impl FnOnce(View<T>, &AppContext) -> R,
|
||||
) -> Result<R>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.window_cx.read_window(window, read)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: 'static> VisualContext for ViewContext<'_, V> {
|
||||
@@ -2273,7 +2309,7 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||
}
|
||||
|
||||
pub fn update<C, R>(
|
||||
self,
|
||||
&self,
|
||||
cx: &mut C,
|
||||
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
|
||||
) -> Result<R>
|
||||
@@ -2287,6 +2323,36 @@ impl<V: 'static + Render> WindowHandle<V> {
|
||||
Ok(cx.update_view(&view, update))
|
||||
})?
|
||||
}
|
||||
|
||||
pub fn read<'a>(&self, cx: &'a AppContext) -> Result<&'a V> {
|
||||
let x = cx
|
||||
.windows
|
||||
.get(self.id)
|
||||
.and_then(|window| {
|
||||
window
|
||||
.as_ref()
|
||||
.and_then(|window| window.root_view.clone())
|
||||
.map(|root_view| root_view.downcast::<V>())
|
||||
})
|
||||
.ok_or_else(|| anyhow!("window not found"))?
|
||||
.map_err(|_| anyhow!("the type of the window's root view has changed"))?;
|
||||
|
||||
Ok(x.read(cx))
|
||||
}
|
||||
|
||||
pub fn read_with<C, R>(&self, cx: &C, read_with: impl FnOnce(&V, &AppContext) -> R) -> Result<R>
|
||||
where
|
||||
C: Context,
|
||||
{
|
||||
cx.read_window(self, |root_view, cx| read_with(root_view.read(cx), cx))
|
||||
}
|
||||
|
||||
pub fn root_view<C>(&self, cx: &C) -> Result<View<V>>
|
||||
where
|
||||
C: Context,
|
||||
{
|
||||
cx.read_window(self, |root_view, _cx| root_view.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Copy for WindowHandle<V> {}
|
||||
@@ -2352,6 +2418,18 @@ impl AnyWindowHandle {
|
||||
{
|
||||
cx.update_window(self, update)
|
||||
}
|
||||
|
||||
pub fn read<T, C, R>(self, cx: &C, read: impl FnOnce(View<T>, &AppContext) -> R) -> Result<R>
|
||||
where
|
||||
C: Context,
|
||||
T: 'static,
|
||||
{
|
||||
let view = self
|
||||
.downcast::<T>()
|
||||
.context("the type of the window's root view has changed")?;
|
||||
|
||||
cx.read_window(&view, read)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
|
||||
@@ -1858,7 +1858,7 @@ mod tests {
|
||||
async fn test_first_line_pattern(cx: &mut TestAppContext) {
|
||||
let mut languages = LanguageRegistry::test();
|
||||
|
||||
languages.set_executor(cx.executor().clone());
|
||||
languages.set_executor(cx.executor());
|
||||
let languages = Arc::new(languages);
|
||||
languages.register(
|
||||
"/javascript",
|
||||
@@ -1895,7 +1895,7 @@ mod tests {
|
||||
#[gpui::test(iterations = 10)]
|
||||
async fn test_language_loading(cx: &mut TestAppContext) {
|
||||
let mut languages = LanguageRegistry::test();
|
||||
languages.set_executor(cx.executor().clone());
|
||||
languages.set_executor(cx.executor());
|
||||
let languages = Arc::new(languages);
|
||||
languages.register(
|
||||
"/JSON",
|
||||
|
||||
@@ -497,7 +497,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
@@ -573,7 +573,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
@@ -638,7 +638,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
@@ -731,7 +731,7 @@ mod tests {
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
@@ -812,7 +812,7 @@ mod tests {
|
||||
async fn test_prettier_lookup_in_npm_workspaces_for_not_installed(
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
|
||||
@@ -863,7 +863,7 @@ impl Project {
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) -> Model<Project> {
|
||||
let mut languages = LanguageRegistry::test();
|
||||
languages.set_executor(cx.executor().clone());
|
||||
languages.set_executor(cx.executor());
|
||||
let http_client = util::http::FakeHttpClient::with_404_response();
|
||||
let client = cx.update(|cx| client::Client::new(http_client.clone(), cx));
|
||||
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
|
||||
|
||||
@@ -89,7 +89,7 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/the-root",
|
||||
json!({
|
||||
@@ -189,7 +189,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/the-root",
|
||||
json!({
|
||||
@@ -547,7 +547,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/the-root",
|
||||
json!({
|
||||
@@ -734,7 +734,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
|
||||
async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -826,7 +826,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/root",
|
||||
json!({
|
||||
@@ -914,7 +914,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -1046,7 +1046,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
@@ -1125,7 +1125,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
@@ -1215,7 +1215,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
@@ -1279,7 +1279,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
|
||||
.await;
|
||||
|
||||
@@ -1401,7 +1401,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
@@ -1671,7 +1671,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
|
||||
"let three = 3;\n",
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
|
||||
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
@@ -1734,7 +1734,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
|
||||
.await;
|
||||
|
||||
@@ -1813,7 +1813,7 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -1959,7 +1959,7 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAp
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2067,7 +2067,7 @@ async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2187,7 +2187,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
|
||||
);
|
||||
let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2299,7 +2299,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2396,7 +2396,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2451,7 +2451,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
);
|
||||
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2559,7 +2559,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_save_file(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2591,7 +2591,7 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2622,7 +2622,7 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_save_as(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree("/dir", json!({})).await;
|
||||
|
||||
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
|
||||
@@ -2830,7 +2830,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2881,7 +2881,7 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -2927,7 +2927,7 @@ async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -3074,7 +3074,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let initial_contents = "aaa\nbbbbb\nc\n";
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -3154,7 +3154,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -3216,7 +3216,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/the-dir",
|
||||
json!({
|
||||
@@ -3479,7 +3479,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||
}))
|
||||
.await;
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -3596,7 +3596,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_search(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -3655,7 +3655,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
let search_query = "file";
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -3767,7 +3767,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
let search_query = "file";
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
@@ -3878,7 +3878,7 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
|
||||
|
||||
let search_query = "file";
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
"/dir",
|
||||
json!({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -577,18 +577,18 @@ mod tests {
|
||||
let client2 = Peer::new(0);
|
||||
|
||||
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
|
||||
Connection::in_memory(cx.executor().clone());
|
||||
Connection::in_memory(cx.executor());
|
||||
let (client1_conn_id, io_task1, client1_incoming) =
|
||||
client1.add_test_connection(client1_to_server_conn, cx.executor().clone());
|
||||
client1.add_test_connection(client1_to_server_conn, cx.executor());
|
||||
let (_, io_task2, server_incoming1) =
|
||||
server.add_test_connection(server_to_client_1_conn, cx.executor().clone());
|
||||
server.add_test_connection(server_to_client_1_conn, cx.executor());
|
||||
|
||||
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
|
||||
Connection::in_memory(cx.executor().clone());
|
||||
Connection::in_memory(cx.executor());
|
||||
let (client2_conn_id, io_task3, client2_incoming) =
|
||||
client2.add_test_connection(client2_to_server_conn, cx.executor().clone());
|
||||
client2.add_test_connection(client2_to_server_conn, cx.executor());
|
||||
let (_, io_task4, server_incoming2) =
|
||||
server.add_test_connection(server_to_client_2_conn, cx.executor().clone());
|
||||
server.add_test_connection(server_to_client_2_conn, cx.executor());
|
||||
|
||||
executor.spawn(io_task1).detach();
|
||||
executor.spawn(io_task2).detach();
|
||||
@@ -763,16 +763,16 @@ mod tests {
|
||||
|
||||
#[gpui::test(iterations = 50)]
|
||||
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
|
||||
let executor = cx.executor().clone();
|
||||
let executor = cx.executor();
|
||||
let server = Peer::new(0);
|
||||
let client = Peer::new(0);
|
||||
|
||||
let (client_to_server_conn, server_to_client_conn, _kill) =
|
||||
Connection::in_memory(cx.executor().clone());
|
||||
Connection::in_memory(cx.executor());
|
||||
let (client_to_server_conn_id, io_task1, mut client_incoming) =
|
||||
client.add_test_connection(client_to_server_conn, cx.executor().clone());
|
||||
client.add_test_connection(client_to_server_conn, cx.executor());
|
||||
let (server_to_client_conn_id, io_task2, mut server_incoming) =
|
||||
server.add_test_connection(server_to_client_conn, cx.executor().clone());
|
||||
server.add_test_connection(server_to_client_conn, cx.executor());
|
||||
|
||||
executor.spawn(io_task1).detach();
|
||||
executor.spawn(io_task2).detach();
|
||||
|
||||
@@ -1319,53 +1319,56 @@ impl Workspace {
|
||||
// }))
|
||||
// }
|
||||
|
||||
// pub fn prepare_to_close(
|
||||
// &mut self,
|
||||
// quitting: bool,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) -> Task<Result<bool>> {
|
||||
// let active_call = self.active_call().cloned();
|
||||
// let window = cx.window();
|
||||
pub fn prepare_to_close(
|
||||
&mut self,
|
||||
quitting: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Task<Result<bool>> {
|
||||
//todo!(saveing)
|
||||
// let active_call = self.active_call().cloned();
|
||||
// let window = cx.window();
|
||||
|
||||
// cx.spawn(|this, mut cx| async move {
|
||||
// let workspace_count = cx
|
||||
// .windows()
|
||||
// .into_iter()
|
||||
// .filter(|window| window.root_is::<Workspace>())
|
||||
// .count();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
// let workspace_count = cx
|
||||
// .windows()
|
||||
// .into_iter()
|
||||
// .filter(|window| window.root_is::<Workspace>())
|
||||
// .count();
|
||||
|
||||
// if let Some(active_call) = active_call {
|
||||
// if !quitting
|
||||
// && workspace_count == 1
|
||||
// && active_call.read_with(&cx, |call, _| call.room().is_some())
|
||||
// {
|
||||
// let answer = window.prompt(
|
||||
// PromptLevel::Warning,
|
||||
// "Do you want to leave the current call?",
|
||||
// &["Close window and hang up", "Cancel"],
|
||||
// &mut cx,
|
||||
// );
|
||||
// if let Some(active_call) = active_call {
|
||||
// if !quitting
|
||||
// && workspace_count == 1
|
||||
// && active_call.read_with(&cx, |call, _| call.room().is_some())
|
||||
// {
|
||||
// let answer = window.prompt(
|
||||
// PromptLevel::Warning,
|
||||
// "Do you want to leave the current call?",
|
||||
// &["Close window and hang up", "Cancel"],
|
||||
// &mut cx,
|
||||
// );
|
||||
|
||||
// if let Some(mut answer) = answer {
|
||||
// if answer.next().await == Some(1) {
|
||||
// return anyhow::Ok(false);
|
||||
// } else {
|
||||
// active_call
|
||||
// .update(&mut cx, |call, cx| call.hang_up(cx))
|
||||
// .await
|
||||
// .log_err();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if let Some(mut answer) = answer {
|
||||
// if answer.next().await == Some(1) {
|
||||
// return anyhow::Ok(false);
|
||||
// } else {
|
||||
// active_call
|
||||
// .update(&mut cx, |call, cx| call.hang_up(cx))
|
||||
// .await
|
||||
// .log_err();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Ok(this
|
||||
// .update(&mut cx, |this, cx| {
|
||||
// this.save_all_internal(SaveIntent::Close, cx)
|
||||
// })?
|
||||
// .await?)
|
||||
// })
|
||||
// }
|
||||
Ok(
|
||||
false, // this
|
||||
// .update(&mut cx, |this, cx| {
|
||||
// this.save_all_internal(SaveIntent::Close, cx)
|
||||
// })?
|
||||
// .await?
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// fn save_all(
|
||||
// &mut self,
|
||||
@@ -4201,24 +4204,24 @@ impl ViewId {
|
||||
}
|
||||
}
|
||||
|
||||
// pub trait WorkspaceHandle {
|
||||
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
|
||||
// }
|
||||
pub trait WorkspaceHandle {
|
||||
fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
|
||||
}
|
||||
|
||||
// impl WorkspaceHandle for View<Workspace> {
|
||||
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
|
||||
// self.read(cx)
|
||||
// .worktrees(cx)
|
||||
// .flat_map(|worktree| {
|
||||
// let worktree_id = worktree.read(cx).id();
|
||||
// worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
|
||||
// worktree_id,
|
||||
// path: f.path.clone(),
|
||||
// })
|
||||
// })
|
||||
// .collect::<Vec<_>>()
|
||||
// }
|
||||
// }
|
||||
impl WorkspaceHandle for View<Workspace> {
|
||||
fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
|
||||
self.read(cx)
|
||||
.worktrees(cx)
|
||||
.flat_map(|worktree| {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
|
||||
worktree_id,
|
||||
path: f.path.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
// impl std::fmt::Debug for OpenPaths {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
|
||||
@@ -9,6 +9,7 @@ use backtrace::Backtrace;
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
use client::UserStore;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::Editor;
|
||||
use fs::RealFs;
|
||||
use futures::StreamExt;
|
||||
use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
|
||||
@@ -357,8 +358,7 @@ async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncApp
|
||||
} else {
|
||||
cx.update(|cx| {
|
||||
workspace::open_new(app_state, cx, |workspace, cx| {
|
||||
// todo!(editor)
|
||||
// Editor::new_file(workspace, &Default::default(), cx)
|
||||
Editor::new_file(workspace, &Default::default(), cx)
|
||||
})
|
||||
.detach();
|
||||
})?;
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn initialize_workspace(
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
workspace_handle.update(&mut cx, |workspace, cx| {
|
||||
let workspace_handle = cx.view();
|
||||
let workspace_handle = cx.view().clone();
|
||||
cx.subscribe(&workspace_handle, {
|
||||
move |workspace, _, event, cx| {
|
||||
if let workspace::Event::PaneAdded(pane) = event {
|
||||
|
||||
Reference in New Issue
Block a user