Merge branch 'main' into project-panel2

This commit is contained in:
Max Brunsfeld 2023-11-14 09:33:48 -08:00
commit b893ac2a02
65 changed files with 9847 additions and 8976 deletions

3
Cargo.lock generated
View file

@ -2012,7 +2012,7 @@ dependencies = [
"serde_derive", "serde_derive",
"settings2", "settings2",
"smol", "smol",
"theme", "theme2",
"util", "util",
] ]
@ -2768,7 +2768,6 @@ dependencies = [
"copilot2", "copilot2",
"ctor", "ctor",
"db2", "db2",
"drag_and_drop",
"env_logger 0.9.3", "env_logger 0.9.3",
"futures 0.3.28", "futures 0.3.28",
"fuzzy2", "fuzzy2",

View file

@ -36,7 +36,7 @@ impl FakeServer {
peer: Peer::new(0), peer: Peer::new(0),
state: Default::default(), state: Default::default(),
user_id: client_user_id, user_id: client_user_id,
executor: cx.executor().clone(), executor: cx.executor(),
}; };
client client

View file

@ -510,7 +510,7 @@ fn test_fuzzy_like_string() {
#[gpui::test] #[gpui::test]
async fn test_fuzzy_search_users(cx: &mut TestAppContext) { 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(); let db = test_db.db();
for (i, github_login) in [ for (i, github_login) in [
"California", "California",

View file

@ -4,6 +4,7 @@ use gpui::{Model, TestAppContext};
mod channel_buffer_tests; mod channel_buffer_tests;
mod channel_message_tests; mod channel_message_tests;
mod channel_tests; mod channel_tests;
mod editor_tests;
mod following_tests; mod following_tests;
mod integration_tests; mod integration_tests;
mod notification_tests; mod notification_tests;

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,32 @@
// use editor::{ //todo(partially ported)
// test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion, // use std::{
// ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo, // 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)] // #[gpui::test(iterations = 10)]
// async fn test_host_disconnect( // async fn test_host_disconnect(
// executor: BackgroundExecutor, // executor: BackgroundExecutor,
@ -11,7 +34,7 @@
// cx_b: &mut TestAppContext, // cx_b: &mut TestAppContext,
// cx_c: &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_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await; // let client_b = server.create_client(cx_b, "user_b").await;
// let client_c = server.create_client(cx_c, "user_c").await; // let client_c = server.create_client(cx_c, "user_c").await;
@ -25,7 +48,7 @@
// .fs() // .fs()
// .insert_tree( // .insert_tree(
// "/a", // "/a",
// json!({ // serde_json::json!({
// "a.txt": "a-contents", // "a.txt": "a-contents",
// "b.txt": "b-contents", // "b.txt": "b-contents",
// }), // }),
@ -35,7 +58,7 @@
// let active_call_a = cx_a.read(ActiveCall::global); // let active_call_a = cx_a.read(ActiveCall::global);
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; // 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 // let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) // .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await // .await
@ -46,21 +69,25 @@
// assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); // 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)); // 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 // let editor_b = workspace_b
// .update(cx_b, |workspace, cx| { // .update(cx_b, |workspace, cx| {
// workspace.open_path((worktree_id, "b.txt"), None, true, cx) // workspace.open_path((worktree_id, "b.txt"), None, true, cx)
// }) // })
// .unwrap()
// .await // .await
// .unwrap() // .unwrap()
// .downcast::<Editor>() // .downcast::<Editor>()
// .unwrap(); // .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)); // 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. // // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
// server.forbid_connections(); // server.forbid_connections();
@ -77,10 +104,10 @@
// // Ensure client B's edited state is reset and that the whole window is blurred. // // 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_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. // // Ensure client B is not prompted to save edits when closing window after disconnecting.
// let can_close = workspace_b // let can_close = workspace_b
@ -120,7 +147,6 @@
// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared())); // project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
// } // }
//todo!(editor)
// #[gpui::test] // #[gpui::test]
// async fn test_newline_above_or_below_does_not_move_guest_cursor( // async fn test_newline_above_or_below_does_not_move_guest_cursor(
// executor: BackgroundExecutor, // executor: BackgroundExecutor,
@ -152,12 +178,14 @@
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) // .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
// .await // .await
// .unwrap(); // .unwrap();
// let window_a = cx_a.add_window(|_| EmptyView); // let window_a = cx_a.add_empty_window();
// let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); // 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 { // let mut editor_cx_a = EditorTestContext {
// cx: cx_a, // cx: cx_a,
// window: window_a.into(), // window: window_a.into(),
// editor: editor_a, // editor: editor_a,
// assertion_cx: AssertionContextManager::new(),
// }; // };
// // Open a buffer as client B // // Open a buffer as client B
@ -165,12 +193,14 @@
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) // .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
// .await // .await
// .unwrap(); // .unwrap();
// let window_b = cx_b.add_window(|_| EmptyView); // let window_b = cx_b.add_empty_window();
// let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx)); // 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 { // let mut editor_cx_b = EditorTestContext {
// cx: cx_b, // cx: cx_b,
// window: window_b.into(), // window: window_b.into(),
// editor: editor_b, // editor: editor_b,
// assertion_cx: AssertionContextManager::new(),
// }; // };
// // Test newline above // // Test newline above
@ -214,7 +244,6 @@
// "}); // "});
// } // }
//todo!(editor)
// #[gpui::test(iterations = 10)] // #[gpui::test(iterations = 10)]
// async fn test_collaborating_with_completion( // async fn test_collaborating_with_completion(
// executor: BackgroundExecutor, // executor: BackgroundExecutor,
@ -275,8 +304,8 @@
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) // .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
// .await // .await
// .unwrap(); // .unwrap();
// let window_b = cx_b.add_window(|_| EmptyView); // let window_b = cx_b.add_empty_window();
// let editor_b = window_b.add_view(cx_b, |cx| { // let editor_b = window_b.build_view(cx_b, |cx| {
// Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx) // Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
// }); // });
@ -384,7 +413,7 @@
// ); // );
// // The additional edit is applied. // // The additional edit is applied.
// cx_a.foreground().run_until_parked(); // cx_a.executor().run_until_parked();
// buffer_a.read_with(cx_a, |buffer, _| { // buffer_a.read_with(cx_a, |buffer, _| {
// assert_eq!( // assert_eq!(
@ -400,7 +429,7 @@
// ); // );
// }); // });
// } // }
//todo!(editor)
// #[gpui::test(iterations = 10)] // #[gpui::test(iterations = 10)]
// async fn test_collaborating_with_code_actions( // async fn test_collaborating_with_code_actions(
// executor: BackgroundExecutor, // executor: BackgroundExecutor,
@ -619,7 +648,6 @@
// }); // });
// } // }
//todo!(editor)
// #[gpui::test(iterations = 10)] // #[gpui::test(iterations = 10)]
// async fn test_collaborating_with_renames( // async fn test_collaborating_with_renames(
// executor: BackgroundExecutor, // executor: BackgroundExecutor,
@ -813,7 +841,6 @@
// }) // })
// } // }
//todo!(editor)
// #[gpui::test(iterations = 10)] // #[gpui::test(iterations = 10)]
// async fn test_language_server_statuses( // async fn test_language_server_statuses(
// executor: BackgroundExecutor, // executor: BackgroundExecutor,
@ -937,8 +964,8 @@
// cx_b: &mut TestAppContext, // cx_b: &mut TestAppContext,
// cx_c: &mut TestAppContext, // cx_c: &mut TestAppContext,
// ) { // ) {
// let window_b = cx_b.add_window(|_| EmptyView); // let window_b = cx_b.add_empty_window();
// 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_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await; // let client_b = server.create_client(cx_b, "user_b").await;
// let client_c = server.create_client(cx_c, "user_c").await; // let client_c = server.create_client(cx_c, "user_c").await;
@ -1052,7 +1079,7 @@
// .await // .await
// .unwrap(); // .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 // // Client A sees client B's selection
// executor.run_until_parked(); // executor.run_until_parked();
@ -1106,3 +1133,757 @@
// == 0 // == 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
// }

View file

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

View file

@ -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 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 workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
let mut language_registry = LanguageRegistry::test(); 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 { let app_state = Arc::new(workspace::AppState {
client: client.clone(), client: client.clone(),
user_store: user_store.clone(), user_store: user_store.clone(),

View file

@ -11,7 +11,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::{v_stack, HighlightedLabel, StyledExt}; use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
use util::{ use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL}, channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt, ResultExt,
@ -318,66 +318,16 @@ impl PickerDelegate for CommandPaletteDelegate {
.rounded_md() .rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected)) .when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover)) .hover(|this| this.bg(colors.ghost_element_hover))
.child(HighlightedLabel::new( .child(
command.name.clone(), h_stack()
r#match.positions.clone(), .justify_between()
)) .child(HighlightedLabel::new(
command.name.clone(),
r#match.positions.clone(),
))
.children(KeyBinding::for_action(&*command.action, cx)),
)
} }
// fn render_match(
// &self,
// ix: usize,
// mouse_state: &mut MouseState,
// selected: bool,
// cx: &gpui::AppContext,
// ) -> AnyElement<Picker<Self>> {
// let mat = &self.matches[ix];
// let command = &self.actions[mat.candidate_id];
// let theme = theme::current(cx);
// let style = theme.picker.item.in_state(selected).style_for(mouse_state);
// let key_style = &theme.command_palette.key.in_state(selected);
// let keystroke_spacing = theme.command_palette.keystroke_spacing;
// Flex::row()
// .with_child(
// Label::new(mat.string.clone(), style.label.clone())
// .with_highlights(mat.positions.clone()),
// )
// .with_children(command.keystrokes.iter().map(|keystroke| {
// Flex::row()
// .with_children(
// [
// (keystroke.ctrl, "^"),
// (keystroke.alt, "⌥"),
// (keystroke.cmd, "⌘"),
// (keystroke.shift, "⇧"),
// ]
// .into_iter()
// .filter_map(|(modifier, label)| {
// if modifier {
// Some(
// Label::new(label, key_style.label.clone())
// .contained()
// .with_style(key_style.container),
// )
// } else {
// None
// }
// }),
// )
// .with_child(
// Label::new(keystroke.key.clone(), key_style.label.clone())
// .contained()
// .with_style(key_style.container),
// )
// .contained()
// .with_margin_left(keystroke_spacing)
// .flex_float()
// }))
// .contained()
// .with_style(style.container)
// .into_any()
// }
} }
fn humanize_action_name(name: &str) -> String { fn humanize_action_name(name: &str) -> String {

View file

@ -24,7 +24,7 @@ collections = { path = "../collections" }
gpui = { package = "gpui2", path = "../gpui2" } gpui = { package = "gpui2", path = "../gpui2" }
language = { package = "language2", path = "../language2" } language = { package = "language2", path = "../language2" }
settings = { package = "settings2", path = "../settings2" } settings = { package = "settings2", path = "../settings2" }
theme = { path = "../theme" } theme = { package = "theme2", path = "../theme2" }
lsp = { package = "lsp2", path = "../lsp2" } lsp = { package = "lsp2", path = "../lsp2" }
node_runtime = { path = "../node_runtime"} node_runtime = { path = "../node_runtime"}
util = { path = "../util" } util = { path = "../util" }

View file

@ -351,28 +351,29 @@ impl Copilot {
} }
} }
// #[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
// pub fn fake(cx: &mut gpui::TestAppContext) -> (ModelHandle<Self>, lsp::FakeLanguageServer) { pub fn fake(cx: &mut gpui::TestAppContext) -> (Model<Self>, lsp::FakeLanguageServer) {
// use node_runtime::FakeNodeRuntime; use node_runtime::FakeNodeRuntime;
// let (server, fake_server) = let (server, fake_server) =
// LanguageServer::fake("copilot".into(), Default::default(), cx.to_async()); LanguageServer::fake("copilot".into(), Default::default(), cx.to_async());
// let http = util::http::FakeHttpClient::create(|_| async { unreachable!() }); let http = util::http::FakeHttpClient::create(|_| async { unreachable!() });
// let node_runtime = FakeNodeRuntime::new(); let node_runtime = FakeNodeRuntime::new();
// let this = cx.add_model(|_| Self { let this = cx.build_model(|cx| Self {
// server_id: LanguageServerId(0), server_id: LanguageServerId(0),
// http: http.clone(), http: http.clone(),
// node_runtime, node_runtime,
// server: CopilotServer::Running(RunningCopilotServer { server: CopilotServer::Running(RunningCopilotServer {
// name: LanguageServerName(Arc::from("copilot")), name: LanguageServerName(Arc::from("copilot")),
// lsp: Arc::new(server), lsp: Arc::new(server),
// sign_in_status: SignInStatus::Authorized, sign_in_status: SignInStatus::Authorized,
// registered_buffers: Default::default(), registered_buffers: Default::default(),
// }), }),
// buffers: Default::default(), _subscription: cx.on_app_quit(Self::shutdown_language_server),
// }); buffers: Default::default(),
// (this, fake_server) });
// } (this, fake_server)
}
fn start_language_server( fn start_language_server(
new_server_id: LanguageServerId, new_server_id: LanguageServerId,

View file

@ -27,7 +27,6 @@ client = { package = "client2", path = "../client2" }
clock = { path = "../clock" } clock = { path = "../clock" }
copilot = { package="copilot2", path = "../copilot2" } copilot = { package="copilot2", path = "../copilot2" }
db = { package="db2", path = "../db2" } db = { package="db2", path = "../db2" }
drag_and_drop = { path = "../drag_and_drop" }
collections = { path = "../collections" } collections = { path = "../collections" }
# context_menu = { path = "../context_menu" } # context_menu = { path = "../context_menu" }
fuzzy = { package = "fuzzy2", path = "../fuzzy2" } fuzzy = { package = "fuzzy2", path = "../fuzzy2" }

View file

@ -2023,24 +2023,24 @@ impl Editor {
dispatch_context dispatch_context
} }
// pub fn new_file( pub fn new_file(
// workspace: &mut Workspace, workspace: &mut Workspace,
// _: &workspace::NewFile, _: &workspace::NewFile,
// cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
// ) { ) {
// let project = workspace.project().clone(); let project = workspace.project().clone();
// if project.read(cx).is_remote() { if project.read(cx).is_remote() {
// cx.propagate(); cx.propagate();
// } else if let Some(buffer) = project } else if let Some(buffer) = project
// .update(cx, |project, cx| project.create_buffer("", None, cx)) .update(cx, |project, cx| project.create_buffer("", None, cx))
// .log_err() .log_err()
// { {
// workspace.add_item( workspace.add_item(
// Box::new(cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))), Box::new(cx.build_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
// cx, cx,
// ); );
// } }
// } }
// pub fn new_file_in_direction( // pub fn new_file_in_direction(
// workspace: &mut Workspace, // workspace: &mut Workspace,
@ -2124,17 +2124,17 @@ impl Editor {
// ) // )
// } // }
// pub fn mode(&self) -> EditorMode { pub fn mode(&self) -> EditorMode {
// self.mode self.mode
// } }
// pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> { pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
// self.collaboration_hub.as_deref() self.collaboration_hub.as_deref()
// } }
// pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) { pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
// self.collaboration_hub = Some(hub); self.collaboration_hub = Some(hub);
// } }
pub fn set_placeholder_text( pub fn set_placeholder_text(
&mut self, &mut self,
@ -10056,76 +10056,76 @@ pub fn diagnostic_style(
} }
} }
// pub fn combine_syntax_and_fuzzy_match_highlights( pub fn combine_syntax_and_fuzzy_match_highlights(
// text: &str, text: &str,
// default_style: HighlightStyle, default_style: HighlightStyle,
// syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>, syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
// match_indices: &[usize], match_indices: &[usize],
// ) -> Vec<(Range<usize>, HighlightStyle)> { ) -> Vec<(Range<usize>, HighlightStyle)> {
// let mut result = Vec::new(); let mut result = Vec::new();
// let mut match_indices = match_indices.iter().copied().peekable(); let mut match_indices = match_indices.iter().copied().peekable();
// for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())]) for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
// { {
// syntax_highlight.weight = None; syntax_highlight.font_weight = None;
// // Add highlights for any fuzzy match characters before the next // Add highlights for any fuzzy match characters before the next
// // syntax highlight range. // syntax highlight range.
// while let Some(&match_index) = match_indices.peek() { while let Some(&match_index) = match_indices.peek() {
// if match_index >= range.start { if match_index >= range.start {
// break; break;
// } }
// match_indices.next(); match_indices.next();
// let end_index = char_ix_after(match_index, text); let end_index = char_ix_after(match_index, text);
// let mut match_style = default_style; let mut match_style = default_style;
// match_style.weight = Some(FontWeight::BOLD); match_style.font_weight = Some(FontWeight::BOLD);
// result.push((match_index..end_index, match_style)); result.push((match_index..end_index, match_style));
// } }
// if range.start == usize::MAX { if range.start == usize::MAX {
// break; break;
// } }
// // Add highlights for any fuzzy match characters within the // Add highlights for any fuzzy match characters within the
// // syntax highlight range. // syntax highlight range.
// let mut offset = range.start; let mut offset = range.start;
// while let Some(&match_index) = match_indices.peek() { while let Some(&match_index) = match_indices.peek() {
// if match_index >= range.end { if match_index >= range.end {
// break; break;
// } }
// match_indices.next(); match_indices.next();
// if match_index > offset { if match_index > offset {
// result.push((offset..match_index, syntax_highlight)); result.push((offset..match_index, syntax_highlight));
// } }
// let mut end_index = char_ix_after(match_index, text); let mut end_index = char_ix_after(match_index, text);
// while let Some(&next_match_index) = match_indices.peek() { while let Some(&next_match_index) = match_indices.peek() {
// if next_match_index == end_index && next_match_index < range.end { if next_match_index == end_index && next_match_index < range.end {
// end_index = char_ix_after(next_match_index, text); end_index = char_ix_after(next_match_index, text);
// match_indices.next(); match_indices.next();
// } else { } else {
// break; break;
// } }
// } }
// let mut match_style = syntax_highlight; let mut match_style = syntax_highlight;
// match_style.weight = Some(FontWeight::BOLD); match_style.font_weight = Some(FontWeight::BOLD);
// result.push((match_index..end_index, match_style)); result.push((match_index..end_index, match_style));
// offset = end_index; offset = end_index;
// } }
// if offset < range.end { if offset < range.end {
// result.push((offset..range.end, syntax_highlight)); result.push((offset..range.end, syntax_highlight));
// } }
// } }
// fn char_ix_after(ix: usize, text: &str) -> usize { fn char_ix_after(ix: usize, text: &str) -> usize {
// ix + text[ix..].chars().next().unwrap().len_utf8() ix + text[ix..].chars().next().unwrap().len_utf8()
// } }
// result result
// } }
// pub fn styled_runs_for_code_label<'a>( // pub fn styled_runs_for_code_label<'a>(
// label: &'a CodeLabel, // label: &'a CodeLabel,

File diff suppressed because it is too large Load diff

View file

@ -1683,21 +1683,24 @@ impl EditorElement {
ShowScrollbar::Never => false, ShowScrollbar::Never => false,
}; };
let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = Vec::new();
.into_iter() // todo!()
.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) // fold_ranges
}) // .into_iter()
.collect(); // .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 head_for_relative = newest_selection_head.unwrap_or_else(|| {
let newest = editor.selections.newest::<Point>(cx); let newest = editor.selections.newest::<Point>(cx);

View file

@ -30,6 +30,7 @@ use std::{
}; };
use text::Selection; use text::Selection;
use theme::{ActiveTheme, Theme}; use theme::{ActiveTheme, Theme};
use ui::{Label, LabelColor};
use util::{paths::PathExt, ResultExt, TryFutureExt}; use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle}; use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
use workspace::{ use workspace::{
@ -595,16 +596,19 @@ impl Item for Editor {
.flex_row() .flex_row()
.items_center() .items_center()
.gap_2() .gap_2()
.child(self.title(cx).to_string()) .child(Label::new(self.title(cx).to_string()))
.children(detail.and_then(|detail| { .children(detail.and_then(|detail| {
let path = path_for_buffer(&self.buffer, detail, false, cx)?; let path = path_for_buffer(&self.buffer, detail, false, cx)?;
let description = path.to_string_lossy(); let description = path.to_string_lossy();
Some( Some(
div() div().child(
.text_color(theme.colors().text_muted) Label::new(util::truncate_and_trailoff(
.text_xs() &description,
.child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)), MAX_TAB_TITLE_LEN,
))
.color(LabelColor::Muted),
),
) )
})), })),
) )

View file

@ -315,11 +315,14 @@ impl SelectionsCollection {
let layed_out_line = display_map.lay_out_line_for_row(row, &text_layout_details); 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; 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) { if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
let start = DisplayPoint::new(row, start_col); 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_col = layed_out_line.closest_index_for_x(positions.end) as u32;
let end = DisplayPoint::new(row, end_col); let end = DisplayPoint::new(row, end_col);
dbg!(start_col, end_col);
Some(Selection { Some(Selection {
id: post_inc(&mut self.next_selection_id), id: post_inc(&mut self.next_selection_id),

View file

@ -1,81 +1,74 @@
pub mod editor_lsp_test_context; pub mod editor_lsp_test_context;
pub mod editor_test_context; pub mod editor_test_context;
// todo!() use crate::{
// use crate::{ display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint},
// display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, DisplayPoint, Editor, EditorMode, MultiBuffer,
// DisplayPoint, Editor, EditorMode, MultiBuffer, };
// };
// use gpui::{Model, ViewContext}; use gpui::{Context, Model, Pixels, ViewContext};
// use project::Project; use project::Project;
// use util::test::{marked_text_offsets, marked_text_ranges}; use util::test::{marked_text_offsets, marked_text_ranges};
// #[cfg(test)] #[cfg(test)]
// #[ctor::ctor] #[ctor::ctor]
// fn init_logger() { fn init_logger() {
// if std::env::var("RUST_LOG").is_ok() { if std::env::var("RUST_LOG").is_ok() {
// env_logger::init(); env_logger::init();
// } }
// } }
// // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one. // Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
// pub fn marked_display_snapshot( pub fn marked_display_snapshot(
// text: &str, text: &str,
// cx: &mut gpui::AppContext, cx: &mut gpui::AppContext,
// ) -> (DisplaySnapshot, Vec<DisplayPoint>) { ) -> (DisplaySnapshot, Vec<DisplayPoint>) {
// let (unmarked_text, markers) = marked_text_offsets(text); let (unmarked_text, markers) = marked_text_offsets(text);
// let family_id = cx let font = cx.text_style().font();
// .font_cache() let font_size: Pixels = 14.into();
// .load_family(&["Helvetica"], &Default::default())
// .unwrap();
// let font_id = cx
// .font_cache()
// .select_font(family_id, &Default::default())
// .unwrap();
// let font_size = 14.0;
// let buffer = MultiBuffer::build_simple(&unmarked_text, cx); let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
// let display_map = let display_map = cx.build_model(|cx| DisplayMap::new(buffer, font, font_size, None, 1, 1, cx));
// 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 snapshot = display_map.update(cx, |map, cx| map.snapshot(cx)); let markers = markers
// let markers = markers .into_iter()
// .into_iter() .map(|offset| offset.to_display_point(&snapshot))
// .map(|offset| offset.to_display_point(&snapshot)) .collect();
// .collect();
// (snapshot, markers) (snapshot, markers)
// } }
// pub fn select_ranges(editor: &mut Editor, marked_text: &str, cx: &mut ViewContext<Editor>) { 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); let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
// assert_eq!(editor.text(cx), unmarked_text); assert_eq!(editor.text(cx), unmarked_text);
// editor.change_selections(None, cx, |s| s.select_ranges(text_ranges)); editor.change_selections(None, cx, |s| s.select_ranges(text_ranges));
// } }
// pub fn assert_text_with_selections( pub fn assert_text_with_selections(
// editor: &mut Editor, editor: &mut Editor,
// marked_text: &str, marked_text: &str,
// cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
// ) { ) {
// let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true); let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
// assert_eq!(editor.text(cx), unmarked_text); assert_eq!(editor.text(cx), unmarked_text);
// assert_eq!(editor.selections.ranges(cx), text_ranges); 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 // RA thinks this is dead code even though it is used in a whole lot of tests
// #[allow(dead_code)] #[allow(dead_code)]
// #[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
// pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor { pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
// Editor::new(EditorMode::Full, buffer, None, None, cx) // todo!()
// } Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
}
// pub(crate) fn build_editor_with_project( pub(crate) fn build_editor_with_project(
// project: Model<Project>, project: Model<Project>,
// buffer: Model<MultiBuffer>, buffer: Model<MultiBuffer>,
// cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
// ) -> Editor { ) -> Editor {
// Editor::new(EditorMode::Full, buffer, Some(project), None, cx) // todo!()
// } Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
}

View file

@ -1,297 +1,298 @@
// use std::{ use std::{
// borrow::Cow, borrow::Cow,
// ops::{Deref, DerefMut, Range}, ops::{Deref, DerefMut, Range},
// sync::Arc, sync::Arc,
// }; };
// use anyhow::Result; use anyhow::Result;
use serde_json::json;
// use crate::{Editor, ToPoint}; use crate::{Editor, ToPoint};
// use collections::HashSet; use collections::HashSet;
// use futures::Future; use futures::Future;
// use gpui::{json, View, ViewContext}; use gpui::{View, ViewContext, VisualTestContext};
// use indoc::indoc; use indoc::indoc;
// use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries}; use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
// use lsp::{notification, request}; use lsp::{notification, request};
// use multi_buffer::ToPointUtf16; use multi_buffer::ToPointUtf16;
// use project::Project; use project::Project;
// use smol::stream::StreamExt; use smol::stream::StreamExt;
// use workspace::{AppState, Workspace, WorkspaceHandle}; use workspace::{AppState, Workspace, WorkspaceHandle};
// use super::editor_test_context::EditorTestContext; use super::editor_test_context::{AssertionContextManager, EditorTestContext};
// pub struct EditorLspTestContext<'a> { pub struct EditorLspTestContext<'a> {
// pub cx: EditorTestContext<'a>, pub cx: EditorTestContext<'a>,
// pub lsp: lsp::FakeLanguageServer, pub lsp: lsp::FakeLanguageServer,
// pub workspace: View<Workspace>, pub workspace: View<Workspace>,
// pub buffer_lsp_url: lsp::Url, pub buffer_lsp_url: lsp::Url,
// } }
// impl<'a> EditorLspTestContext<'a> { impl<'a> EditorLspTestContext<'a> {
// pub async fn new( pub async fn new(
// mut language: Language, mut language: Language,
// capabilities: lsp::ServerCapabilities, capabilities: lsp::ServerCapabilities,
// cx: &'a mut gpui::TestAppContext, cx: &'a mut gpui::TestAppContext,
// ) -> EditorLspTestContext<'a> { ) -> EditorLspTestContext<'a> {
// use json::json; 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| { let file_name = format!(
// language::init(cx); "file.{}",
// crate::init(cx); language
// workspace::init(app_state.clone(), cx); .path_suffixes()
// Project::init_settings(cx); .first()
// }); .expect("language must have a path suffix for EditorLspTestContext")
);
// let file_name = format!( let mut fake_servers = language
// "file.{}", .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
// language capabilities,
// .path_suffixes() ..Default::default()
// .first() }))
// .expect("language must have a path suffix for EditorLspTestContext") .await;
// );
// let mut fake_servers = language let project = Project::test(app_state.fs.clone(), [], cx).await;
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
// capabilities,
// ..Default::default()
// }))
// .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 app_state
// .fs .fs
// .as_fake() .as_fake()
// .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }})) .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
// .await; .await;
// let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); 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 file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone()); let workspace = window.root_view(cx).unwrap();
// let item = workspace
// .update(cx, |workspace, cx| {
// workspace.open_path(file, None, true, cx)
// })
// .await
// .expect("Could not open test file");
// let editor = cx.update(|cx| { let mut cx = VisualTestContext::from_window(*window.deref(), cx);
// item.act_as::<Editor>(cx) project
// .expect("Opened test file wasn't an editor") .update(&mut cx, |project, cx| {
// }); project.find_or_create_local_worktree("/root", true, cx)
// editor.update(cx, |_, cx| cx.focus_self()); })
.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 { pub async fn new_rust(
// cx: EditorTestContext { capabilities: lsp::ServerCapabilities,
// cx, cx: &'a mut gpui::TestAppContext,
// window: window.into(), ) -> EditorLspTestContext<'a> {
// editor, let language = Language::new(
// }, LanguageConfig {
// lsp, name: "Rust".into(),
// workspace, path_suffixes: vec!["rs".to_string()],
// buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(), ..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( (_ "[" "]" @end) @indent
// capabilities: lsp::ServerCapabilities, (_ "<" ">" @end) @indent
// cx: &'a mut gpui::TestAppContext, (_ "{" "}" @end) @indent
// ) -> EditorLspTestContext<'a> { (_ "(" ")" @end) @indent"#})),
// let language = Language::new( brackets: Some(Cow::from(indoc! {r#"
// LanguageConfig { ("(" @open ")" @close)
// name: "Rust".into(), ("[" @open "]" @close)
// path_suffixes: vec!["rs".to_string()], ("{" @open "}" @close)
// ..Default::default() ("<" @open ">" @close)
// }, ("\"" @open "\"" @close)
// Some(tree_sitter_rust::language()), (closure_parameters "|" @open "|" @close)"#})),
// ) ..Default::default()
// .with_queries(LanguageQueries { })
// indents: Some(Cow::from(indoc! {r#" .expect("Could not parse queries");
// [
// ((where_clause) _ @end)
// (field_expression)
// (call_expression)
// (assignment_expression)
// (let_declaration)
// (let_chain)
// (await_expression)
// ] @indent
// (_ "[" "]" @end) @indent Self::new(language, capabilities, cx).await
// (_ "<" ">" @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 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( (_ "[" "]" @end) @indent
// capabilities: lsp::ServerCapabilities, (_ "<" ">" @end) @indent
// cx: &'a mut gpui::TestAppContext, (_ "{" "}" @end) @indent
// ) -> EditorLspTestContext<'a> { (_ "(" ")" @end) @indent
// let mut word_characters: HashSet<char> = Default::default(); "#})),
// word_characters.insert('$'); ..Default::default()
// word_characters.insert('#'); })
// let language = Language::new( .expect("Could not parse queries");
// 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 Self::new(language, capabilities, cx).await
// (_ "<" ">" @end) @indent }
// (_ "{" "}" @end) @indent
// (_ "(" ")" @end) @indent
// "#})),
// ..Default::default()
// })
// .expect("Could not parse queries");
// 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 to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
// pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range { let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
// let ranges = self.ranges(marked_text); let start_point = range.start.to_point(&snapshot.buffer_snapshot);
// self.to_lsp_range(ranges[0].clone()) let end_point = range.end.to_point(&snapshot.buffer_snapshot);
// }
// pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range { self.editor(|editor, cx| {
// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); let buffer = editor.buffer().read(cx);
// let start_point = range.start.to_point(&snapshot.buffer_snapshot); let start = point_to_lsp(
// let end_point = range.end.to_point(&snapshot.buffer_snapshot); 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| { lsp::Range { start, end }
// 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 } 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 { self.editor(|editor, cx| {
// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx)); let buffer = editor.buffer().read(cx);
// let point = offset.to_point(&snapshot.buffer_snapshot); point_to_lsp(
buffer
.point_to_buffer_offset(point, cx)
.unwrap()
.1
.to_point_utf16(&buffer.read(cx)),
)
})
}
// self.editor(|editor, cx| { pub fn update_workspace<F, T>(&mut self, update: F) -> T
// let buffer = editor.buffer().read(cx); where
// point_to_lsp( F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
// buffer {
// .point_to_buffer_offset(point, cx) self.workspace.update(&mut self.cx.cx, update)
// .unwrap() }
// .1
// .to_point_utf16(&buffer.read(cx)),
// )
// })
// }
// pub fn update_workspace<F, T>(&mut self, update: F) -> T pub fn handle_request<T, F, Fut>(
// where &self,
// F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T, mut handler: F,
// { ) -> futures::channel::mpsc::UnboundedReceiver<()>
// self.workspace.update(self.cx.cx, update) 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>( pub fn notify<T: notification::Notification>(&self, params: T::Params) {
// &self, self.lsp.notify::<T>(params);
// 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) { impl<'a> Deref for EditorLspTestContext<'a> {
// self.lsp.notify::<T>(params); type Target = EditorTestContext<'a>;
// }
// }
// impl<'a> Deref for EditorLspTestContext<'a> { fn deref(&self) -> &Self::Target {
// type Target = EditorTestContext<'a>; &self.cx
}
}
// fn deref(&self) -> &Self::Target { impl<'a> DerefMut for EditorLspTestContext<'a> {
// &self.cx 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
// }
// }

View file

@ -1,331 +1,400 @@
use crate::{ use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
}; };
use collections::BTreeMap;
use futures::Future; use futures::Future;
use gpui::{ use gpui::{
AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext, AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
VisualTestContext, WindowHandle,
}; };
use indoc::indoc; use indoc::indoc;
use itertools::Itertools;
use language::{Buffer, BufferSnapshot}; use language::{Buffer, BufferSnapshot};
use parking_lot::RwLock;
use project::{FakeFs, Project}; use project::{FakeFs, Project};
use std::{ use std::{
any::TypeId, any::TypeId,
ops::{Deref, DerefMut, Range}, ops::{Deref, DerefMut, Range},
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
}; };
use util::{ use util::{
assert_set_eq, assert_set_eq,
test::{generate_marked_text, marked_text_ranges}, test::{generate_marked_text, marked_text_ranges},
}; };
// use super::build_editor_with_project; use super::build_editor_with_project;
// pub struct EditorTestContext<'a> { pub struct EditorTestContext<'a> {
// pub cx: &'a mut gpui::TestAppContext, pub cx: gpui::VisualTestContext<'a>,
// pub window: AnyWindowHandle, pub window: AnyWindowHandle,
// pub editor: View<Editor>, pub editor: View<Editor>,
// } pub assertion_cx: AssertionContextManager,
}
// impl<'a> EditorTestContext<'a> { impl<'a> EditorTestContext<'a> {
// pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> { pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
// let fs = FakeFs::new(cx.background()); let fs = FakeFs::new(cx.executor());
// // fs.insert_file("/file", "".to_owned()).await; // fs.insert_file("/file", "".to_owned()).await;
// fs.insert_tree( fs.insert_tree(
// "/root", "/root",
// gpui::serde_json::json!({ gpui::serde_json::json!({
// "file": "", "file": "",
// }), }),
// ) )
// .await; .await;
// let project = Project::test(fs, ["/root".as_ref()], cx).await; let project = Project::test(fs, ["/root".as_ref()], cx).await;
// let buffer = project let buffer = project
// .update(cx, |project, cx| { .update(cx, |project, cx| {
// project.open_local_buffer("/root/file", cx) project.open_local_buffer("/root/file", cx)
// }) })
// .await .await
// .unwrap(); .unwrap();
// let window = cx.add_window(|cx| { let editor = cx.add_window(|cx| {
// cx.focus_self(); let editor =
// build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx) build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx);
// }); editor.focus(cx);
// let editor = window.root(cx); editor
// Self { });
// cx, let editor_view = editor.root_view(cx).unwrap();
// window: window.into(), Self {
// editor, cx: VisualTestContext::from_window(*editor.deref(), cx),
// } window: editor.into(),
// } editor: editor_view,
assertion_cx: AssertionContextManager::new(),
}
}
// pub fn condition( pub fn condition(
// &self, &self,
// predicate: impl FnMut(&Editor, &AppContext) -> bool, predicate: impl FnMut(&Editor, &AppContext) -> bool,
// ) -> impl Future<Output = ()> { ) -> impl Future<Output = ()> {
// self.editor.condition(self.cx, predicate) self.editor.condition::<crate::Event>(&self.cx, predicate)
// } }
// pub fn editor<F, T>(&self, read: F) -> T #[track_caller]
// where pub fn editor<F, T>(&mut self, read: F) -> T
// F: FnOnce(&Editor, &ViewContext<Editor>) -> T, where
// { F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
// self.editor.update(self.cx, read) {
// } self.editor
.update(&mut self.cx, |this, cx| read(&this, &cx))
}
// pub fn update_editor<F, T>(&mut self, update: F) -> T #[track_caller]
// where pub fn update_editor<F, T>(&mut self, update: F) -> T
// F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T, where
// { F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
// self.editor.update(self.cx, update) {
// } self.editor.update(&mut self.cx, update)
}
// pub fn multibuffer<F, T>(&self, read: F) -> T pub fn multibuffer<F, T>(&mut self, read: F) -> T
// where where
// F: FnOnce(&MultiBuffer, &AppContext) -> T, F: FnOnce(&MultiBuffer, &AppContext) -> T,
// { {
// self.editor(|editor, cx| read(editor.buffer().read(cx), cx)) self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
// } }
// pub fn update_multibuffer<F, T>(&mut self, update: F) -> T pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
// where where
// F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T, F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
// { {
// self.update_editor(|editor, cx| editor.buffer().update(cx, update)) self.update_editor(|editor, cx| editor.buffer().update(cx, update))
// } }
// pub fn buffer_text(&self) -> String { pub fn buffer_text(&mut self) -> String {
// self.multibuffer(|buffer, cx| buffer.snapshot(cx).text()) self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
// } }
// pub fn buffer<F, T>(&self, read: F) -> T pub fn buffer<F, T>(&mut self, read: F) -> T
// where where
// F: FnOnce(&Buffer, &AppContext) -> T, F: FnOnce(&Buffer, &AppContext) -> T,
// { {
// self.multibuffer(|multibuffer, cx| { self.multibuffer(|multibuffer, cx| {
// let buffer = multibuffer.as_singleton().unwrap().read(cx); let buffer = multibuffer.as_singleton().unwrap().read(cx);
// read(buffer, cx) read(buffer, cx)
// }) })
// } }
// pub fn update_buffer<F, T>(&mut self, update: F) -> T pub fn update_buffer<F, T>(&mut self, update: F) -> T
// where where
// F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T, F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
// { {
// self.update_multibuffer(|multibuffer, cx| { self.update_multibuffer(|multibuffer, cx| {
// let buffer = multibuffer.as_singleton().unwrap(); let buffer = multibuffer.as_singleton().unwrap();
// buffer.update(cx, update) buffer.update(cx, update)
// }) })
// } }
// pub fn buffer_snapshot(&self) -> BufferSnapshot { pub fn buffer_snapshot(&mut self) -> BufferSnapshot {
// self.buffer(|buffer, _| buffer.snapshot()) self.buffer(|buffer, _| buffer.snapshot())
// } }
// pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle { pub fn add_assertion_context(&self, context: String) -> ContextHandle {
// let keystroke_under_test_handle = self.assertion_cx.add_context(context)
// self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text)); }
// let keystroke = Keystroke::parse(keystroke_text).unwrap();
// 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>( self.cx.dispatch_keystroke(self.window, keystroke, false);
// &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!();
// }
// keystrokes_under_test_handle keystroke_under_test_handle
// } }
// pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> { pub fn simulate_keystrokes<const COUNT: usize>(
// let (unmarked_text, ranges) = marked_text_ranges(marked_text, false); &mut self,
// assert_eq!(self.buffer_text(), unmarked_text); keystroke_texts: [&str; COUNT],
// ranges ) -> 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 { keystrokes_under_test_handle
// 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)
// }
// // Returns anchors for the current buffer using `«` and `»` pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
// pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> { let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
// let ranges = self.ranges(marked_text); assert_eq!(self.buffer_text(), unmarked_text);
// let snapshot = self.buffer_snapshot(); ranges
// snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end) }
// }
// pub fn set_diff_base(&mut self, diff_base: Option<&str>) { pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
// let diff_base = diff_base.map(String::from); let ranges = self.ranges(marked_text);
// self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx)); 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 // Returns anchors for the current buffer using `«` and `»`
// /// embedded range markers that represent the ranges and directions of pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
// /// each selection. let ranges = self.ranges(marked_text);
// /// let snapshot = self.buffer_snapshot();
// /// Returns a context handle so that assertion failures can print what snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
// /// 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
// }
// /// Only change the editor's selections pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
// pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle { let diff_base = diff_base.map(String::from);
// let state_context = self.add_assertion_context(format!( self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
// "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
// }
// /// Make an assertion about the editor's text and the ranges and directions /// Change the editor's text and selections using a string containing
// /// of its selections using a string containing embedded range markers. /// embedded range markers that represent the ranges and directions of
// /// /// each selection.
// /// See the `util::test::marked_text_ranges` function for more information. ///
// #[track_caller] /// Returns a context handle so that assertion failures can print what
// pub fn assert_editor_state(&mut self, marked_text: &str) { /// editor state was needed to cause the failure.
// let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true); ///
// let buffer_text = self.buffer_text(); /// 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 { /// Only change the editor's selections
// 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}"); 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 { if buffer_text != unmarked_text {
// generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true) 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] self.assert_selections(expected_selections, marked_text.to_string())
// 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);
// }
// #[track_caller] pub fn editor_state(&mut self) -> String {
// pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) { generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
// 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] #[track_caller]
// pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) { pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
// let expected_marked_text = let expected_ranges = self.ranges(marked_text);
// generate_marked_text(&self.buffer_text(), &expected_selections, true); let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
// self.assert_selections(expected_selections, expected_marked_text) 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>> { #[track_caller]
// self.editor pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
// .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx)) let expected_ranges = self.ranges(marked_text);
// .into_iter() let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
// .map(|s| { let actual_ranges: Vec<Range<usize>> = snapshot
// if s.reversed { .text_highlight_ranges::<Tag>()
// s.end..s.start .map(|ranges| ranges.as_ref().clone().1)
// } else { .unwrap_or_default()
// s.start..s.end .into_iter()
// } .map(|range| range.to_offset(&snapshot.buffer_snapshot))
// }) .collect();
// .collect::<Vec<_>>() assert_set_eq!(actual_ranges, expected_ranges);
// } }
// #[track_caller] #[track_caller]
// fn assert_selections( pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
// &mut self, let expected_marked_text =
// expected_selections: Vec<Range<usize>>, generate_marked_text(&self.buffer_text(), &expected_selections, true);
// expected_marked_text: String, self.assert_selections(expected_selections, expected_marked_text)
// ) { }
// 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! {"
// {}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: {}Editor has unexpected 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 { Expected selections:
// self.cx {}
// }
// }
// impl<'a> DerefMut for EditorTestContext<'a> { Actual selections:
// fn deref_mut(&mut self) -> &mut Self::Target { {}
// &mut self.cx "},
// } 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);
}
}

View file

@ -176,8 +176,7 @@ macro_rules! actions {
() => {}; () => {};
( $name:ident ) => { ( $name:ident ) => {
#[gpui::register_action] #[gpui::action]
#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug, ::std::cmp::PartialEq, $crate::serde::Deserialize)]
pub struct $name; pub struct $name;
}; };

View file

@ -1012,6 +1012,29 @@ impl Context for AppContext {
let entity = self.entities.read(handle); let entity = self.entities.read(handle);
read(entity, self) 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. /// These effects are processed at the end of each application update cycle.

View file

@ -66,6 +66,19 @@ impl Context for AsyncAppContext {
let mut lock = app.borrow_mut(); let mut lock = app.borrow_mut();
lock.update_window(window, f) 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 { impl AsyncAppContext {
@ -250,6 +263,17 @@ impl Context for AsyncWindowContext {
{ {
self.app.read_model(handle, read) 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 { impl VisualContext for AsyncWindowContext {

View file

@ -1,6 +1,6 @@
use crate::{ use crate::{
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId, 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 anyhow::Result;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
@ -239,6 +239,17 @@ impl<'a, T> Context for ModelContext<'a, T> {
{ {
self.app.read_model(handle, read) 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> { impl<T> Borrow<AppContext> for ModelContext<'_, T> {

View file

@ -1,12 +1,12 @@
use crate::{ use crate::{
AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context, div, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor,
EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model,
Render, Result, Task, TestDispatcher, TestPlatform, ViewContext, VisualContext, WindowContext, ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, View, ViewContext,
WindowHandle, WindowOptions, VisualContext, WindowContext, WindowHandle, WindowOptions,
}; };
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt}; 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)] #[derive(Clone)]
pub struct TestAppContext { pub struct TestAppContext {
@ -58,6 +58,18 @@ impl Context for TestAppContext {
let app = self.app.borrow(); let app = self.app.borrow();
app.read_model(handle, read) 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 { impl TestAppContext {
@ -93,8 +105,8 @@ impl TestAppContext {
Ok(()) Ok(())
} }
pub fn executor(&self) -> &BackgroundExecutor { pub fn executor(&self) -> BackgroundExecutor {
&self.background_executor self.background_executor.clone()
} }
pub fn foreground_executor(&self) -> &ForegroundExecutor { pub fn foreground_executor(&self) -> &ForegroundExecutor {
@ -120,6 +132,26 @@ impl TestAppContext {
cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window)) 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> pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
where where
Fut: Future<Output = R> + 'static, Fut: Future<Output = R> + 'static,
@ -146,6 +178,11 @@ impl TestAppContext {
Some(read(lock.try_global()?, &lock)) 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>( pub fn update_global<G: 'static, R>(
&mut self, &mut self,
update: impl FnOnce(&mut G, &mut AppContext) -> R, update: impl FnOnce(&mut G, &mut AppContext) -> R,
@ -259,3 +296,191 @@ impl<T: Send> Model<T> {
.expect("model was dropped") .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()
}
}

View file

@ -167,7 +167,7 @@ impl TryFrom<&'_ str> for Rgba {
} }
} }
#[derive(Default, Copy, Clone, Debug, PartialEq)] #[derive(Default, Copy, Clone, Debug)]
#[repr(C)] #[repr(C)]
pub struct Hsla { pub struct Hsla {
pub h: f32, pub h: f32,
@ -176,10 +176,63 @@ pub struct Hsla {
pub a: f32, 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 { impl Hsla {
pub fn to_rgb(self) -> Rgba { pub fn to_rgb(self) -> Rgba {
self.into() 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 {} impl Eq for Hsla {}

View file

@ -81,6 +81,7 @@ impl<V: 'static> Element<V> for Text<V> {
let text = self.text.clone(); let text = self.text.clone();
let rem_size = cx.rem_size(); let rem_size = cx.rem_size();
let layout_id = cx.request_measured_layout(Default::default(), rem_size, { let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let element_state = element_state.clone(); let element_state = element_state.clone();
move |known_dimensions, _| { move |known_dimensions, _| {
@ -93,6 +94,10 @@ impl<V: 'static> Element<V> for Text<V> {
) )
.log_err() .log_err()
else { else {
element_state.lock().replace(TextElementState {
lines: Default::default(),
line_height,
});
return Size::default(); 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.lock();
let element_state = element_state let element_state = element_state
.as_ref() .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 line_height = element_state.line_height;
let mut line_origin = bounds.origin; let mut line_origin = bounds.origin;

View file

@ -105,6 +105,14 @@ pub trait Context {
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T> fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where where
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T; 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 { pub trait VisualContext: Context {

View file

@ -45,7 +45,7 @@ impl<V: 'static> ElementInputHandler<V> {
/// containing view. /// containing view.
pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self { pub fn new(element_bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) -> Self {
ElementInputHandler { ElementInputHandler {
view: cx.view(), view: cx.view().clone(),
element_bounds, element_bounds,
cx: cx.to_async(), cx: cx.to_async(),
} }

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle, build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
FocusId, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent, Pixels, FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent,
Style, StyleRefinement, ViewContext, WindowContext, Pixels, Style, StyleRefinement, ViewContext, WindowContext,
}; };
use collections::HashMap; use collections::HashMap;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -145,6 +145,15 @@ impl DispatchTree {
actions actions
} }
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
self.keymap
.lock()
.bindings_for_action(action.type_id())
.filter(|candidate| candidate.action.partial_eq(action))
.cloned()
.collect()
}
pub fn dispatch_key( pub fn dispatch_key(
&mut self, &mut self,
keystroke: &Keystroke, keystroke: &Keystroke,

View file

@ -3,9 +3,19 @@ use anyhow::Result;
use smallvec::SmallVec; use smallvec::SmallVec;
pub struct KeyBinding { pub struct KeyBinding {
action: Box<dyn Action>, pub(crate) action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>, pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
pub(super) context_predicate: Option<KeyBindingContextPredicate>, pub(crate) context_predicate: Option<KeyBindingContextPredicate>,
}
impl Clone for KeyBinding {
fn clone(&self) -> Self {
KeyBinding {
action: self.action.boxed_clone(),
keystrokes: self.keystrokes.clone(),
context_predicate: self.context_predicate.clone(),
}
}
} }
impl KeyBinding { impl KeyBinding {

View file

@ -8,7 +8,8 @@ use parking_lot::Mutex;
use crate::{ use crate::{
px, AtlasKey, AtlasTextureId, AtlasTile, Pixels, PlatformAtlas, PlatformDisplay, 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)] #[derive(Default)]
@ -23,6 +24,7 @@ pub struct TestWindow {
bounds: WindowBounds, bounds: WindowBounds,
current_scene: Mutex<Option<Scene>>, current_scene: Mutex<Option<Scene>>,
display: Rc<dyn PlatformDisplay>, display: Rc<dyn PlatformDisplay>,
input_handler: Option<Box<dyn PlatformInputHandler>>,
handlers: Mutex<Handlers>, handlers: Mutex<Handlers>,
sprite_atlas: Arc<dyn PlatformAtlas>, sprite_atlas: Arc<dyn PlatformAtlas>,
@ -33,7 +35,7 @@ impl TestWindow {
bounds: options.bounds, bounds: options.bounds,
current_scene: Default::default(), current_scene: Default::default(),
display, display,
input_handler: None,
sprite_atlas: Arc::new(TestAtlas::new()), sprite_atlas: Arc::new(TestAtlas::new()),
handlers: Default::default(), handlers: Default::default(),
} }
@ -77,8 +79,8 @@ impl PlatformWindow for TestWindow {
todo!() todo!()
} }
fn set_input_handler(&mut self, _input_handler: Box<dyn crate::PlatformInputHandler>) { fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
todo!() self.input_handler = Some(input_handler);
} }
fn prompt( fn prompt(

View file

@ -54,9 +54,9 @@ impl LineLayout {
pub fn closest_index_for_x(&self, x: Pixels) -> usize { pub fn closest_index_for_x(&self, x: Pixels) -> usize {
let mut prev_index = 0; let mut prev_index = 0;
let mut prev_x = px(0.); let mut prev_x = px(0.);
for run in self.runs.iter() { for run in self.runs.iter() {
for glyph in run.glyphs.iter() { for glyph in run.glyphs.iter() {
glyph.index;
if glyph.position.x >= x { if glyph.position.x >= x {
if glyph.position.x - x < x - prev_x { if glyph.position.x - x < x - prev_x {
return glyph.index; return glyph.index;
@ -68,7 +68,7 @@ impl LineLayout {
prev_x = glyph.position.x; prev_x = glyph.position.x;
} }
} }
prev_index prev_index + 1
} }
pub fn x_for_index(&self, index: usize) -> Pixels { pub fn x_for_index(&self, index: usize) -> Pixels {

View file

@ -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 use util::*;
// pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()> #[cfg(any(test, feature = "test-support"))]
// where pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
// F: Future<Output = T>, where
// { F: Future<Output = T>,
// let timer = async { {
// smol::Timer::after(timeout).await; let timer = async {
// Err(()) smol::Timer::after(timeout).await;
// }; Err(())
// let future = async move { Ok(f.await) }; };
// timer.race(future).await let future = async move { Ok(f.await) };
// } timer.race(future).await
}
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace); pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);

View file

@ -3,15 +3,15 @@ use crate::{
AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla, ImageData,
InputEvent, IsZero, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext, Modifiers, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext,
MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path,
PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Context as _, Result};
use collections::HashMap; use collections::HashMap;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use futures::{ use futures::{
@ -1377,6 +1377,13 @@ impl<'a> WindowContext<'a> {
Vec::new() Vec::new()
} }
} }
pub fn bindings_for_action(&self, action: &dyn Action) -> Vec<KeyBinding> {
self.window
.current_frame
.dispatch_tree
.bindings_for_action(action)
}
} }
impl Context for WindowContext<'_> { impl Context for WindowContext<'_> {
@ -1431,6 +1438,28 @@ impl Context for WindowContext<'_> {
let entity = self.entities.read(handle); let entity = self.entities.read(handle);
read(&*entity, &*self.app) 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<'_> { impl VisualContext for WindowContext<'_> {
@ -1737,9 +1766,12 @@ impl<'a, V: 'static> ViewContext<'a, V> {
} }
} }
// todo!("change this to return a reference"); pub fn entity_id(&self) -> EntityId {
pub fn view(&self) -> View<V> { self.view.entity_id()
self.view.clone() }
pub fn view(&self) -> &View<V> {
self.view
} }
pub fn model(&self) -> Model<V> { pub fn model(&self) -> Model<V> {
@ -1762,7 +1794,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
where where
V: 'static, V: 'static,
{ {
let view = self.view(); let view = self.view().clone();
self.window_cx.on_next_frame(move |cx| view.update(cx, f)); self.window_cx.on_next_frame(move |cx| view.update(cx, f));
} }
@ -2094,7 +2126,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
&mut self, &mut self,
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static, 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| { self.window_cx.on_mouse_event(move |event, phase, cx| {
handle.update(cx, |view, cx| { handle.update(cx, |view, cx| {
handler(view, event, phase, cx); handler(view, event, phase, cx);
@ -2106,7 +2138,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
&mut self, &mut self,
handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext<V>) + 'static, 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| { self.window_cx.on_key_event(move |event, phase, cx| {
handle.update(cx, |view, cx| { handle.update(cx, |view, cx| {
handler(view, event, phase, cx); handler(view, event, phase, cx);
@ -2119,7 +2151,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
action_type: TypeId, action_type: TypeId,
handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static, handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) + 'static,
) { ) {
let handle = self.view(); let handle = self.view().clone();
self.window_cx self.window_cx
.on_action(action_type, move |action, phase, cx| { .on_action(action_type, move |action, phase, cx| {
handle.update(cx, |view, cx| { handle.update(cx, |view, cx| {
@ -2194,6 +2226,17 @@ impl<V> Context for ViewContext<'_, V> {
{ {
self.window_cx.read_model(handle, read) 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> { impl<V: 'static> VisualContext for ViewContext<'_, V> {
@ -2266,7 +2309,7 @@ impl<V: 'static + Render> WindowHandle<V> {
} }
pub fn update<C, R>( pub fn update<C, R>(
self, &self,
cx: &mut C, cx: &mut C,
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R, update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
) -> Result<R> ) -> Result<R>
@ -2280,6 +2323,36 @@ impl<V: 'static + Render> WindowHandle<V> {
Ok(cx.update_view(&view, update)) 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> {} impl<V> Copy for WindowHandle<V> {}
@ -2345,6 +2418,18 @@ impl AnyWindowHandle {
{ {
cx.update_window(self, update) 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"))] #[cfg(any(test, feature = "test-support"))]

View file

@ -34,13 +34,21 @@ pub fn action(_attr: TokenStream, item: TokenStream) -> TokenStream {
let visibility = input.vis; let visibility = input.vis;
let output = match input.data { let output = match input.data {
syn::Data::Struct(ref struct_data) => { syn::Data::Struct(ref struct_data) => match &struct_data.fields {
let fields = &struct_data.fields; syn::Fields::Named(_) | syn::Fields::Unnamed(_) => {
quote! { let fields = &struct_data.fields;
#attributes quote! {
#visibility struct #name #fields #attributes
#visibility struct #name #fields
}
} }
} syn::Fields::Unit => {
quote! {
#attributes
#visibility struct #name;
}
}
},
syn::Data::Enum(ref enum_data) => { syn::Data::Enum(ref enum_data) => {
let variants = &enum_data.variants; let variants = &enum_data.variants;
quote! { quote! {

View file

@ -1858,7 +1858,7 @@ mod tests {
async fn test_first_line_pattern(cx: &mut TestAppContext) { async fn test_first_line_pattern(cx: &mut TestAppContext) {
let mut languages = LanguageRegistry::test(); let mut languages = LanguageRegistry::test();
languages.set_executor(cx.executor().clone()); languages.set_executor(cx.executor());
let languages = Arc::new(languages); let languages = Arc::new(languages);
languages.register( languages.register(
"/javascript", "/javascript",
@ -1895,7 +1895,7 @@ mod tests {
#[gpui::test(iterations = 10)] #[gpui::test(iterations = 10)]
async fn test_language_loading(cx: &mut TestAppContext) { async fn test_language_loading(cx: &mut TestAppContext) {
let mut languages = LanguageRegistry::test(); let mut languages = LanguageRegistry::test();
languages.set_executor(cx.executor().clone()); languages.set_executor(cx.executor());
let languages = Arc::new(languages); let languages = Arc::new(languages);
languages.register( languages.register(
"/JSON", "/JSON",

View file

@ -1,9 +1,13 @@
use gpui::actions; use gpui::actions;
// todo!(remove this) // If the zed binary doesn't use anything in this crate, it will be optimized away
// and the actions won't initialize. So we just provide an empty initialization function
// to be called from main.
//
// These may provide relevant context:
// https://github.com/rust-lang/rust/issues/47384 // https://github.com/rust-lang/rust/issues/47384
// https://github.com/mmastrac/rust-ctor/issues/280 // https://github.com/mmastrac/rust-ctor/issues/280
pub fn unused() {} pub fn init() {}
actions!( actions!(
Cancel, Cancel,

View file

@ -497,7 +497,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_prettier_lookup_finds_nothing(cx: &mut gpui::TestAppContext) { 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( fs.insert_tree(
"/root", "/root",
json!({ json!({
@ -573,7 +573,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_prettier_lookup_in_simple_npm_projects(cx: &mut gpui::TestAppContext) { 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( fs.insert_tree(
"/root", "/root",
json!({ json!({
@ -638,7 +638,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_prettier_lookup_for_not_installed(cx: &mut gpui::TestAppContext) { 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( fs.insert_tree(
"/root", "/root",
json!({ json!({
@ -731,7 +731,7 @@ mod tests {
#[gpui::test] #[gpui::test]
async fn test_prettier_lookup_in_npm_workspaces(cx: &mut gpui::TestAppContext) { 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( fs.insert_tree(
"/root", "/root",
json!({ json!({
@ -812,7 +812,7 @@ mod tests {
async fn test_prettier_lookup_in_npm_workspaces_for_not_installed( async fn test_prettier_lookup_in_npm_workspaces_for_not_installed(
cx: &mut gpui::TestAppContext, cx: &mut gpui::TestAppContext,
) { ) {
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/root", "/root",
json!({ json!({

View file

@ -863,7 +863,7 @@ impl Project {
cx: &mut gpui::TestAppContext, cx: &mut gpui::TestAppContext,
) -> Model<Project> { ) -> Model<Project> {
let mut languages = LanguageRegistry::test(); 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 http_client = util::http::FakeHttpClient::with_404_response();
let client = cx.update(|cx| client::Client::new(http_client.clone(), cx)); 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)); let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));

View file

@ -89,7 +89,7 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) {
async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/the-root", "/the-root",
json!({ json!({
@ -189,7 +189,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
})) }))
.await; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/the-root", "/the-root",
json!({ json!({
@ -547,7 +547,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
})) }))
.await; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/the-root", "/the-root",
json!({ 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) { async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ 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) { async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/root", "/root",
json!({ json!({
@ -914,7 +914,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
})) }))
.await; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -1046,7 +1046,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
})) }))
.await; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "" })).await; fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).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; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "x" })).await; fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).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; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "" })).await; fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).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; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" })) fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
.await; .await;
@ -1401,7 +1401,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
" "
.unindent(); .unindent();
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": text })).await; fs.insert_tree("/dir", json!({ "a.rs": text })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).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 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; fs.insert_tree("/dir", json!({ "a.rs": text })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).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) { async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
init_test(cx); 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" })) fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
.await; .await;
@ -1813,7 +1813,7 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
" "
.unindent(); .unindent();
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -1959,7 +1959,7 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAp
" "
.unindent(); .unindent();
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -2067,7 +2067,7 @@ async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
" "
.unindent(); .unindent();
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ 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 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( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -2299,7 +2299,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
})) }))
.await; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -2396,7 +2396,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
})) }))
.await; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ 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 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( fs.insert_tree(
"/dir", "/dir",
json!({ 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) { async fn test_save_file(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ 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) { async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ 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) { async fn test_save_as(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({})).await; fs.insert_tree("/dir", json!({})).await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).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) { async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ 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) { async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -2927,7 +2927,7 @@ async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) { async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -3074,7 +3074,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let initial_contents = "aaa\nbbbbb\nc\n"; let initial_contents = "aaa\nbbbbb\nc\n";
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ 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) { async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -3216,7 +3216,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) { async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/the-dir", "/the-dir",
json!({ json!({
@ -3479,7 +3479,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
})) }))
.await; .await;
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -3596,7 +3596,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
async fn test_search(cx: &mut gpui::TestAppContext) { async fn test_search(cx: &mut gpui::TestAppContext) {
init_test(cx); init_test(cx);
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -3655,7 +3655,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
let search_query = "file"; let search_query = "file";
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -3767,7 +3767,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
let search_query = "file"; let search_query = "file";
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({
@ -3878,7 +3878,7 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
let search_query = "file"; let search_query = "file";
let fs = FakeFs::new(cx.executor().clone()); let fs = FakeFs::new(cx.executor());
fs.insert_tree( fs.insert_tree(
"/dir", "/dir",
json!({ json!({

View file

@ -577,18 +577,18 @@ mod tests {
let client2 = Peer::new(0); let client2 = Peer::new(0);
let (client1_to_server_conn, server_to_client_1_conn, _kill) = 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) = 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) = 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) = 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) = 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) = 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_task1).detach();
executor.spawn(io_task2).detach(); executor.spawn(io_task2).detach();
@ -763,16 +763,16 @@ mod tests {
#[gpui::test(iterations = 50)] #[gpui::test(iterations = 50)]
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) { 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 server = Peer::new(0);
let client = Peer::new(0); let client = Peer::new(0);
let (client_to_server_conn, server_to_client_conn, _kill) = 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) = 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) = 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_task1).detach();
executor.spawn(io_task2).detach(); executor.spawn(io_task2).detach();

View file

@ -48,7 +48,7 @@ fn main() {
let args = Args::parse(); let args = Args::parse();
let story_selector = args.story.clone(); let story_selector = args.story.clone();
let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string()); let theme_name = args.theme.unwrap_or("One Dark".to_string());
let asset_source = Arc::new(Assets); let asset_source = Arc::new(Assets);
gpui::App::production(asset_source).run(move |cx| { gpui::App::production(asset_source).run(move |cx| {

View file

@ -1,261 +1,15 @@
use gpui::{hsla, Hsla, Rgba}; use gpui::{Hsla, Rgba};
use crate::colors::{StatusColors, SystemColors, ThemeColors};
use crate::scale::{ColorScaleSet, ColorScales}; use crate::scale::{ColorScaleSet, ColorScales};
use crate::syntax::SyntaxTheme; use crate::ColorScale;
use crate::{ColorScale, PlayerColor, PlayerColors}; use crate::{SystemColors, ThemeColors};
impl Default for PlayerColors { pub(crate) fn neutral() -> ColorScaleSet {
fn default() -> Self {
Self(vec![
PlayerColor {
cursor: blue().dark().step_9(),
background: blue().dark().step_5(),
selection: blue().dark().step_3(),
},
PlayerColor {
cursor: orange().dark().step_9(),
background: orange().dark().step_5(),
selection: orange().dark().step_3(),
},
PlayerColor {
cursor: pink().dark().step_9(),
background: pink().dark().step_5(),
selection: pink().dark().step_3(),
},
PlayerColor {
cursor: lime().dark().step_9(),
background: lime().dark().step_5(),
selection: lime().dark().step_3(),
},
PlayerColor {
cursor: purple().dark().step_9(),
background: purple().dark().step_5(),
selection: purple().dark().step_3(),
},
PlayerColor {
cursor: amber().dark().step_9(),
background: amber().dark().step_5(),
selection: amber().dark().step_3(),
},
PlayerColor {
cursor: jade().dark().step_9(),
background: jade().dark().step_5(),
selection: jade().dark().step_3(),
},
PlayerColor {
cursor: red().dark().step_9(),
background: red().dark().step_5(),
selection: red().dark().step_3(),
},
])
}
}
impl PlayerColors {
pub fn default_light() -> Self {
Self(vec![
PlayerColor {
cursor: blue().light().step_9(),
background: blue().light().step_4(),
selection: blue().light().step_3(),
},
PlayerColor {
cursor: orange().light().step_9(),
background: orange().light().step_4(),
selection: orange().light().step_3(),
},
PlayerColor {
cursor: pink().light().step_9(),
background: pink().light().step_4(),
selection: pink().light().step_3(),
},
PlayerColor {
cursor: lime().light().step_9(),
background: lime().light().step_4(),
selection: lime().light().step_3(),
},
PlayerColor {
cursor: purple().light().step_9(),
background: purple().light().step_4(),
selection: purple().light().step_3(),
},
PlayerColor {
cursor: amber().light().step_9(),
background: amber().light().step_4(),
selection: amber().light().step_3(),
},
PlayerColor {
cursor: jade().light().step_9(),
background: jade().light().step_4(),
selection: jade().light().step_3(),
},
PlayerColor {
cursor: red().light().step_9(),
background: red().light().step_4(),
selection: red().light().step_3(),
},
])
}
}
fn neutral() -> ColorScaleSet {
slate() slate()
} }
impl Default for SystemColors {
fn default() -> Self {
Self {
transparent: hsla(0.0, 0.0, 0.0, 0.0),
mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
}
}
}
impl Default for StatusColors {
fn default() -> Self {
Self {
conflict: red().dark().step_9(),
created: grass().dark().step_9(),
deleted: red().dark().step_9(),
error: red().dark().step_9(),
hidden: neutral().dark().step_9(),
ignored: neutral().dark().step_9(),
info: blue().dark().step_9(),
modified: yellow().dark().step_9(),
renamed: blue().dark().step_9(),
success: grass().dark().step_9(),
warning: yellow().dark().step_9(),
}
}
}
impl SyntaxTheme {
pub fn default_light() -> Self {
Self {
highlights: vec![
("attribute".into(), cyan().light().step_11().into()),
("boolean".into(), tomato().light().step_11().into()),
("comment".into(), neutral().light().step_11().into()),
("comment.doc".into(), iris().light().step_12().into()),
("constant".into(), red().light().step_9().into()),
("constructor".into(), red().light().step_9().into()),
("embedded".into(), red().light().step_9().into()),
("emphasis".into(), red().light().step_9().into()),
("emphasis.strong".into(), red().light().step_9().into()),
("enum".into(), red().light().step_9().into()),
("function".into(), red().light().step_9().into()),
("hint".into(), red().light().step_9().into()),
("keyword".into(), orange().light().step_11().into()),
("label".into(), red().light().step_9().into()),
("link_text".into(), red().light().step_9().into()),
("link_uri".into(), red().light().step_9().into()),
("number".into(), red().light().step_9().into()),
("operator".into(), red().light().step_9().into()),
("predictive".into(), red().light().step_9().into()),
("preproc".into(), red().light().step_9().into()),
("primary".into(), red().light().step_9().into()),
("property".into(), red().light().step_9().into()),
("punctuation".into(), neutral().light().step_11().into()),
(
"punctuation.bracket".into(),
neutral().light().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().light().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().light().step_11().into(),
),
("punctuation.special".into(), red().light().step_9().into()),
("string".into(), jade().light().step_11().into()),
("string.escape".into(), red().light().step_9().into()),
("string.regex".into(), tomato().light().step_11().into()),
("string.special".into(), red().light().step_9().into()),
(
"string.special.symbol".into(),
red().light().step_9().into(),
),
("tag".into(), red().light().step_9().into()),
("text.literal".into(), red().light().step_9().into()),
("title".into(), red().light().step_9().into()),
("type".into(), red().light().step_9().into()),
("variable".into(), red().light().step_9().into()),
("variable.special".into(), red().light().step_9().into()),
("variant".into(), red().light().step_9().into()),
],
inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
}
}
pub fn default_dark() -> Self {
Self {
highlights: vec![
("attribute".into(), tomato().dark().step_11().into()),
("boolean".into(), tomato().dark().step_11().into()),
("comment".into(), neutral().dark().step_11().into()),
("comment.doc".into(), iris().dark().step_12().into()),
("constant".into(), orange().dark().step_11().into()),
("constructor".into(), gold().dark().step_11().into()),
("embedded".into(), red().dark().step_11().into()),
("emphasis".into(), red().dark().step_11().into()),
("emphasis.strong".into(), red().dark().step_11().into()),
("enum".into(), yellow().dark().step_11().into()),
("function".into(), blue().dark().step_11().into()),
("hint".into(), indigo().dark().step_11().into()),
("keyword".into(), plum().dark().step_11().into()),
("label".into(), red().dark().step_11().into()),
("link_text".into(), red().dark().step_11().into()),
("link_uri".into(), red().dark().step_11().into()),
("number".into(), red().dark().step_11().into()),
("operator".into(), red().dark().step_11().into()),
("predictive".into(), red().dark().step_11().into()),
("preproc".into(), red().dark().step_11().into()),
("primary".into(), red().dark().step_11().into()),
("property".into(), red().dark().step_11().into()),
("punctuation".into(), neutral().dark().step_11().into()),
(
"punctuation.bracket".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().dark().step_11().into(),
),
("punctuation.special".into(), red().dark().step_11().into()),
("string".into(), lime().dark().step_11().into()),
("string.escape".into(), orange().dark().step_11().into()),
("string.regex".into(), tomato().dark().step_11().into()),
("string.special".into(), red().dark().step_11().into()),
(
"string.special.symbol".into(),
red().dark().step_11().into(),
),
("tag".into(), red().dark().step_11().into()),
("text.literal".into(), purple().dark().step_11().into()),
("title".into(), sky().dark().step_11().into()),
("type".into(), mint().dark().step_11().into()),
("variable".into(), red().dark().step_11().into()),
("variable.special".into(), red().dark().step_11().into()),
("variant".into(), red().dark().step_11().into()),
],
inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
}
}
}
impl ThemeColors { impl ThemeColors {
pub fn default_light() -> Self { pub fn light() -> Self {
let system = SystemColors::default(); let system = SystemColors::default();
Self { Self {
@ -327,7 +81,7 @@ impl ThemeColors {
} }
} }
pub fn default_dark() -> Self { pub fn dark() -> Self {
let system = SystemColors::default(); let system = SystemColors::default();
Self { Self {
@ -470,7 +224,7 @@ pub fn default_color_scales() -> ColorScales {
} }
} }
fn gray() -> ColorScaleSet { pub(crate) fn gray() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Gray", scale: "Gray",
light: [ light: [
@ -534,7 +288,7 @@ fn gray() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn mauve() -> ColorScaleSet { pub(crate) fn mauve() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Mauve", scale: "Mauve",
light: [ light: [
@ -598,7 +352,7 @@ fn mauve() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn slate() -> ColorScaleSet { pub(crate) fn slate() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Slate", scale: "Slate",
light: [ light: [
@ -662,7 +416,7 @@ fn slate() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn sage() -> ColorScaleSet { pub(crate) fn sage() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Sage", scale: "Sage",
light: [ light: [
@ -726,7 +480,7 @@ fn sage() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn olive() -> ColorScaleSet { pub(crate) fn olive() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Olive", scale: "Olive",
light: [ light: [
@ -790,7 +544,7 @@ fn olive() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn sand() -> ColorScaleSet { pub(crate) fn sand() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Sand", scale: "Sand",
light: [ light: [
@ -854,7 +608,7 @@ fn sand() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn gold() -> ColorScaleSet { pub(crate) fn gold() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Gold", scale: "Gold",
light: [ light: [
@ -918,7 +672,7 @@ fn gold() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn bronze() -> ColorScaleSet { pub(crate) fn bronze() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Bronze", scale: "Bronze",
light: [ light: [
@ -982,7 +736,7 @@ fn bronze() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn brown() -> ColorScaleSet { pub(crate) fn brown() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Brown", scale: "Brown",
light: [ light: [
@ -1046,7 +800,7 @@ fn brown() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn yellow() -> ColorScaleSet { pub(crate) fn yellow() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Yellow", scale: "Yellow",
light: [ light: [
@ -1110,7 +864,7 @@ fn yellow() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn amber() -> ColorScaleSet { pub(crate) fn amber() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Amber", scale: "Amber",
light: [ light: [
@ -1174,7 +928,7 @@ fn amber() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn orange() -> ColorScaleSet { pub(crate) fn orange() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Orange", scale: "Orange",
light: [ light: [
@ -1238,7 +992,7 @@ fn orange() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn tomato() -> ColorScaleSet { pub(crate) fn tomato() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Tomato", scale: "Tomato",
light: [ light: [
@ -1302,7 +1056,7 @@ fn tomato() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn red() -> ColorScaleSet { pub(crate) fn red() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Red", scale: "Red",
light: [ light: [
@ -1366,7 +1120,7 @@ fn red() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn ruby() -> ColorScaleSet { pub(crate) fn ruby() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Ruby", scale: "Ruby",
light: [ light: [
@ -1430,7 +1184,7 @@ fn ruby() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn crimson() -> ColorScaleSet { pub(crate) fn crimson() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Crimson", scale: "Crimson",
light: [ light: [
@ -1494,7 +1248,7 @@ fn crimson() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn pink() -> ColorScaleSet { pub(crate) fn pink() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Pink", scale: "Pink",
light: [ light: [
@ -1558,7 +1312,7 @@ fn pink() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn plum() -> ColorScaleSet { pub(crate) fn plum() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Plum", scale: "Plum",
light: [ light: [
@ -1622,7 +1376,7 @@ fn plum() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn purple() -> ColorScaleSet { pub(crate) fn purple() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Purple", scale: "Purple",
light: [ light: [
@ -1686,7 +1440,7 @@ fn purple() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn violet() -> ColorScaleSet { pub(crate) fn violet() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Violet", scale: "Violet",
light: [ light: [
@ -1750,7 +1504,7 @@ fn violet() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn iris() -> ColorScaleSet { pub(crate) fn iris() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Iris", scale: "Iris",
light: [ light: [
@ -1814,7 +1568,7 @@ fn iris() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn indigo() -> ColorScaleSet { pub(crate) fn indigo() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Indigo", scale: "Indigo",
light: [ light: [
@ -1878,7 +1632,7 @@ fn indigo() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn blue() -> ColorScaleSet { pub(crate) fn blue() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Blue", scale: "Blue",
light: [ light: [
@ -1942,7 +1696,7 @@ fn blue() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn cyan() -> ColorScaleSet { pub(crate) fn cyan() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Cyan", scale: "Cyan",
light: [ light: [
@ -2006,7 +1760,7 @@ fn cyan() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn teal() -> ColorScaleSet { pub(crate) fn teal() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Teal", scale: "Teal",
light: [ light: [
@ -2070,7 +1824,7 @@ fn teal() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn jade() -> ColorScaleSet { pub(crate) fn jade() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Jade", scale: "Jade",
light: [ light: [
@ -2134,7 +1888,7 @@ fn jade() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn green() -> ColorScaleSet { pub(crate) fn green() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Green", scale: "Green",
light: [ light: [
@ -2198,7 +1952,7 @@ fn green() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn grass() -> ColorScaleSet { pub(crate) fn grass() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Grass", scale: "Grass",
light: [ light: [
@ -2262,7 +2016,7 @@ fn grass() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn lime() -> ColorScaleSet { pub(crate) fn lime() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Lime", scale: "Lime",
light: [ light: [
@ -2326,7 +2080,7 @@ fn lime() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn mint() -> ColorScaleSet { pub(crate) fn mint() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Mint", scale: "Mint",
light: [ light: [
@ -2390,7 +2144,7 @@ fn mint() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn sky() -> ColorScaleSet { pub(crate) fn sky() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Sky", scale: "Sky",
light: [ light: [
@ -2454,7 +2208,7 @@ fn sky() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn black() -> ColorScaleSet { pub(crate) fn black() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "Black", scale: "Black",
light: [ light: [
@ -2518,7 +2272,7 @@ fn black() -> ColorScaleSet {
.unwrap() .unwrap()
} }
fn white() -> ColorScaleSet { pub(crate) fn white() -> ColorScaleSet {
StaticColorScaleSet { StaticColorScaleSet {
scale: "White", scale: "White",
light: [ light: [

View file

@ -1,58 +1,56 @@
use std::sync::Arc;
use crate::{ use crate::{
colors::{StatusColors, SystemColors, ThemeColors, ThemeStyles}, one_themes::{one_dark, one_family},
default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily, Theme, ThemeFamily,
}; };
fn zed_pro_daylight() -> Theme { // fn zed_pro_daylight() -> Theme {
Theme { // Theme {
id: "zed_pro_daylight".to_string(), // id: "zed_pro_daylight".to_string(),
name: "Zed Pro Daylight".into(), // name: "Zed Pro Daylight".into(),
appearance: Appearance::Light, // appearance: Appearance::Light,
styles: ThemeStyles { // styles: ThemeStyles {
system: SystemColors::default(), // system: SystemColors::default(),
colors: ThemeColors::default_light(), // colors: ThemeColors::light(),
status: StatusColors::default(), // status: StatusColors::light(),
player: PlayerColors::default_light(), // player: PlayerColors::light(),
syntax: Arc::new(SyntaxTheme::default_light()), // syntax: Arc::new(SyntaxTheme::light()),
}, // },
} // }
} // }
pub(crate) fn zed_pro_moonlight() -> Theme { // pub(crate) fn zed_pro_moonlight() -> Theme {
Theme { // Theme {
id: "zed_pro_moonlight".to_string(), // id: "zed_pro_moonlight".to_string(),
name: "Zed Pro Moonlight".into(), // name: "Zed Pro Moonlight".into(),
appearance: Appearance::Dark, // appearance: Appearance::Dark,
styles: ThemeStyles { // styles: ThemeStyles {
system: SystemColors::default(), // system: SystemColors::default(),
colors: ThemeColors::default_dark(), // colors: ThemeColors::dark(),
status: StatusColors::default(), // status: StatusColors::dark(),
player: PlayerColors::default(), // player: PlayerColors::dark(),
syntax: Arc::new(SyntaxTheme::default_dark()), // syntax: Arc::new(SyntaxTheme::dark()),
}, // },
} // }
} // }
pub fn zed_pro_family() -> ThemeFamily { // pub fn zed_pro_family() -> ThemeFamily {
ThemeFamily { // ThemeFamily {
id: "zed_pro".to_string(), // id: "zed_pro".to_string(),
name: "Zed Pro".into(), // name: "Zed Pro".into(),
author: "Zed Team".into(), // author: "Zed Team".into(),
themes: vec![zed_pro_daylight(), zed_pro_moonlight()], // themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
scales: default_color_scales(), // scales: default_color_scales(),
} // }
} // }
impl Default for ThemeFamily { impl Default for ThemeFamily {
fn default() -> Self { fn default() -> Self {
zed_pro_family() one_family()
} }
} }
impl Default for Theme { impl Default for Theme {
fn default() -> Self { fn default() -> Self {
zed_pro_daylight() one_dark()
} }
} }

View file

@ -0,0 +1,198 @@
use std::sync::Arc;
use gpui::{hsla, FontStyle, FontWeight, HighlightStyle};
use crate::{
default_color_scales, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
ThemeColors, ThemeFamily, ThemeStyles,
};
pub fn one_family() -> ThemeFamily {
ThemeFamily {
id: "one".to_string(),
name: "One".into(),
author: "".into(),
themes: vec![one_dark()],
scales: default_color_scales(),
}
}
pub(crate) fn one_dark() -> Theme {
let bg = hsla(215. / 360., 12. / 100., 15. / 100., 1.);
let editor = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let elevated_surface = hsla(220. / 360., 12. / 100., 18. / 100., 1.);
let blue = hsla(207.8 / 360., 81. / 100., 66. / 100., 1.0);
let gray = hsla(218.8 / 360., 10. / 100., 40. / 100., 1.0);
let green = hsla(95. / 360., 38. / 100., 62. / 100., 1.0);
let orange = hsla(29. / 360., 54. / 100., 61. / 100., 1.0);
let purple = hsla(286. / 360., 51. / 100., 64. / 100., 1.0);
let red = hsla(355. / 360., 65. / 100., 65. / 100., 1.0);
let teal = hsla(187. / 360., 47. / 100., 55. / 100., 1.0);
let yellow = hsla(39. / 360., 67. / 100., 69. / 100., 1.0);
Theme {
id: "one_dark".to_string(),
name: "One Dark".into(),
appearance: Appearance::Dark,
styles: ThemeStyles {
system: SystemColors::default(),
colors: ThemeColors {
border: hsla(225. / 360., 13. / 100., 12. / 100., 1.),
border_variant: hsla(228. / 360., 8. / 100., 25. / 100., 1.),
border_focused: hsla(223. / 360., 78. / 100., 65. / 100., 1.),
border_selected: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
border_transparent: SystemColors::default().transparent,
border_disabled: hsla(222.0 / 360., 11.6 / 100., 33.7 / 100., 1.0),
elevated_surface_background: elevated_surface,
surface_background: bg,
background: bg,
element_background: elevated_surface,
element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
drop_target_background: hsla(220.0 / 360., 8.3 / 100., 21.4 / 100., 1.0),
ghost_element_background: SystemColors::default().transparent,
ghost_element_hover: hsla(225.0 / 360., 11.8 / 100., 26.7 / 100., 1.0),
ghost_element_active: hsla(220.0 / 360., 11.8 / 100., 20.0 / 100., 1.0),
ghost_element_selected: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
ghost_element_disabled: hsla(224.0 / 360., 11.3 / 100., 26.1 / 100., 1.0),
text: hsla(222.9 / 360., 9.1 / 100., 84.9 / 100., 1.0),
text_muted: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
text_placeholder: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
text_disabled: hsla(220.0 / 360., 6.6 / 100., 44.5 / 100., 1.0),
text_accent: hsla(222.6 / 360., 77.5 / 100., 65.1 / 100., 1.0),
icon: hsla(222.9 / 360., 9.9 / 100., 86.1 / 100., 1.0),
icon_muted: hsla(220.0 / 360., 12.1 / 100., 66.1 / 100., 1.0),
icon_disabled: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
icon_placeholder: hsla(220.0 / 360., 6.4 / 100., 45.7 / 100., 1.0),
icon_accent: blue.into(),
status_bar_background: bg,
title_bar_background: bg,
toolbar_background: editor,
tab_bar_background: bg,
tab_inactive_background: bg,
tab_active_background: editor,
editor_background: editor,
editor_gutter_background: editor,
editor_subheader_background: bg,
editor_active_line_background: hsla(222.9 / 360., 13.5 / 100., 20.4 / 100., 1.0),
editor_highlighted_line_background: hsla(207.8 / 360., 81. / 100., 66. / 100., 0.1),
editor_line_number: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
editor_active_line_number: hsla(216.0 / 360., 5.9 / 100., 49.6 / 100., 1.0),
editor_invisible: hsla(222.0 / 360., 11.5 / 100., 34.1 / 100., 1.0),
editor_wrap_guide: gpui::black(),
editor_active_wrap_guide: gpui::red(),
editor_document_highlight_read_background: hsla(
207.8 / 360.,
81. / 100.,
66. / 100.,
0.2,
),
editor_document_highlight_write_background: gpui::red(),
terminal_background: bg,
// todo!("Use one colors for terminal")
terminal_ansi_black: crate::black().dark().step_12(),
terminal_ansi_red: crate::red().dark().step_11(),
terminal_ansi_green: crate::green().dark().step_11(),
terminal_ansi_yellow: crate::yellow().dark().step_11(),
terminal_ansi_blue: crate::blue().dark().step_11(),
terminal_ansi_magenta: crate::violet().dark().step_11(),
terminal_ansi_cyan: crate::cyan().dark().step_11(),
terminal_ansi_white: crate::neutral().dark().step_12(),
terminal_ansi_bright_black: crate::black().dark().step_11(),
terminal_ansi_bright_red: crate::red().dark().step_10(),
terminal_ansi_bright_green: crate::green().dark().step_10(),
terminal_ansi_bright_yellow: crate::yellow().dark().step_10(),
terminal_ansi_bright_blue: crate::blue().dark().step_10(),
terminal_ansi_bright_magenta: crate::violet().dark().step_10(),
terminal_ansi_bright_cyan: crate::cyan().dark().step_10(),
terminal_ansi_bright_white: crate::neutral().dark().step_11(),
},
status: StatusColors {
conflict: yellow,
created: green,
deleted: red,
error: red,
hidden: gray,
hint: blue,
ignored: gray,
info: blue,
modified: yellow,
predictive: gray,
renamed: blue,
success: green,
unreachable: gray,
warning: yellow,
},
player: PlayerColors::dark(),
syntax: Arc::new(SyntaxTheme {
highlights: vec![
("attribute".into(), purple.into()),
("boolean".into(), orange.into()),
("comment".into(), gray.into()),
("comment.doc".into(), gray.into()),
("constant".into(), yellow.into()),
("constructor".into(), blue.into()),
("embedded".into(), HighlightStyle::default()),
(
"emphasis".into(),
HighlightStyle {
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
),
(
"emphasis.strong".into(),
HighlightStyle {
font_weight: Some(FontWeight::BOLD),
..HighlightStyle::default()
},
),
("enum".into(), HighlightStyle::default()),
("function".into(), blue.into()),
("function.method".into(), blue.into()),
("function.definition".into(), blue.into()),
("hint".into(), blue.into()),
("keyword".into(), purple.into()),
("label".into(), HighlightStyle::default()),
("link_text".into(), blue.into()),
(
"link_uri".into(),
HighlightStyle {
color: Some(teal.into()),
font_style: Some(FontStyle::Italic),
..HighlightStyle::default()
},
),
("number".into(), orange.into()),
("operator".into(), HighlightStyle::default()),
("predictive".into(), HighlightStyle::default()),
("preproc".into(), HighlightStyle::default()),
("primary".into(), HighlightStyle::default()),
("property".into(), red.into()),
("punctuation".into(), HighlightStyle::default()),
("punctuation.bracket".into(), HighlightStyle::default()),
("punctuation.delimiter".into(), HighlightStyle::default()),
("punctuation.list_marker".into(), HighlightStyle::default()),
("punctuation.special".into(), HighlightStyle::default()),
("string".into(), green.into()),
("string.escape".into(), HighlightStyle::default()),
("string.regex".into(), red.into()),
("string.special".into(), HighlightStyle::default()),
("string.special.symbol".into(), HighlightStyle::default()),
("tag".into(), HighlightStyle::default()),
("text.literal".into(), HighlightStyle::default()),
("title".into(), HighlightStyle::default()),
("type".into(), teal.into()),
("variable".into(), HighlightStyle::default()),
("variable.special".into(), red.into()),
("variant".into(), HighlightStyle::default()),
],
inlay_style: HighlightStyle::default(),
suggestion_style: HighlightStyle::default(),
}),
},
}
}

View file

@ -6,8 +6,8 @@ use gpui::{HighlightStyle, SharedString};
use refineable::Refineable; use refineable::Refineable;
use crate::{ use crate::{
zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme, one_themes::one_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily, Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
}; };
pub struct ThemeRegistry { pub struct ThemeRegistry {
@ -38,17 +38,17 @@ impl ThemeRegistry {
fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = UserTheme>) { fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = UserTheme>) {
self.insert_themes(themes.into_iter().map(|user_theme| { self.insert_themes(themes.into_iter().map(|user_theme| {
let mut theme_colors = match user_theme.appearance { let mut theme_colors = match user_theme.appearance {
Appearance::Light => ThemeColors::default_light(), Appearance::Light => ThemeColors::light(),
Appearance::Dark => ThemeColors::default_dark(), Appearance::Dark => ThemeColors::dark(),
}; };
theme_colors.refine(&user_theme.styles.colors); theme_colors.refine(&user_theme.styles.colors);
let mut status_colors = StatusColors::default(); let mut status_colors = StatusColors::dark();
status_colors.refine(&user_theme.styles.status); status_colors.refine(&user_theme.styles.status);
let mut syntax_colors = match user_theme.appearance { let mut syntax_colors = match user_theme.appearance {
Appearance::Light => SyntaxTheme::default_light(), Appearance::Light => SyntaxTheme::light(),
Appearance::Dark => SyntaxTheme::default_dark(), Appearance::Dark => SyntaxTheme::dark(),
}; };
if let Some(user_syntax) = user_theme.styles.syntax { if let Some(user_syntax) = user_theme.styles.syntax {
syntax_colors.highlights = user_syntax syntax_colors.highlights = user_syntax
@ -76,7 +76,10 @@ impl ThemeRegistry {
system: SystemColors::default(), system: SystemColors::default(),
colors: theme_colors, colors: theme_colors,
status: status_colors, status: status_colors,
player: PlayerColors::default(), player: match user_theme.appearance {
Appearance::Light => PlayerColors::light(),
Appearance::Dark => PlayerColors::dark(),
},
syntax: Arc::new(syntax_colors), syntax: Arc::new(syntax_colors),
}, },
} }
@ -105,7 +108,7 @@ impl Default for ThemeRegistry {
themes: HashMap::default(), themes: HashMap::default(),
}; };
this.insert_theme_families([zed_pro_family()]); this.insert_theme_families([one_family()]);
#[cfg(not(feature = "importing-themes"))] #[cfg(not(feature = "importing-themes"))]
this.insert_user_theme_familes(crate::all_user_themes()); this.insert_user_theme_familes(crate::all_user_themes());

View file

@ -1,3 +1,4 @@
use crate::one_themes::one_dark;
use crate::{Theme, ThemeRegistry}; use crate::{Theme, ThemeRegistry};
use anyhow::Result; use anyhow::Result;
use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; use gpui::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
@ -129,7 +130,7 @@ impl settings::Settings for ThemeSettings {
buffer_line_height: defaults.buffer_line_height.unwrap(), buffer_line_height: defaults.buffer_line_height.unwrap(),
active_theme: themes active_theme: themes
.get(defaults.theme.as_ref().unwrap()) .get(defaults.theme.as_ref().unwrap())
.or(themes.get("Zed Pro Moonlight")) .or(themes.get(&one_dark().name))
.unwrap(), .unwrap(),
}; };

View file

@ -0,0 +1,11 @@
mod colors;
mod players;
mod status;
mod syntax;
mod system;
pub use colors::*;
pub use players::*;
pub use status::*;
pub use syntax::*;
pub use system::*;

View file

@ -1,31 +1,8 @@
use crate::{PlayerColors, SyntaxTheme};
use gpui::Hsla; use gpui::Hsla;
use refineable::Refineable; use refineable::Refineable;
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone)] use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};
pub struct SystemColors {
pub transparent: Hsla,
pub mac_os_traffic_light_red: Hsla,
pub mac_os_traffic_light_yellow: Hsla,
pub mac_os_traffic_light_green: Hsla,
}
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)]
pub struct StatusColors {
pub conflict: Hsla,
pub created: Hsla,
pub deleted: Hsla,
pub error: Hsla,
pub hidden: Hsla,
pub ignored: Hsla,
pub info: Hsla,
pub modified: Hsla,
pub renamed: Hsla,
pub success: Hsla,
pub warning: Hsla,
}
#[derive(Refineable, Clone, Debug)] #[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)] #[refineable(Debug, serde::Deserialize)]
@ -259,7 +236,7 @@ mod tests {
#[test] #[test]
fn override_a_single_theme_color() { fn override_a_single_theme_color() {
let mut colors = ThemeColors::default_light(); let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff); let magenta: Hsla = gpui::rgb(0xff00ff);
@ -277,7 +254,7 @@ mod tests {
#[test] #[test]
fn override_multiple_theme_colors() { fn override_multiple_theme_colors() {
let mut colors = ThemeColors::default_light(); let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff); let magenta: Hsla = gpui::rgb(0xff00ff);
let green: Hsla = gpui::rgb(0x00ff00); let green: Hsla = gpui::rgb(0x00ff00);

View file

@ -16,6 +16,107 @@ pub struct PlayerColor {
#[derive(Clone)] #[derive(Clone)]
pub struct PlayerColors(pub Vec<PlayerColor>); pub struct PlayerColors(pub Vec<PlayerColor>);
impl Default for PlayerColors {
/// Don't use this!
/// We have to have a default to be `[refineable::Refinable]`.
/// todo!("Find a way to not need this for Refinable")
fn default() -> Self {
Self::dark()
}
}
impl PlayerColors {
pub fn dark() -> Self {
Self(vec![
PlayerColor {
cursor: blue().dark().step_9(),
background: blue().dark().step_5(),
selection: blue().dark().step_3(),
},
PlayerColor {
cursor: orange().dark().step_9(),
background: orange().dark().step_5(),
selection: orange().dark().step_3(),
},
PlayerColor {
cursor: pink().dark().step_9(),
background: pink().dark().step_5(),
selection: pink().dark().step_3(),
},
PlayerColor {
cursor: lime().dark().step_9(),
background: lime().dark().step_5(),
selection: lime().dark().step_3(),
},
PlayerColor {
cursor: purple().dark().step_9(),
background: purple().dark().step_5(),
selection: purple().dark().step_3(),
},
PlayerColor {
cursor: amber().dark().step_9(),
background: amber().dark().step_5(),
selection: amber().dark().step_3(),
},
PlayerColor {
cursor: jade().dark().step_9(),
background: jade().dark().step_5(),
selection: jade().dark().step_3(),
},
PlayerColor {
cursor: red().dark().step_9(),
background: red().dark().step_5(),
selection: red().dark().step_3(),
},
])
}
pub fn light() -> Self {
Self(vec![
PlayerColor {
cursor: blue().light().step_9(),
background: blue().light().step_4(),
selection: blue().light().step_3(),
},
PlayerColor {
cursor: orange().light().step_9(),
background: orange().light().step_4(),
selection: orange().light().step_3(),
},
PlayerColor {
cursor: pink().light().step_9(),
background: pink().light().step_4(),
selection: pink().light().step_3(),
},
PlayerColor {
cursor: lime().light().step_9(),
background: lime().light().step_4(),
selection: lime().light().step_3(),
},
PlayerColor {
cursor: purple().light().step_9(),
background: purple().light().step_4(),
selection: purple().light().step_3(),
},
PlayerColor {
cursor: amber().light().step_9(),
background: amber().light().step_4(),
selection: amber().light().step_3(),
},
PlayerColor {
cursor: jade().light().step_9(),
background: jade().light().step_4(),
selection: jade().light().step_3(),
},
PlayerColor {
cursor: red().light().step_9(),
background: red().light().step_4(),
selection: red().light().step_3(),
},
])
}
}
impl PlayerColors { impl PlayerColors {
pub fn local(&self) -> PlayerColor { pub fn local(&self) -> PlayerColor {
// todo!("use a valid color"); // todo!("use a valid color");
@ -36,6 +137,8 @@ impl PlayerColors {
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
use crate::{amber, blue, jade, lime, orange, pink, purple, red};
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
mod stories { mod stories {
use super::*; use super::*;

View file

@ -0,0 +1,134 @@
use gpui::Hsla;
use refineable::Refineable;
use crate::{blue, grass, neutral, red, yellow};
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)]
pub struct StatusColors {
/// Indicates some kind of conflict, like a file changed on disk while it was open, or
/// merge conflicts in a Git repository.
pub conflict: Hsla,
/// Indicates something new, like a new file added to a Git repository.
pub created: Hsla,
/// Indicates that something no longer exists, like a deleted file.
pub deleted: Hsla,
/// Indicates a system error, a failed operation or a diagnostic error.
pub error: Hsla,
/// Represents a hidden status, such as a file being hidden in a file tree.
pub hidden: Hsla,
/// Indicates a hint or some kind of additional information.
pub hint: Hsla,
/// Indicates that something is deliberately ignored, such as a file or operation ignored by Git.
pub ignored: Hsla,
/// Represents informational status updates or messages.
pub info: Hsla,
/// Indicates a changed or altered status, like a file that has been edited.
pub modified: Hsla,
/// Indicates something that is predicted, like automatic code completion, or generated code.
pub predictive: Hsla,
/// Represents a renamed status, such as a file that has been renamed.
pub renamed: Hsla,
/// Indicates a successful operation or task completion.
pub success: Hsla,
/// Indicates some kind of unreachable status, like a block of code that can never be reached.
pub unreachable: Hsla,
/// Represents a warning status, like an operation that is about to fail.
pub warning: Hsla,
}
impl Default for StatusColors {
/// Don't use this!
/// We have to have a default to be `[refineable::Refinable]`.
/// todo!("Find a way to not need this for Refinable")
fn default() -> Self {
Self::dark()
}
}
pub struct DiagnosticColors {
pub error: Hsla,
pub warning: Hsla,
pub info: Hsla,
}
pub struct GitStatusColors {
pub created: Hsla,
pub deleted: Hsla,
pub modified: Hsla,
pub renamed: Hsla,
pub conflict: Hsla,
pub ignored: Hsla,
}
impl StatusColors {
pub fn dark() -> Self {
Self {
conflict: red().dark().step_9(),
created: grass().dark().step_9(),
deleted: red().dark().step_9(),
error: red().dark().step_9(),
hidden: neutral().dark().step_9(),
hint: blue().dark().step_9(),
ignored: neutral().dark().step_9(),
info: blue().dark().step_9(),
modified: yellow().dark().step_9(),
predictive: neutral().dark_alpha().step_9(),
renamed: blue().dark().step_9(),
success: grass().dark().step_9(),
unreachable: neutral().dark().step_10(),
warning: yellow().dark().step_9(),
}
}
pub fn light() -> Self {
Self {
conflict: red().light().step_9(),
created: grass().light().step_9(),
deleted: red().light().step_9(),
error: red().light().step_9(),
hidden: neutral().light().step_9(),
hint: blue().light().step_9(),
ignored: neutral().light().step_9(),
info: blue().light().step_9(),
modified: yellow().light().step_9(),
predictive: neutral().light_alpha().step_9(),
renamed: blue().light().step_9(),
success: grass().light().step_9(),
unreachable: neutral().light().step_10(),
warning: yellow().light().step_9(),
}
}
pub fn diagnostic(&self) -> DiagnosticColors {
DiagnosticColors {
error: self.error,
warning: self.warning,
info: self.info,
}
}
pub fn git(&self) -> GitStatusColors {
GitStatusColors {
created: self.created,
deleted: self.deleted,
modified: self.modified,
renamed: self.renamed,
conflict: self.conflict,
ignored: self.ignored,
}
}
}

View file

@ -0,0 +1,170 @@
use gpui::{HighlightStyle, Hsla};
use crate::{
blue, cyan, gold, indigo, iris, jade, lime, mint, neutral, orange, plum, purple, red, sky,
tomato, yellow,
};
#[derive(Clone, Default)]
pub struct SyntaxTheme {
pub highlights: Vec<(String, HighlightStyle)>,
// todo!("Remove this in favor of StatusColor.hint")
// If this should be overridable we should move it to ThemeColors
pub inlay_style: HighlightStyle,
// todo!("Remove this in favor of StatusColor.prediction")
// If this should be overridable we should move it to ThemeColors
pub suggestion_style: HighlightStyle,
}
impl SyntaxTheme {
pub fn light() -> Self {
Self {
highlights: vec![
("attribute".into(), cyan().light().step_11().into()),
("boolean".into(), tomato().light().step_11().into()),
("comment".into(), neutral().light().step_11().into()),
("comment.doc".into(), iris().light().step_12().into()),
("constant".into(), red().light().step_9().into()),
("constructor".into(), red().light().step_9().into()),
("embedded".into(), red().light().step_9().into()),
("emphasis".into(), red().light().step_9().into()),
("emphasis.strong".into(), red().light().step_9().into()),
("enum".into(), red().light().step_9().into()),
("function".into(), red().light().step_9().into()),
("hint".into(), red().light().step_9().into()),
("keyword".into(), orange().light().step_11().into()),
("label".into(), red().light().step_9().into()),
("link_text".into(), red().light().step_9().into()),
("link_uri".into(), red().light().step_9().into()),
("number".into(), red().light().step_9().into()),
("operator".into(), red().light().step_9().into()),
("predictive".into(), red().light().step_9().into()),
("preproc".into(), red().light().step_9().into()),
("primary".into(), red().light().step_9().into()),
("property".into(), red().light().step_9().into()),
("punctuation".into(), neutral().light().step_11().into()),
(
"punctuation.bracket".into(),
neutral().light().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().light().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().light().step_11().into(),
),
("punctuation.special".into(), red().light().step_9().into()),
("string".into(), jade().light().step_11().into()),
("string.escape".into(), red().light().step_9().into()),
("string.regex".into(), tomato().light().step_11().into()),
("string.special".into(), red().light().step_9().into()),
(
"string.special.symbol".into(),
red().light().step_9().into(),
),
("tag".into(), red().light().step_9().into()),
("text.literal".into(), red().light().step_9().into()),
("title".into(), red().light().step_9().into()),
("type".into(), red().light().step_9().into()),
("variable".into(), red().light().step_9().into()),
("variable.special".into(), red().light().step_9().into()),
("variant".into(), red().light().step_9().into()),
],
inlay_style: tomato().light().step_1().into(), // todo!("nate: use a proper style")
suggestion_style: orange().light().step_1().into(), // todo!("nate: use proper style")
}
}
pub fn dark() -> Self {
Self {
highlights: vec![
("attribute".into(), tomato().dark().step_11().into()),
("boolean".into(), tomato().dark().step_11().into()),
("comment".into(), neutral().dark().step_11().into()),
("comment.doc".into(), iris().dark().step_12().into()),
("constant".into(), orange().dark().step_11().into()),
("constructor".into(), gold().dark().step_11().into()),
("embedded".into(), red().dark().step_11().into()),
("emphasis".into(), red().dark().step_11().into()),
("emphasis.strong".into(), red().dark().step_11().into()),
("enum".into(), yellow().dark().step_11().into()),
("function".into(), blue().dark().step_11().into()),
("hint".into(), indigo().dark().step_11().into()),
("keyword".into(), plum().dark().step_11().into()),
("label".into(), red().dark().step_11().into()),
("link_text".into(), red().dark().step_11().into()),
("link_uri".into(), red().dark().step_11().into()),
("number".into(), red().dark().step_11().into()),
("operator".into(), red().dark().step_11().into()),
("predictive".into(), red().dark().step_11().into()),
("preproc".into(), red().dark().step_11().into()),
("primary".into(), red().dark().step_11().into()),
("property".into(), red().dark().step_11().into()),
("punctuation".into(), neutral().dark().step_11().into()),
(
"punctuation.bracket".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.delimiter".into(),
neutral().dark().step_11().into(),
),
(
"punctuation.list_marker".into(),
blue().dark().step_11().into(),
),
("punctuation.special".into(), red().dark().step_11().into()),
("string".into(), lime().dark().step_11().into()),
("string.escape".into(), orange().dark().step_11().into()),
("string.regex".into(), tomato().dark().step_11().into()),
("string.special".into(), red().dark().step_11().into()),
(
"string.special.symbol".into(),
red().dark().step_11().into(),
),
("tag".into(), red().dark().step_11().into()),
("text.literal".into(), purple().dark().step_11().into()),
("title".into(), sky().dark().step_11().into()),
("type".into(), mint().dark().step_11().into()),
("variable".into(), red().dark().step_11().into()),
("variable.special".into(), red().dark().step_11().into()),
("variant".into(), red().dark().step_11().into()),
],
inlay_style: neutral().dark().step_11().into(), // todo!("nate: use a proper style")
suggestion_style: orange().dark().step_11().into(), // todo!("nate: use a proper style")
}
}
// TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
SyntaxTheme {
highlights: colors
.into_iter()
.map(|(key, color)| {
(
key.to_owned(),
HighlightStyle {
color: Some(color),
..Default::default()
},
)
})
.collect(),
inlay_style: HighlightStyle::default(),
suggestion_style: HighlightStyle::default(),
}
}
pub fn get(&self, name: &str) -> HighlightStyle {
self.highlights
.iter()
.find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
.unwrap_or_default()
}
pub fn color(&self, name: &str) -> Hsla {
self.get(name).color.unwrap_or_default()
}
}

View file

@ -0,0 +1,20 @@
use gpui::{hsla, Hsla};
#[derive(Clone)]
pub struct SystemColors {
pub transparent: Hsla,
pub mac_os_traffic_light_red: Hsla,
pub mac_os_traffic_light_yellow: Hsla,
pub mac_os_traffic_light_green: Hsla,
}
impl Default for SystemColors {
fn default() -> Self {
Self {
transparent: hsla(0.0, 0.0, 0.0, 0.0),
mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0),
mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0),
mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0),
}
}
}

View file

@ -1,41 +0,0 @@
use gpui::{HighlightStyle, Hsla};
#[derive(Clone, Default)]
pub struct SyntaxTheme {
pub highlights: Vec<(String, HighlightStyle)>,
pub inlay_style: HighlightStyle,
pub suggestion_style: HighlightStyle,
}
impl SyntaxTheme {
// TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
SyntaxTheme {
highlights: colors
.into_iter()
.map(|(key, color)| {
(
key.to_owned(),
HighlightStyle {
color: Some(color),
..Default::default()
},
)
})
.collect(),
inlay_style: HighlightStyle::default(),
suggestion_style: HighlightStyle::default(),
}
}
pub fn get(&self, name: &str) -> HighlightStyle {
self.highlights
.iter()
.find_map(|entry| if entry.0 == name { Some(entry.1) } else { None })
.unwrap_or_default()
}
pub fn color(&self, name: &str) -> Hsla {
self.get(name).color.unwrap_or_default()
}
}

View file

@ -1,11 +1,10 @@
mod colors;
mod default_colors; mod default_colors;
mod default_theme; mod default_theme;
mod players; mod one_themes;
mod registry; mod registry;
mod scale; mod scale;
mod settings; mod settings;
mod syntax; mod styles;
#[cfg(not(feature = "importing-themes"))] #[cfg(not(feature = "importing-themes"))]
mod themes; mod themes;
mod user_theme; mod user_theme;
@ -13,14 +12,12 @@ mod user_theme;
use std::sync::Arc; use std::sync::Arc;
use ::settings::Settings; use ::settings::Settings;
pub use colors::*;
pub use default_colors::*; pub use default_colors::*;
pub use default_theme::*; pub use default_theme::*;
pub use players::*;
pub use registry::*; pub use registry::*;
pub use scale::*; pub use scale::*;
pub use settings::*; pub use settings::*;
pub use syntax::*; pub use styles::*;
#[cfg(not(feature = "importing-themes"))] #[cfg(not(feature = "importing-themes"))]
pub use themes::*; pub use themes::*;
pub use user_theme::*; pub use user_theme::*;

View file

@ -0,0 +1,35 @@
import colorsys
import sys
def hex_to_rgb(hex):
hex = hex.lstrip('#')
if len(hex) == 8: # 8 digit hex color
r, g, b, a = (int(hex[i:i+2], 16) for i in (0, 2, 4, 6))
return r, g, b, a / 255.0
else: # 6 digit hex color
return tuple(int(hex[i:i+2], 16) for i in (0, 2, 4)) + (1.0,)
def rgb_to_hsla(rgb):
h, l, s = colorsys.rgb_to_hls(rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0)
a = rgb[3] # alpha value
return (round(h * 360, 1), round(s * 100, 1), round(l * 100, 1), round(a, 3))
def hex_to_hsla(hex):
return rgb_to_hsla(hex_to_rgb(hex))
if len(sys.argv) != 2:
print("Usage: python util/hex_to_hsla.py <6 or 8 digit hex color or comma-separated list of colors>")
else:
input_arg = sys.argv[1]
if ',' in input_arg: # comma-separated list of colors
hex_colors = input_arg.split(',')
hslas = [] # output array
for hex_color in hex_colors:
hex_color = hex_color.strip("'\" ")
h, s, l, a = hex_to_hsla(hex_color)
hslas.append(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")
print(hslas)
else: # single color
hex_color = input_arg.strip("'\"")
h, s, l, a = hex_to_hsla(hex_color)
print(f"hsla({h} / 360., {s} / 100., {l} / 100., {a})")

View file

@ -1,50 +1,42 @@
use std::collections::HashSet; use gpui::Action;
use strum::EnumIter;
use strum::{EnumIter, IntoEnumIterator};
use crate::prelude::*; use crate::prelude::*;
#[derive(Component)] #[derive(Component)]
pub struct Keybinding { pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys. /// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord. /// More then one keybinding produces a chord.
/// ///
/// This should always contain at least one element. /// This should always contain at least one element.
keybinding: Vec<(String, ModifierKeys)>, key_binding: gpui::KeyBinding,
} }
impl Keybinding { impl KeyBinding {
pub fn new(key: String, modifiers: ModifierKeys) -> Self { pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
Self { // todo! this last is arbitrary, we want to prefer users key bindings over defaults,
keybinding: vec![(key, modifiers)], // and vim over normal (in vim mode), etc.
} let key_binding = cx.bindings_for_action(action).last().cloned()?;
Some(Self::new(key_binding))
} }
pub fn new_chord( pub fn new(key_binding: gpui::KeyBinding) -> Self {
first_note: (String, ModifierKeys), Self { key_binding }
second_note: (String, ModifierKeys),
) -> Self {
Self {
keybinding: vec![first_note, second_note],
}
} }
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> { fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div() div()
.flex() .flex()
.gap_2() .gap_2()
.children(self.keybinding.iter().map(|(key, modifiers)| { .children(self.key_binding.keystrokes().iter().map(|keystroke| {
div() div()
.flex() .flex()
.gap_1() .gap_1()
.children(ModifierKey::iter().filter_map(|modifier| { .when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
if modifiers.0.contains(&modifier) { .when(keystroke.modifiers.alt, |el| el.child(Key::new("")))
Some(Key::new(modifier.glyph().to_string())) .when(keystroke.modifiers.command, |el| el.child(Key::new("")))
} else { .when(keystroke.modifiers.shift, |el| el.child(Key::new("")))
None .child(Key::new(keystroke.key.clone()))
}
}))
.child(Key::new(key.clone()))
})) }))
} }
} }
@ -81,76 +73,6 @@ pub enum ModifierKey {
Shift, Shift,
} }
impl ModifierKey {
/// Returns the glyph for the [`ModifierKey`].
pub fn glyph(&self) -> char {
match self {
Self::Control => '^',
Self::Alt => '⌥',
Self::Command => '⌘',
Self::Shift => '⇧',
}
}
}
#[derive(Clone)]
pub struct ModifierKeys(HashSet<ModifierKey>);
impl ModifierKeys {
pub fn new() -> Self {
Self(HashSet::new())
}
pub fn all() -> Self {
Self(HashSet::from_iter(ModifierKey::iter()))
}
pub fn add(mut self, modifier: ModifierKey) -> Self {
self.0.insert(modifier);
self
}
pub fn control(mut self, control: bool) -> Self {
if control {
self.0.insert(ModifierKey::Control);
} else {
self.0.remove(&ModifierKey::Control);
}
self
}
pub fn alt(mut self, alt: bool) -> Self {
if alt {
self.0.insert(ModifierKey::Alt);
} else {
self.0.remove(&ModifierKey::Alt);
}
self
}
pub fn command(mut self, command: bool) -> Self {
if command {
self.0.insert(ModifierKey::Command);
} else {
self.0.remove(&ModifierKey::Command);
}
self
}
pub fn shift(mut self, shift: bool) -> Self {
if shift {
self.0.insert(ModifierKey::Shift);
} else {
self.0.remove(&ModifierKey::Shift);
}
self
}
}
#[cfg(feature = "stories")] #[cfg(feature = "stories")]
pub use stories::*; pub use stories::*;
@ -158,29 +80,38 @@ pub use stories::*;
mod stories { mod stories {
use super::*; use super::*;
use crate::Story; use crate::Story;
use gpui::{Div, Render}; use gpui::{action, Div, Render};
use itertools::Itertools; use itertools::Itertools;
pub struct KeybindingStory; pub struct KeybindingStory;
#[action]
struct NoAction {}
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render for KeybindingStory { impl Render for KeybindingStory {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let all_modifier_permutations = ModifierKey::iter().permutations(2); let all_modifier_permutations =
["ctrl", "alt", "cmd", "shift"].into_iter().permutations(2);
Story::container(cx) Story::container(cx)
.child(Story::title_for::<_, Keybinding>(cx)) .child(Story::title_for::<_, KeyBinding>(cx))
.child(Story::label(cx, "Single Key")) .child(Story::label(cx, "Single Key"))
.child(Keybinding::new("Z".to_string(), ModifierKeys::new())) .child(KeyBinding::new(binding("Z")))
.child(Story::label(cx, "Single Key with Modifier")) .child(Story::label(cx, "Single Key with Modifier"))
.child( .child(
div() div()
.flex() .flex()
.gap_3() .gap_3()
.children(ModifierKey::iter().map(|modifier| { .child(KeyBinding::new(binding("ctrl-c")))
Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier)) .child(KeyBinding::new(binding("alt-c")))
})), .child(KeyBinding::new(binding("cmd-c")))
.child(KeyBinding::new(binding("shift-c"))),
) )
.child(Story::label(cx, "Single Key with Modifier (Permuted)")) .child(Story::label(cx, "Single Key with Modifier (Permuted)"))
.child( .child(
@ -194,29 +125,17 @@ mod stories {
.gap_4() .gap_4()
.py_3() .py_3()
.children(chunk.map(|permutation| { .children(chunk.map(|permutation| {
let mut modifiers = ModifierKeys::new(); KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
for modifier in permutation {
modifiers = modifiers.add(modifier);
}
Keybinding::new("X".to_string(), modifiers)
})) }))
}), }),
), ),
) )
.child(Story::label(cx, "Single Key with All Modifiers")) .child(Story::label(cx, "Single Key with All Modifiers"))
.child(Keybinding::new("Z".to_string(), ModifierKeys::all())) .child(KeyBinding::new(binding("ctrl-alt-cmd-shift-z")))
.child(Story::label(cx, "Chord")) .child(Story::label(cx, "Chord"))
.child(Keybinding::new_chord( .child(KeyBinding::new(binding("a z")))
("A".to_string(), ModifierKeys::new()),
("Z".to_string(), ModifierKeys::new()),
))
.child(Story::label(cx, "Chord with Modifier")) .child(Story::label(cx, "Chord with Modifier"))
.child(Keybinding::new_chord( .child(KeyBinding::new(binding("ctrl-a shift-z")))
("A".to_string(), ModifierKeys::new().control(true)),
("Z".to_string(), ModifierKeys::new().shift(true)),
))
} }
} }
} }

View file

@ -1,5 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use crate::{h_stack, v_stack, Keybinding, Label, LabelColor}; use crate::{h_stack, v_stack, KeyBinding, Label, LabelColor};
#[derive(Component)] #[derive(Component)]
pub struct Palette { pub struct Palette {
@ -108,7 +108,7 @@ impl Palette {
pub struct PaletteItem { pub struct PaletteItem {
pub label: SharedString, pub label: SharedString,
pub sublabel: Option<SharedString>, pub sublabel: Option<SharedString>,
pub keybinding: Option<Keybinding>, pub keybinding: Option<KeyBinding>,
} }
impl PaletteItem { impl PaletteItem {
@ -132,7 +132,7 @@ impl PaletteItem {
pub fn keybinding<K>(mut self, keybinding: K) -> Self pub fn keybinding<K>(mut self, keybinding: K) -> Self
where where
K: Into<Option<Keybinding>>, K: Into<Option<KeyBinding>>,
{ {
self.keybinding = keybinding.into(); self.keybinding = keybinding.into();
self self
@ -161,7 +161,7 @@ pub use stories::*;
mod stories { mod stories {
use gpui::{Div, Render}; use gpui::{Div, Render};
use crate::{ModifierKeys, Story}; use crate::{binding, Story};
use super::*; use super::*;
@ -181,46 +181,24 @@ mod stories {
Palette::new("palette-2") Palette::new("palette-2")
.placeholder("Execute a command...") .placeholder("Execute a command...")
.items(vec![ .items(vec![
PaletteItem::new("theme selector: toggle").keybinding( PaletteItem::new("theme selector: toggle")
Keybinding::new_chord( .keybinding(KeyBinding::new(binding("cmd-k cmd-t"))),
("k".to_string(), ModifierKeys::new().command(true)), PaletteItem::new("assistant: inline assist")
("t".to_string(), ModifierKeys::new().command(true)), .keybinding(KeyBinding::new(binding("cmd-enter"))),
), PaletteItem::new("assistant: quote selection")
), .keybinding(KeyBinding::new(binding("cmd-<"))),
PaletteItem::new("assistant: inline assist").keybinding( PaletteItem::new("assistant: toggle focus")
Keybinding::new( .keybinding(KeyBinding::new(binding("cmd-?"))),
"enter".to_string(),
ModifierKeys::new().command(true),
),
),
PaletteItem::new("assistant: quote selection").keybinding(
Keybinding::new(
">".to_string(),
ModifierKeys::new().command(true),
),
),
PaletteItem::new("assistant: toggle focus").keybinding(
Keybinding::new(
"?".to_string(),
ModifierKeys::new().command(true),
),
),
PaletteItem::new("auto update: check"), PaletteItem::new("auto update: check"),
PaletteItem::new("auto update: view release notes"), PaletteItem::new("auto update: view release notes"),
PaletteItem::new("branches: open recent").keybinding( PaletteItem::new("branches: open recent")
Keybinding::new( .keybinding(KeyBinding::new(binding("cmd-alt-b"))),
"b".to_string(),
ModifierKeys::new().command(true).alt(true),
),
),
PaletteItem::new("chat panel: toggle focus"), PaletteItem::new("chat panel: toggle focus"),
PaletteItem::new("cli: install"), PaletteItem::new("cli: install"),
PaletteItem::new("client: sign in"), PaletteItem::new("client: sign in"),
PaletteItem::new("client: sign out"), PaletteItem::new("client: sign out"),
PaletteItem::new("editor: cancel").keybinding(Keybinding::new( PaletteItem::new("editor: cancel")
"escape".to_string(), .keybinding(KeyBinding::new(binding("escape"))),
ModifierKeys::new(),
)),
]), ]),
) )
} }

View file

@ -1,6 +1,8 @@
use gpui::{div, Div, ParentElement, Render, SharedString, Styled, ViewContext}; use gpui::{div, Div, ParentElement, Render, SharedString, Styled, ViewContext};
use theme2::ActiveTheme; use theme2::ActiveTheme;
use crate::StyledExt;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TextTooltip { pub struct TextTooltip {
title: SharedString, title: SharedString,
@ -16,16 +18,13 @@ impl Render for TextTooltip {
type Element = Div<Self>; type Element = Div<Self>;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let theme = cx.theme();
div() div()
.bg(theme.colors().background) .elevation_2(cx)
.rounded_lg()
.border()
.font("Zed Sans") .font("Zed Sans")
.border_color(theme.colors().border) .text_ui()
.text_color(theme.colors().text) .text_color(cx.theme().colors().text)
.pl_2() .py_1()
.pr_2() .px_2()
.child(self.title.clone()) .child(self.title.clone())
} }
} }

View file

@ -7,12 +7,12 @@ use gpui::{AppContext, ViewContext};
use rand::Rng; use rand::Rng;
use theme2::ActiveTheme; use theme2::ActiveTheme;
use crate::HighlightedText; use crate::{binding, HighlightedText};
use crate::{ use crate::{
Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus,
HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream, HighlightedLine, Icon, KeyBinding, Label, LabelColor, ListEntry, ListEntrySize, Livestream,
MicStatus, ModifierKeys, Notification, PaletteItem, Player, PlayerCallStatus, MicStatus, Notification, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus,
PlayerWithCallStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus, PublicPlayer, ScreenShareStatus, Symbol, Tab, Toggle, VideoStatus,
}; };
use crate::{ListItem, NotificationAction}; use crate::{ListItem, NotificationAction};
@ -701,46 +701,16 @@ pub fn static_collab_panel_channels() -> Vec<ListItem> {
pub fn example_editor_actions() -> Vec<PaletteItem> { pub fn example_editor_actions() -> Vec<PaletteItem> {
vec![ vec![
PaletteItem::new("New File").keybinding(Keybinding::new( PaletteItem::new("New File").keybinding(KeyBinding::new(binding("cmd-n"))),
"N".to_string(), PaletteItem::new("Open File").keybinding(KeyBinding::new(binding("cmd-o"))),
ModifierKeys::new().command(true), PaletteItem::new("Save File").keybinding(KeyBinding::new(binding("cmd-s"))),
)), PaletteItem::new("Cut").keybinding(KeyBinding::new(binding("cmd-x"))),
PaletteItem::new("Open File").keybinding(Keybinding::new( PaletteItem::new("Copy").keybinding(KeyBinding::new(binding("cmd-c"))),
"O".to_string(), PaletteItem::new("Paste").keybinding(KeyBinding::new(binding("cmd-v"))),
ModifierKeys::new().command(true), PaletteItem::new("Undo").keybinding(KeyBinding::new(binding("cmd-z"))),
)), PaletteItem::new("Redo").keybinding(KeyBinding::new(binding("cmd-shift-z"))),
PaletteItem::new("Save File").keybinding(Keybinding::new( PaletteItem::new("Find").keybinding(KeyBinding::new(binding("cmd-f"))),
"S".to_string(), PaletteItem::new("Replace").keybinding(KeyBinding::new(binding("cmd-r"))),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Cut").keybinding(Keybinding::new(
"X".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Copy").keybinding(Keybinding::new(
"C".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Paste").keybinding(Keybinding::new(
"V".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Undo").keybinding(Keybinding::new(
"Z".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Redo").keybinding(Keybinding::new(
"Z".to_string(),
ModifierKeys::new().command(true).shift(true),
)),
PaletteItem::new("Find").keybinding(Keybinding::new(
"F".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Replace").keybinding(Keybinding::new(
"R".to_string(),
ModifierKeys::new().command(true),
)),
PaletteItem::new("Jump to Line"), PaletteItem::new("Jump to Line"),
PaletteItem::new("Select All"), PaletteItem::new("Select All"),
PaletteItem::new("Deselect All"), PaletteItem::new("Deselect All"),

View file

@ -1401,20 +1401,32 @@ impl Pane {
// .on_drop(|_view, state: View<DraggedTab>, cx| { // .on_drop(|_view, state: View<DraggedTab>, cx| {
// eprintln!("{:?}", state.read(cx)); // eprintln!("{:?}", state.read(cx));
// }) // })
.px_2()
.py_0p5()
.flex() .flex()
.items_center() .items_center()
.justify_center() .justify_center()
// todo!("Nate - I need to do some work to balance all the items in the tab once things stablize")
.map(|this| {
if close_right {
this.pl_3().pr_1()
} else {
this.pr_1().pr_3()
}
})
.py_1()
.bg(tab_bg) .bg(tab_bg)
.hover(|h| h.bg(tab_hover_bg)) .border_color(cx.theme().colors().border)
.active(|a| a.bg(tab_active_bg)) .map(|this| match ix.cmp(&self.active_item_index) {
cmp::Ordering::Less => this.border_l(),
cmp::Ordering::Equal => this.border_r(),
cmp::Ordering::Greater => this.border_l().border_r(),
})
// .hover(|h| h.bg(tab_hover_bg))
// .active(|a| a.bg(tab_active_bg))
.child( .child(
div() div()
.px_1()
.flex() .flex()
.items_center() .items_center()
.gap_1p5() .gap_1()
.text_color(text_color) .text_color(text_color)
.children(if item.has_conflict(cx) { .children(if item.has_conflict(cx) {
Some( Some(

View file

@ -1130,53 +1130,56 @@ impl Workspace {
// })) // }))
// } // }
// pub fn prepare_to_close( pub fn prepare_to_close(
// &mut self, &mut self,
// quitting: bool, quitting: bool,
// cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
// ) -> Task<Result<bool>> { ) -> Task<Result<bool>> {
// let active_call = self.active_call().cloned(); //todo!(saveing)
// let window = cx.window(); // let active_call = self.active_call().cloned();
// let window = cx.window();
// cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
// let workspace_count = cx // let workspace_count = cx
// .windows() // .windows()
// .into_iter() // .into_iter()
// .filter(|window| window.root_is::<Workspace>()) // .filter(|window| window.root_is::<Workspace>())
// .count(); // .count();
// if let Some(active_call) = active_call { // if let Some(active_call) = active_call {
// if !quitting // if !quitting
// && workspace_count == 1 // && workspace_count == 1
// && active_call.read_with(&cx, |call, _| call.room().is_some()) // && active_call.read_with(&cx, |call, _| call.room().is_some())
// { // {
// let answer = window.prompt( // let answer = window.prompt(
// PromptLevel::Warning, // PromptLevel::Warning,
// "Do you want to leave the current call?", // "Do you want to leave the current call?",
// &["Close window and hang up", "Cancel"], // &["Close window and hang up", "Cancel"],
// &mut cx, // &mut cx,
// ); // );
// if let Some(mut answer) = answer { // if let Some(mut answer) = answer {
// if answer.next().await == Some(1) { // if answer.next().await == Some(1) {
// return anyhow::Ok(false); // return anyhow::Ok(false);
// } else { // } else {
// active_call // active_call
// .update(&mut cx, |call, cx| call.hang_up(cx)) // .update(&mut cx, |call, cx| call.hang_up(cx))
// .await // .await
// .log_err(); // .log_err();
// } // }
// } // }
// } // }
// } // }
// Ok(this Ok(
// .update(&mut cx, |this, cx| { false, // this
// this.save_all_internal(SaveIntent::Close, cx) // .update(&mut cx, |this, cx| {
// })? // this.save_all_internal(SaveIntent::Close, cx)
// .await?) // })?
// }) // .await?
// } )
})
}
// fn save_all( // fn save_all(
// &mut self, // &mut self,
@ -4062,24 +4065,24 @@ impl ViewId {
} }
} }
// pub trait WorkspaceHandle { pub trait WorkspaceHandle {
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>; fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
// } }
// impl WorkspaceHandle for View<Workspace> { impl WorkspaceHandle for View<Workspace> {
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> { fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
// self.read(cx) self.read(cx)
// .worktrees(cx) .worktrees(cx)
// .flat_map(|worktree| { .flat_map(|worktree| {
// let worktree_id = worktree.read(cx).id(); let worktree_id = worktree.read(cx).id();
// worktree.read(cx).files(true, 0).map(move |f| ProjectPath { worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
// worktree_id, worktree_id,
// path: f.path.clone(), path: f.path.clone(),
// }) })
// }) })
// .collect::<Vec<_>>() .collect::<Vec<_>>()
// } }
// } }
// impl std::fmt::Debug for OpenPaths { // impl std::fmt::Debug for OpenPaths {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View file

@ -9,6 +9,7 @@ use backtrace::Backtrace;
use cli::FORCE_CLI_MODE_ENV_VAR_NAME; use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
use client::UserStore; use client::UserStore;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use editor::Editor;
use fs::RealFs; use fs::RealFs;
use futures::StreamExt; use futures::StreamExt;
use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task}; use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
@ -56,10 +57,7 @@ use zed2::{
mod open_listener; mod open_listener;
fn main() { fn main() {
//TODO!(figure out what the linker issues are here) menu::init();
// https://github.com/rust-lang/rust/issues/47384
// https://github.com/mmastrac/rust-ctor/issues/280
menu::unused();
let http = http::client(); let http = http::client();
init_paths(); init_paths();
init_logger(); init_logger();
@ -357,8 +355,7 @@ async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncApp
} else { } else {
cx.update(|cx| { cx.update(|cx| {
workspace::open_new(app_state, cx, |workspace, 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(); .detach();
})?; })?;

View file

@ -56,7 +56,7 @@ pub fn initialize_workspace(
) -> Task<Result<()>> { ) -> Task<Result<()>> {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
workspace_handle.update(&mut cx, |workspace, cx| { workspace_handle.update(&mut cx, |workspace, cx| {
let workspace_handle = cx.view(); let workspace_handle = cx.view().clone();
cx.subscribe(&workspace_handle, { cx.subscribe(&workspace_handle, {
move |workspace, _, event, cx| { move |workspace, _, event, cx| {
if let workspace::Event::PaneAdded(pane) = event { if let workspace::Event::PaneAdded(pane) = event {