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",
"settings2",
"smol",
"theme",
"theme2",
"util",
]
@ -2768,7 +2768,6 @@ dependencies = [
"copilot2",
"ctor",
"db2",
"drag_and_drop",
"env_logger 0.9.3",
"futures 0.3.28",
"fuzzy2",

View file

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

View file

@ -510,7 +510,7 @@ fn test_fuzzy_like_string() {
#[gpui::test]
async fn test_fuzzy_search_users(cx: &mut TestAppContext) {
let test_db = TestDb::postgres(cx.executor().clone());
let test_db = TestDb::postgres(cx.executor());
let db = test_db.db();
for (i, github_login) in [
"California",

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,32 @@
// use editor::{
// test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
// ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo,
//todo(partially ported)
// use std::{
// path::Path,
// sync::{
// atomic::{self, AtomicBool, AtomicUsize},
// Arc,
// },
// };
//todo!(editor)
// use call::ActiveCall;
// use editor::{
// test::editor_test_context::{AssertionContextManager, EditorTestContext},
// Anchor, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename,
// ToggleCodeActions, Undo,
// };
// use gpui::{BackgroundExecutor, TestAppContext, VisualContext, VisualTestContext};
// use indoc::indoc;
// use language::{
// language_settings::{AllLanguageSettings, InlayHintSettings},
// tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
// };
// use rpc::RECEIVE_TIMEOUT;
// use serde_json::json;
// use settings::SettingsStore;
// use text::Point;
// use workspace::Workspace;
// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
// #[gpui::test(iterations = 10)]
// async fn test_host_disconnect(
// executor: BackgroundExecutor,
@ -11,7 +34,7 @@
// cx_b: &mut TestAppContext,
// cx_c: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let mut server = TestServer::start(executor).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// let client_c = server.create_client(cx_c, "user_c").await;
@ -25,7 +48,7 @@
// .fs()
// .insert_tree(
// "/a",
// json!({
// serde_json::json!({
// "a.txt": "a-contents",
// "b.txt": "b-contents",
// }),
@ -35,7 +58,7 @@
// let active_call_a = cx_a.read(ActiveCall::global);
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
// let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
// let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees().next().unwrap());
// let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await
@ -46,21 +69,25 @@
// assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
// let window_b =
// let workspace_b =
// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
// let workspace_b = window_b.root(cx_b);
// let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
// let editor_b = workspace_b
// .update(cx_b, |workspace, cx| {
// workspace.open_path((worktree_id, "b.txt"), None, true, cx)
// })
// .unwrap()
// .await
// .unwrap()
// .downcast::<Editor>()
// .unwrap();
// assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
// //TODO: focus
// assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
// editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
// assert!(window_b.is_edited(cx_b));
// //todo(is_edited)
// // assert!(workspace_b.is_edited(cx_b));
// // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
// server.forbid_connections();
@ -77,10 +104,10 @@
// // Ensure client B's edited state is reset and that the whole window is blurred.
// window_b.read_with(cx_b, |cx| {
// workspace_b.update(cx_b, |_, cx| {
// assert_eq!(cx.focused_view_id(), None);
// });
// assert!(!window_b.is_edited(cx_b));
// // assert!(!workspace_b.is_edited(cx_b));
// // Ensure client B is not prompted to save edits when closing window after disconnecting.
// let can_close = workspace_b
@ -120,7 +147,6 @@
// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
// }
//todo!(editor)
// #[gpui::test]
// async fn test_newline_above_or_below_does_not_move_guest_cursor(
// executor: BackgroundExecutor,
@ -152,12 +178,14 @@
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
// .await
// .unwrap();
// let window_a = cx_a.add_window(|_| EmptyView);
// let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
// let window_a = cx_a.add_empty_window();
// let editor_a =
// window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
// let mut editor_cx_a = EditorTestContext {
// cx: cx_a,
// window: window_a.into(),
// editor: editor_a,
// assertion_cx: AssertionContextManager::new(),
// };
// // Open a buffer as client B
@ -165,12 +193,14 @@
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
// .await
// .unwrap();
// let window_b = cx_b.add_window(|_| EmptyView);
// let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
// let window_b = cx_b.add_empty_window();
// let editor_b =
// window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
// let mut editor_cx_b = EditorTestContext {
// cx: cx_b,
// window: window_b.into(),
// editor: editor_b,
// assertion_cx: AssertionContextManager::new(),
// };
// // Test newline above
@ -214,7 +244,6 @@
// "});
// }
//todo!(editor)
// #[gpui::test(iterations = 10)]
// async fn test_collaborating_with_completion(
// executor: BackgroundExecutor,
@ -275,8 +304,8 @@
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
// .await
// .unwrap();
// let window_b = cx_b.add_window(|_| EmptyView);
// let editor_b = window_b.add_view(cx_b, |cx| {
// let window_b = cx_b.add_empty_window();
// let editor_b = window_b.build_view(cx_b, |cx| {
// Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
// });
@ -384,7 +413,7 @@
// );
// // The additional edit is applied.
// cx_a.foreground().run_until_parked();
// cx_a.executor().run_until_parked();
// buffer_a.read_with(cx_a, |buffer, _| {
// assert_eq!(
@ -400,7 +429,7 @@
// );
// });
// }
//todo!(editor)
// #[gpui::test(iterations = 10)]
// async fn test_collaborating_with_code_actions(
// executor: BackgroundExecutor,
@ -619,7 +648,6 @@
// });
// }
//todo!(editor)
// #[gpui::test(iterations = 10)]
// async fn test_collaborating_with_renames(
// executor: BackgroundExecutor,
@ -813,7 +841,6 @@
// })
// }
//todo!(editor)
// #[gpui::test(iterations = 10)]
// async fn test_language_server_statuses(
// executor: BackgroundExecutor,
@ -937,8 +964,8 @@
// cx_b: &mut TestAppContext,
// cx_c: &mut TestAppContext,
// ) {
// let window_b = cx_b.add_window(|_| EmptyView);
// let mut server = TestServer::start(&executor).await;
// let window_b = cx_b.add_empty_window();
// let mut server = TestServer::start(executor).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// let client_c = server.create_client(cx_c, "user_c").await;
@ -1052,7 +1079,7 @@
// .await
// .unwrap();
// let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
// let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
// // Client A sees client B's selection
// executor.run_until_parked();
@ -1106,3 +1133,757 @@
// == 0
// });
// }
// #[gpui::test(iterations = 10)]
// async fn test_on_input_format_from_host_to_guest(
// executor: BackgroundExecutor,
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
// .await;
// let active_call_a = cx_a.read(ActiveCall::global);
// // Set up a fake language server.
// let mut language = Language::new(
// LanguageConfig {
// name: "Rust".into(),
// path_suffixes: vec!["rs".to_string()],
// ..Default::default()
// },
// Some(tree_sitter_rust::language()),
// );
// let mut fake_language_servers = language
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
// capabilities: lsp::ServerCapabilities {
// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
// first_trigger_character: ":".to_string(),
// more_trigger_character: Some(vec![">".to_string()]),
// }),
// ..Default::default()
// },
// ..Default::default()
// }))
// .await;
// client_a.language_registry().add(Arc::new(language));
// client_a
// .fs()
// .insert_tree(
// "/a",
// json!({
// "main.rs": "fn main() { a }",
// "other.rs": "// Test file",
// }),
// )
// .await;
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
// let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await
// .unwrap();
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
// // Open a file in an editor as the host.
// let buffer_a = project_a
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
// .await
// .unwrap();
// let window_a = cx_a.add_empty_window();
// let editor_a = window_a
// .update(cx_a, |_, cx| {
// cx.build_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
// })
// .unwrap();
// let fake_language_server = fake_language_servers.next().await.unwrap();
// executor.run_until_parked();
// // Receive an OnTypeFormatting request as the host's language server.
// // Return some formattings from the host's language server.
// fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
// |params, _| async move {
// assert_eq!(
// params.text_document_position.text_document.uri,
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
// );
// assert_eq!(
// params.text_document_position.position,
// lsp::Position::new(0, 14),
// );
// Ok(Some(vec![lsp::TextEdit {
// new_text: "~<".to_string(),
// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
// }]))
// },
// );
// // Open the buffer on the guest and see that the formattings worked
// let buffer_b = project_b
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
// .await
// .unwrap();
// // Type a on type formatting trigger character as the guest.
// editor_a.update(cx_a, |editor, cx| {
// cx.focus(&editor_a);
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
// editor.handle_input(">", cx);
// });
// executor.run_until_parked();
// buffer_b.read_with(cx_b, |buffer, _| {
// assert_eq!(buffer.text(), "fn main() { a>~< }")
// });
// // Undo should remove LSP edits first
// editor_a.update(cx_a, |editor, cx| {
// assert_eq!(editor.text(cx), "fn main() { a>~< }");
// editor.undo(&Undo, cx);
// assert_eq!(editor.text(cx), "fn main() { a> }");
// });
// executor.run_until_parked();
// buffer_b.read_with(cx_b, |buffer, _| {
// assert_eq!(buffer.text(), "fn main() { a> }")
// });
// editor_a.update(cx_a, |editor, cx| {
// assert_eq!(editor.text(cx), "fn main() { a> }");
// editor.undo(&Undo, cx);
// assert_eq!(editor.text(cx), "fn main() { a }");
// });
// executor.run_until_parked();
// buffer_b.read_with(cx_b, |buffer, _| {
// assert_eq!(buffer.text(), "fn main() { a }")
// });
// }
// #[gpui::test(iterations = 10)]
// async fn test_on_input_format_from_guest_to_host(
// executor: BackgroundExecutor,
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
// .await;
// let active_call_a = cx_a.read(ActiveCall::global);
// // Set up a fake language server.
// let mut language = Language::new(
// LanguageConfig {
// name: "Rust".into(),
// path_suffixes: vec!["rs".to_string()],
// ..Default::default()
// },
// Some(tree_sitter_rust::language()),
// );
// let mut fake_language_servers = language
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
// capabilities: lsp::ServerCapabilities {
// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
// first_trigger_character: ":".to_string(),
// more_trigger_character: Some(vec![">".to_string()]),
// }),
// ..Default::default()
// },
// ..Default::default()
// }))
// .await;
// client_a.language_registry().add(Arc::new(language));
// client_a
// .fs()
// .insert_tree(
// "/a",
// json!({
// "main.rs": "fn main() { a }",
// "other.rs": "// Test file",
// }),
// )
// .await;
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
// let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await
// .unwrap();
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
// // Open a file in an editor as the guest.
// let buffer_b = project_b
// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
// .await
// .unwrap();
// let window_b = cx_b.add_empty_window();
// let editor_b = window_b.build_view(cx_b, |cx| {
// Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
// });
// let fake_language_server = fake_language_servers.next().await.unwrap();
// executor.run_until_parked();
// // Type a on type formatting trigger character as the guest.
// editor_b.update(cx_b, |editor, cx| {
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
// editor.handle_input(":", cx);
// cx.focus(&editor_b);
// });
// // Receive an OnTypeFormatting request as the host's language server.
// // Return some formattings from the host's language server.
// cx_a.foreground().start_waiting();
// fake_language_server
// .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
// assert_eq!(
// params.text_document_position.text_document.uri,
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
// );
// assert_eq!(
// params.text_document_position.position,
// lsp::Position::new(0, 14),
// );
// Ok(Some(vec![lsp::TextEdit {
// new_text: "~:".to_string(),
// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
// }]))
// })
// .next()
// .await
// .unwrap();
// cx_a.foreground().finish_waiting();
// // Open the buffer on the host and see that the formattings worked
// let buffer_a = project_a
// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
// .await
// .unwrap();
// executor.run_until_parked();
// buffer_a.read_with(cx_a, |buffer, _| {
// assert_eq!(buffer.text(), "fn main() { a:~: }")
// });
// // Undo should remove LSP edits first
// editor_b.update(cx_b, |editor, cx| {
// assert_eq!(editor.text(cx), "fn main() { a:~: }");
// editor.undo(&Undo, cx);
// assert_eq!(editor.text(cx), "fn main() { a: }");
// });
// executor.run_until_parked();
// buffer_a.read_with(cx_a, |buffer, _| {
// assert_eq!(buffer.text(), "fn main() { a: }")
// });
// editor_b.update(cx_b, |editor, cx| {
// assert_eq!(editor.text(cx), "fn main() { a: }");
// editor.undo(&Undo, cx);
// assert_eq!(editor.text(cx), "fn main() { a }");
// });
// executor.run_until_parked();
// buffer_a.read_with(cx_a, |buffer, _| {
// assert_eq!(buffer.text(), "fn main() { a }")
// });
// }
// #[gpui::test(iterations = 10)]
// async fn test_mutual_editor_inlay_hint_cache_update(
// executor: BackgroundExecutor,
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
// .await;
// let active_call_a = cx_a.read(ActiveCall::global);
// let active_call_b = cx_b.read(ActiveCall::global);
// cx_a.update(editor::init);
// cx_b.update(editor::init);
// cx_a.update(|cx| {
// cx.update_global(|store: &mut SettingsStore, cx| {
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
// settings.defaults.inlay_hints = Some(InlayHintSettings {
// enabled: true,
// show_type_hints: true,
// show_parameter_hints: false,
// show_other_hints: true,
// })
// });
// });
// });
// cx_b.update(|cx| {
// cx.update_global(|store: &mut SettingsStore, cx| {
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
// settings.defaults.inlay_hints = Some(InlayHintSettings {
// enabled: true,
// show_type_hints: true,
// show_parameter_hints: false,
// show_other_hints: true,
// })
// });
// });
// });
// let mut language = Language::new(
// LanguageConfig {
// name: "Rust".into(),
// path_suffixes: vec!["rs".to_string()],
// ..Default::default()
// },
// Some(tree_sitter_rust::language()),
// );
// let mut fake_language_servers = language
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
// capabilities: lsp::ServerCapabilities {
// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
// ..Default::default()
// },
// ..Default::default()
// }))
// .await;
// let language = Arc::new(language);
// client_a.language_registry().add(Arc::clone(&language));
// client_b.language_registry().add(language);
// // Client A opens a project.
// client_a
// .fs()
// .insert_tree(
// "/a",
// json!({
// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
// "other.rs": "// Test file",
// }),
// )
// .await;
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
// active_call_a
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
// .await
// .unwrap();
// let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await
// .unwrap();
// // Client B joins the project
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
// active_call_b
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
// .await
// .unwrap();
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root_view(cx_a);
// cx_a.foreground().start_waiting();
// // The host opens a rust file.
// let _buffer_a = project_a
// .update(cx_a, |project, cx| {
// project.open_local_buffer("/a/main.rs", cx)
// })
// .await
// .unwrap();
// let fake_language_server = fake_language_servers.next().await.unwrap();
// let editor_a = workspace_a
// .update(cx_a, |workspace, cx| {
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
// })
// .await
// .unwrap()
// .downcast::<Editor>()
// .unwrap();
// // Set up the language server to return an additional inlay hint on each request.
// let edits_made = Arc::new(AtomicUsize::new(0));
// let closure_edits_made = Arc::clone(&edits_made);
// fake_language_server
// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
// let task_edits_made = Arc::clone(&closure_edits_made);
// async move {
// assert_eq!(
// params.text_document.uri,
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
// );
// let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
// Ok(Some(vec![lsp::InlayHint {
// position: lsp::Position::new(0, edits_made as u32),
// label: lsp::InlayHintLabel::String(edits_made.to_string()),
// kind: None,
// text_edits: None,
// tooltip: None,
// padding_left: None,
// padding_right: None,
// data: None,
// }]))
// }
// })
// .next()
// .await
// .unwrap();
// executor.run_until_parked();
// let initial_edit = edits_made.load(atomic::Ordering::Acquire);
// editor_a.update(cx_a, |editor, _| {
// assert_eq!(
// vec![initial_edit.to_string()],
// extract_hint_labels(editor),
// "Host should get its first hints when opens an editor"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 1,
// "Host editor update the cache version after every cache/view change",
// );
// });
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
// let editor_b = workspace_b
// .update(cx_b, |workspace, cx| {
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
// })
// .await
// .unwrap()
// .downcast::<Editor>()
// .unwrap();
// executor.run_until_parked();
// editor_b.update(cx_b, |editor, _| {
// assert_eq!(
// vec![initial_edit.to_string()],
// extract_hint_labels(editor),
// "Client should get its first hints when opens an editor"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 1,
// "Guest editor update the cache version after every cache/view change"
// );
// });
// let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
// editor_b.update(cx_b, |editor, cx| {
// editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
// editor.handle_input(":", cx);
// cx.focus(&editor_b);
// });
// executor.run_until_parked();
// editor_a.update(cx_a, |editor, _| {
// assert_eq!(
// vec![after_client_edit.to_string()],
// extract_hint_labels(editor),
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(inlay_cache.version(), 2);
// });
// editor_b.update(cx_b, |editor, _| {
// assert_eq!(
// vec![after_client_edit.to_string()],
// extract_hint_labels(editor),
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(inlay_cache.version(), 2);
// });
// let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
// editor_a.update(cx_a, |editor, cx| {
// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
// editor.handle_input("a change to increment both buffers' versions", cx);
// cx.focus(&editor_a);
// });
// executor.run_until_parked();
// editor_a.update(cx_a, |editor, _| {
// assert_eq!(
// vec![after_host_edit.to_string()],
// extract_hint_labels(editor),
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(inlay_cache.version(), 3);
// });
// editor_b.update(cx_b, |editor, _| {
// assert_eq!(
// vec![after_host_edit.to_string()],
// extract_hint_labels(editor),
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(inlay_cache.version(), 3);
// });
// let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
// fake_language_server
// .request::<lsp::request::InlayHintRefreshRequest>(())
// .await
// .expect("inlay refresh request failed");
// executor.run_until_parked();
// editor_a.update(cx_a, |editor, _| {
// assert_eq!(
// vec![after_special_edit_for_refresh.to_string()],
// extract_hint_labels(editor),
// "Host should react to /refresh LSP request"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 4,
// "Host should accepted all edits and bump its cache version every time"
// );
// });
// editor_b.update(cx_b, |editor, _| {
// assert_eq!(
// vec![after_special_edit_for_refresh.to_string()],
// extract_hint_labels(editor),
// "Guest should get a /refresh LSP request propagated by host"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 4,
// "Guest should accepted all edits and bump its cache version every time"
// );
// });
// }
// #[gpui::test(iterations = 10)]
// async fn test_inlay_hint_refresh_is_forwarded(
// executor: BackgroundExecutor,
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
// let mut server = TestServer::start(&executor).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
// server
// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
// .await;
// let active_call_a = cx_a.read(ActiveCall::global);
// let active_call_b = cx_b.read(ActiveCall::global);
// cx_a.update(editor::init);
// cx_b.update(editor::init);
// cx_a.update(|cx| {
// cx.update_global(|store: &mut SettingsStore, cx| {
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
// settings.defaults.inlay_hints = Some(InlayHintSettings {
// enabled: false,
// show_type_hints: false,
// show_parameter_hints: false,
// show_other_hints: false,
// })
// });
// });
// });
// cx_b.update(|cx| {
// cx.update_global(|store: &mut SettingsStore, cx| {
// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
// settings.defaults.inlay_hints = Some(InlayHintSettings {
// enabled: true,
// show_type_hints: true,
// show_parameter_hints: true,
// show_other_hints: true,
// })
// });
// });
// });
// let mut language = Language::new(
// LanguageConfig {
// name: "Rust".into(),
// path_suffixes: vec!["rs".to_string()],
// ..Default::default()
// },
// Some(tree_sitter_rust::language()),
// );
// let mut fake_language_servers = language
// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
// capabilities: lsp::ServerCapabilities {
// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
// ..Default::default()
// },
// ..Default::default()
// }))
// .await;
// let language = Arc::new(language);
// client_a.language_registry().add(Arc::clone(&language));
// client_b.language_registry().add(language);
// client_a
// .fs()
// .insert_tree(
// "/a",
// json!({
// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
// "other.rs": "// Test file",
// }),
// )
// .await;
// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
// active_call_a
// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
// .await
// .unwrap();
// let project_id = active_call_a
// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
// .await
// .unwrap();
// let project_b = client_b.build_remote_project(project_id, cx_b).await;
// active_call_b
// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
// .await
// .unwrap();
// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
// cx_a.foreground().start_waiting();
// cx_b.foreground().start_waiting();
// let editor_a = workspace_a
// .update(cx_a, |workspace, cx| {
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
// })
// .await
// .unwrap()
// .downcast::<Editor>()
// .unwrap();
// let editor_b = workspace_b
// .update(cx_b, |workspace, cx| {
// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
// })
// .await
// .unwrap()
// .downcast::<Editor>()
// .unwrap();
// let other_hints = Arc::new(AtomicBool::new(false));
// let fake_language_server = fake_language_servers.next().await.unwrap();
// let closure_other_hints = Arc::clone(&other_hints);
// fake_language_server
// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
// let task_other_hints = Arc::clone(&closure_other_hints);
// async move {
// assert_eq!(
// params.text_document.uri,
// lsp::Url::from_file_path("/a/main.rs").unwrap(),
// );
// let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
// let character = if other_hints { 0 } else { 2 };
// let label = if other_hints {
// "other hint"
// } else {
// "initial hint"
// };
// Ok(Some(vec![lsp::InlayHint {
// position: lsp::Position::new(0, character),
// label: lsp::InlayHintLabel::String(label.to_string()),
// kind: None,
// text_edits: None,
// tooltip: None,
// padding_left: None,
// padding_right: None,
// data: None,
// }]))
// }
// })
// .next()
// .await
// .unwrap();
// cx_a.foreground().finish_waiting();
// cx_b.foreground().finish_waiting();
// executor.run_until_parked();
// editor_a.update(cx_a, |editor, _| {
// assert!(
// extract_hint_labels(editor).is_empty(),
// "Host should get no hints due to them turned off"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 0,
// "Turned off hints should not generate version updates"
// );
// });
// executor.run_until_parked();
// editor_b.update(cx_b, |editor, _| {
// assert_eq!(
// vec!["initial hint".to_string()],
// extract_hint_labels(editor),
// "Client should get its first hints when opens an editor"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 1,
// "Should update cache verison after first hints"
// );
// });
// other_hints.fetch_or(true, atomic::Ordering::Release);
// fake_language_server
// .request::<lsp::request::InlayHintRefreshRequest>(())
// .await
// .expect("inlay refresh request failed");
// executor.run_until_parked();
// editor_a.update(cx_a, |editor, _| {
// assert!(
// extract_hint_labels(editor).is_empty(),
// "Host should get nop hints due to them turned off, even after the /refresh"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 0,
// "Turned off hints should not generate version updates, again"
// );
// });
// executor.run_until_parked();
// editor_b.update(cx_b, |editor, _| {
// assert_eq!(
// vec!["other hint".to_string()],
// extract_hint_labels(editor),
// "Guest should get a /refresh LSP request propagated by host despite host hints are off"
// );
// let inlay_cache = editor.inlay_hint_cache();
// assert_eq!(
// inlay_cache.version(),
// 2,
// "Guest should accepted all edits and bump its cache version every time"
// );
// });
// }
// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
// let mut labels = Vec::new();
// for hint in editor.inlay_hint_cache().hints() {
// match hint.label {
// project::InlayHintLabel::String(s) => labels.push(s),
// _ => unreachable!(),
// }
// }
// labels
// }

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

View file

@ -11,7 +11,7 @@ use std::{
sync::Arc,
};
use theme::ActiveTheme;
use ui::{v_stack, HighlightedLabel, StyledExt};
use ui::{h_stack, v_stack, HighlightedLabel, KeyBinding, StyledExt};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@ -318,66 +318,16 @@ impl PickerDelegate for CommandPaletteDelegate {
.rounded_md()
.when(selected, |this| this.bg(colors.ghost_element_selected))
.hover(|this| this.bg(colors.ghost_element_hover))
.child(
h_stack()
.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 {

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1683,21 +1683,24 @@ impl EditorElement {
ShowScrollbar::Never => false,
};
let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = fold_ranges
.into_iter()
.map(|(id, fold)| {
todo!("folds!")
// let color = self
// .style
// .folds
// .ellipses
// .background
// .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
// .color;
let fold_ranges: Vec<(BufferRow, Range<DisplayPoint>, Hsla)> = Vec::new();
// todo!()
// (id, fold, color)
})
.collect();
// fold_ranges
// .into_iter()
// .map(|(id, fold)| {
// // todo!("folds!")
// // let color = self
// // .style
// // .folds
// // .ellipses
// // .background
// // .style_for(&mut cx.mouse_state::<FoldMarkers>(id as usize))
// // .color;
// // (id, fold, color)
// })
// .collect();
let head_for_relative = newest_selection_head.unwrap_or_else(|| {
let newest = editor.selections.newest::<Point>(cx);

View file

@ -30,6 +30,7 @@ use std::{
};
use text::Selection;
use theme::{ActiveTheme, Theme};
use ui::{Label, LabelColor};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
use workspace::{
@ -595,16 +596,19 @@ impl Item for Editor {
.flex_row()
.items_center()
.gap_2()
.child(self.title(cx).to_string())
.child(Label::new(self.title(cx).to_string()))
.children(detail.and_then(|detail| {
let path = path_for_buffer(&self.buffer, detail, false, cx)?;
let description = path.to_string_lossy();
Some(
div()
.text_color(theme.colors().text_muted)
.text_xs()
.child(util::truncate_and_trailoff(&description, MAX_TAB_TITLE_LEN)),
div().child(
Label::new(util::truncate_and_trailoff(
&description,
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);
dbg!("****START COL****");
let start_col = layed_out_line.closest_index_for_x(positions.start) as u32;
if start_col < line_len || (is_empty && positions.start == layed_out_line.width) {
let start = DisplayPoint::new(row, start_col);
dbg!("****END COL****");
let end_col = layed_out_line.closest_index_for_x(positions.end) as u32;
let end = DisplayPoint::new(row, end_col);
dbg!(start_col, end_col);
Some(Selection {
id: post_inc(&mut self.next_selection_id),

View file

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

View file

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

View file

@ -1,331 +1,400 @@
use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
};
use collections::BTreeMap;
use futures::Future;
use gpui::{
AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
VisualTestContext, WindowHandle,
};
use indoc::indoc;
use itertools::Itertools;
use language::{Buffer, BufferSnapshot};
use parking_lot::RwLock;
use project::{FakeFs, Project};
use std::{
any::TypeId,
ops::{Deref, DerefMut, Range},
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
use util::{
assert_set_eq,
test::{generate_marked_text, marked_text_ranges},
};
// use super::build_editor_with_project;
use super::build_editor_with_project;
// pub struct EditorTestContext<'a> {
// pub cx: &'a mut gpui::TestAppContext,
// pub window: AnyWindowHandle,
// pub editor: View<Editor>,
// }
pub struct EditorTestContext<'a> {
pub cx: gpui::VisualTestContext<'a>,
pub window: AnyWindowHandle,
pub editor: View<Editor>,
pub assertion_cx: AssertionContextManager,
}
// impl<'a> EditorTestContext<'a> {
// pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
// let fs = FakeFs::new(cx.background());
// // fs.insert_file("/file", "".to_owned()).await;
// fs.insert_tree(
// "/root",
// gpui::serde_json::json!({
// "file": "",
// }),
// )
// .await;
// let project = Project::test(fs, ["/root".as_ref()], cx).await;
// let buffer = project
// .update(cx, |project, cx| {
// project.open_local_buffer("/root/file", cx)
// })
// .await
// .unwrap();
// let window = cx.add_window(|cx| {
// cx.focus_self();
// build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx)
// });
// let editor = window.root(cx);
// Self {
// cx,
// window: window.into(),
// editor,
// }
// }
impl<'a> EditorTestContext<'a> {
pub async fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
let fs = FakeFs::new(cx.executor());
// fs.insert_file("/file", "".to_owned()).await;
fs.insert_tree(
"/root",
gpui::serde_json::json!({
"file": "",
}),
)
.await;
let project = Project::test(fs, ["/root".as_ref()], cx).await;
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer("/root/file", cx)
})
.await
.unwrap();
let editor = cx.add_window(|cx| {
let editor =
build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx);
editor.focus(cx);
editor
});
let editor_view = editor.root_view(cx).unwrap();
Self {
cx: VisualTestContext::from_window(*editor.deref(), cx),
window: editor.into(),
editor: editor_view,
assertion_cx: AssertionContextManager::new(),
}
}
// pub fn condition(
// &self,
// predicate: impl FnMut(&Editor, &AppContext) -> bool,
// ) -> impl Future<Output = ()> {
// self.editor.condition(self.cx, predicate)
// }
pub fn condition(
&self,
predicate: impl FnMut(&Editor, &AppContext) -> bool,
) -> impl Future<Output = ()> {
self.editor.condition::<crate::Event>(&self.cx, predicate)
}
// pub fn editor<F, T>(&self, read: F) -> T
// where
// F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
// {
// self.editor.update(self.cx, read)
// }
#[track_caller]
pub fn editor<F, T>(&mut self, read: F) -> T
where
F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
{
self.editor
.update(&mut self.cx, |this, cx| read(&this, &cx))
}
// pub fn update_editor<F, T>(&mut self, update: F) -> T
// where
// F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
// {
// self.editor.update(self.cx, update)
// }
#[track_caller]
pub fn update_editor<F, T>(&mut self, update: F) -> T
where
F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
{
self.editor.update(&mut self.cx, update)
}
// pub fn multibuffer<F, T>(&self, read: F) -> T
// where
// F: FnOnce(&MultiBuffer, &AppContext) -> T,
// {
// self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
// }
pub fn multibuffer<F, T>(&mut self, read: F) -> T
where
F: FnOnce(&MultiBuffer, &AppContext) -> T,
{
self.editor(|editor, cx| read(editor.buffer().read(cx), cx))
}
// pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
// where
// F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
// {
// self.update_editor(|editor, cx| editor.buffer().update(cx, update))
// }
pub fn update_multibuffer<F, T>(&mut self, update: F) -> T
where
F: FnOnce(&mut MultiBuffer, &mut ModelContext<MultiBuffer>) -> T,
{
self.update_editor(|editor, cx| editor.buffer().update(cx, update))
}
// pub fn buffer_text(&self) -> String {
// self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
// }
pub fn buffer_text(&mut self) -> String {
self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
}
// pub fn buffer<F, T>(&self, read: F) -> T
// where
// F: FnOnce(&Buffer, &AppContext) -> T,
// {
// self.multibuffer(|multibuffer, cx| {
// let buffer = multibuffer.as_singleton().unwrap().read(cx);
// read(buffer, cx)
// })
// }
pub fn buffer<F, T>(&mut self, read: F) -> T
where
F: FnOnce(&Buffer, &AppContext) -> T,
{
self.multibuffer(|multibuffer, cx| {
let buffer = multibuffer.as_singleton().unwrap().read(cx);
read(buffer, cx)
})
}
// pub fn update_buffer<F, T>(&mut self, update: F) -> T
// where
// F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
// {
// self.update_multibuffer(|multibuffer, cx| {
// let buffer = multibuffer.as_singleton().unwrap();
// buffer.update(cx, update)
// })
// }
pub fn update_buffer<F, T>(&mut self, update: F) -> T
where
F: FnOnce(&mut Buffer, &mut ModelContext<Buffer>) -> T,
{
self.update_multibuffer(|multibuffer, cx| {
let buffer = multibuffer.as_singleton().unwrap();
buffer.update(cx, update)
})
}
// pub fn buffer_snapshot(&self) -> BufferSnapshot {
// self.buffer(|buffer, _| buffer.snapshot())
// }
pub fn buffer_snapshot(&mut self) -> BufferSnapshot {
self.buffer(|buffer, _| buffer.snapshot())
}
// pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
// let keystroke_under_test_handle =
// self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
// let keystroke = Keystroke::parse(keystroke_text).unwrap();
pub fn add_assertion_context(&self, context: String) -> ContextHandle {
self.assertion_cx.add_context(context)
}
// self.cx.dispatch_keystroke(self.window, keystroke, false);
pub fn assertion_context(&self) -> String {
self.assertion_cx.context()
}
// keystroke_under_test_handle
// }
pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
let keystroke_under_test_handle =
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
let keystroke = Keystroke::parse(keystroke_text).unwrap();
// pub fn simulate_keystrokes<const COUNT: usize>(
// &mut self,
// keystroke_texts: [&str; COUNT],
// ) -> ContextHandle {
// let keystrokes_under_test_handle =
// self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
// for keystroke_text in keystroke_texts.into_iter() {
// self.simulate_keystroke(keystroke_text);
// }
// // it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete
// // before returning.
// // NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
// // quickly races with async actions.
// if let Foreground::Deterministic { cx_id: _, executor } = self.cx.foreground().as_ref() {
// executor.run_until_parked();
// } else {
// unreachable!();
// }
self.cx.dispatch_keystroke(self.window, keystroke, false);
// keystrokes_under_test_handle
// }
keystroke_under_test_handle
}
// pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
// let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
// assert_eq!(self.buffer_text(), unmarked_text);
// ranges
// }
pub fn simulate_keystrokes<const COUNT: usize>(
&mut self,
keystroke_texts: [&str; COUNT],
) -> ContextHandle {
let keystrokes_under_test_handle =
self.add_assertion_context(format!("Simulated Keystrokes: {:?}", keystroke_texts));
for keystroke_text in keystroke_texts.into_iter() {
self.simulate_keystroke(keystroke_text);
}
// it is common for keyboard shortcuts to kick off async actions, so this ensures that they are complete
// before returning.
// NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
// quickly races with async actions.
self.cx.background_executor.run_until_parked();
// pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
// let ranges = self.ranges(marked_text);
// let snapshot = self
// .editor
// .update(self.cx, |editor, cx| editor.snapshot(cx));
// ranges[0].start.to_display_point(&snapshot)
// }
keystrokes_under_test_handle
}
// // Returns anchors for the current buffer using `«` and `»`
// pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
// let ranges = self.ranges(marked_text);
// let snapshot = self.buffer_snapshot();
// snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
// }
pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
assert_eq!(self.buffer_text(), unmarked_text);
ranges
}
// pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
// let diff_base = diff_base.map(String::from);
// self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
// }
pub fn display_point(&mut self, marked_text: &str) -> DisplayPoint {
let ranges = self.ranges(marked_text);
let snapshot = self
.editor
.update(&mut self.cx, |editor, cx| editor.snapshot(cx));
ranges[0].start.to_display_point(&snapshot)
}
// /// Change the editor's text and selections using a string containing
// /// embedded range markers that represent the ranges and directions of
// /// each selection.
// ///
// /// Returns a context handle so that assertion failures can print what
// /// editor state was needed to cause the failure.
// ///
// /// See the `util::test::marked_text_ranges` function for more information.
// pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
// let state_context = self.add_assertion_context(format!(
// "Initial Editor State: \"{}\"",
// marked_text.escape_debug().to_string()
// ));
// let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
// self.editor.update(self.cx, |editor, cx| {
// editor.set_text(unmarked_text, cx);
// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
// s.select_ranges(selection_ranges)
// })
// });
// state_context
// }
// Returns anchors for the current buffer using `«` and `»`
pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
let ranges = self.ranges(marked_text);
let snapshot = self.buffer_snapshot();
snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
}
// /// Only change the editor's selections
// pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
// let state_context = self.add_assertion_context(format!(
// "Initial Editor State: \"{}\"",
// marked_text.escape_debug().to_string()
// ));
// let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
// self.editor.update(self.cx, |editor, cx| {
// assert_eq!(editor.text(cx), unmarked_text);
// editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
// s.select_ranges(selection_ranges)
// })
// });
// state_context
// }
pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
let diff_base = diff_base.map(String::from);
self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base, cx));
}
// /// Make an assertion about the editor's text and the ranges and directions
// /// of its selections using a string containing embedded range markers.
// ///
// /// See the `util::test::marked_text_ranges` function for more information.
// #[track_caller]
// pub fn assert_editor_state(&mut self, marked_text: &str) {
// let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
// let buffer_text = self.buffer_text();
/// Change the editor's text and selections using a string containing
/// embedded range markers that represent the ranges and directions of
/// each selection.
///
/// Returns a context handle so that assertion failures can print what
/// editor state was needed to cause the failure.
///
/// See the `util::test::marked_text_ranges` function for more information.
pub fn set_state(&mut self, marked_text: &str) -> ContextHandle {
let state_context = self.add_assertion_context(format!(
"Initial Editor State: \"{}\"",
marked_text.escape_debug().to_string()
));
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
self.editor.update(&mut self.cx, |editor, cx| {
editor.set_text(unmarked_text, cx);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(selection_ranges)
})
});
state_context
}
// if buffer_text != unmarked_text {
// panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
// }
/// Only change the editor's selections
pub fn set_selections_state(&mut self, marked_text: &str) -> ContextHandle {
let state_context = self.add_assertion_context(format!(
"Initial Editor State: \"{}\"",
marked_text.escape_debug().to_string()
));
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
self.editor.update(&mut self.cx, |editor, cx| {
assert_eq!(editor.text(cx), unmarked_text);
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges(selection_ranges)
})
});
state_context
}
// self.assert_selections(expected_selections, marked_text.to_string())
// }
/// Make an assertion about the editor's text and the ranges and directions
/// of its selections using a string containing embedded range markers.
///
/// See the `util::test::marked_text_ranges` function for more information.
#[track_caller]
pub fn assert_editor_state(&mut self, marked_text: &str) {
let (unmarked_text, expected_selections) = marked_text_ranges(marked_text, true);
let buffer_text = self.buffer_text();
// pub fn editor_state(&mut self) -> String {
// generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
// }
if buffer_text != unmarked_text {
panic!("Unmarked text doesn't match buffer text\nBuffer text: {buffer_text:?}\nUnmarked text: {unmarked_text:?}\nRaw buffer text\n{buffer_text}Raw unmarked text\n{unmarked_text}");
}
// #[track_caller]
// pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
// let expected_ranges = self.ranges(marked_text);
// let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
// let snapshot = editor.snapshot(cx);
// editor
// .background_highlights
// .get(&TypeId::of::<Tag>())
// .map(|h| h.1.clone())
// .unwrap_or_default()
// .into_iter()
// .map(|range| range.to_offset(&snapshot.buffer_snapshot))
// .collect()
// });
// assert_set_eq!(actual_ranges, expected_ranges);
// }
self.assert_selections(expected_selections, marked_text.to_string())
}
// #[track_caller]
// pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
// let expected_ranges = self.ranges(marked_text);
// let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
// let actual_ranges: Vec<Range<usize>> = snapshot
// .text_highlight_ranges::<Tag>()
// .map(|ranges| ranges.as_ref().clone().1)
// .unwrap_or_default()
// .into_iter()
// .map(|range| range.to_offset(&snapshot.buffer_snapshot))
// .collect();
// assert_set_eq!(actual_ranges, expected_ranges);
// }
pub fn editor_state(&mut self) -> String {
generate_marked_text(self.buffer_text().as_str(), &self.editor_selections(), true)
}
// #[track_caller]
// pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
// let expected_marked_text =
// generate_marked_text(&self.buffer_text(), &expected_selections, true);
// self.assert_selections(expected_selections, expected_marked_text)
// }
#[track_caller]
pub fn assert_editor_background_highlights<Tag: 'static>(&mut self, marked_text: &str) {
let expected_ranges = self.ranges(marked_text);
let actual_ranges: Vec<Range<usize>> = self.update_editor(|editor, cx| {
let snapshot = editor.snapshot(cx);
editor
.background_highlights
.get(&TypeId::of::<Tag>())
.map(|h| h.1.clone())
.unwrap_or_default()
.into_iter()
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect()
});
assert_set_eq!(actual_ranges, expected_ranges);
}
// fn editor_selections(&self) -> Vec<Range<usize>> {
// self.editor
// .read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
// .into_iter()
// .map(|s| {
// if s.reversed {
// s.end..s.start
// } else {
// s.start..s.end
// }
// })
// .collect::<Vec<_>>()
// }
#[track_caller]
pub fn assert_editor_text_highlights<Tag: ?Sized + 'static>(&mut self, marked_text: &str) {
let expected_ranges = self.ranges(marked_text);
let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
let actual_ranges: Vec<Range<usize>> = snapshot
.text_highlight_ranges::<Tag>()
.map(|ranges| ranges.as_ref().clone().1)
.unwrap_or_default()
.into_iter()
.map(|range| range.to_offset(&snapshot.buffer_snapshot))
.collect();
assert_set_eq!(actual_ranges, expected_ranges);
}
// #[track_caller]
// fn assert_selections(
// &mut self,
// expected_selections: Vec<Range<usize>>,
// expected_marked_text: String,
// ) {
// let actual_selections = self.editor_selections();
// let actual_marked_text =
// generate_marked_text(&self.buffer_text(), &actual_selections, true);
// if expected_selections != actual_selections {
// panic!(
// indoc! {"
#[track_caller]
pub fn assert_editor_selections(&mut self, expected_selections: Vec<Range<usize>>) {
let expected_marked_text =
generate_marked_text(&self.buffer_text(), &expected_selections, true);
self.assert_selections(expected_selections, expected_marked_text)
}
// {}Editor has unexpected selections.
#[track_caller]
fn editor_selections(&mut self) -> Vec<Range<usize>> {
self.editor
.update(&mut self.cx, |editor, cx| {
editor.selections.all::<usize>(cx)
})
.into_iter()
.map(|s| {
if s.reversed {
s.end..s.start
} else {
s.start..s.end
}
})
.collect::<Vec<_>>()
}
// Expected selections:
// {}
#[track_caller]
fn assert_selections(
&mut self,
expected_selections: Vec<Range<usize>>,
expected_marked_text: String,
) {
let actual_selections = self.editor_selections();
let actual_marked_text =
generate_marked_text(&self.buffer_text(), &actual_selections, true);
if expected_selections != actual_selections {
panic!(
indoc! {"
// Actual selections:
// {}
// "},
// self.assertion_context(),
// expected_marked_text,
// actual_marked_text,
// );
// }
// }
// }
//
// impl<'a> Deref for EditorTestContext<'a> {
// type Target = gpui::TestAppContext;
{}Editor has unexpected selections.
// fn deref(&self) -> &Self::Target {
// self.cx
// }
// }
Expected selections:
{}
// impl<'a> DerefMut for EditorTestContext<'a> {
// fn deref_mut(&mut self) -> &mut Self::Target {
// &mut self.cx
// }
// }
Actual selections:
{}
"},
self.assertion_context(),
expected_marked_text,
actual_marked_text,
);
}
}
}
impl<'a> Deref for EditorTestContext<'a> {
type Target = gpui::TestAppContext;
fn deref(&self) -> &Self::Target {
&self.cx
}
}
impl<'a> DerefMut for EditorTestContext<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.cx
}
}
/// Tracks string context to be printed when assertions fail.
/// Often this is done by storing a context string in the manager and returning the handle.
#[derive(Clone)]
pub struct AssertionContextManager {
id: Arc<AtomicUsize>,
contexts: Arc<RwLock<BTreeMap<usize, String>>>,
}
impl AssertionContextManager {
pub fn new() -> Self {
Self {
id: Arc::new(AtomicUsize::new(0)),
contexts: Arc::new(RwLock::new(BTreeMap::new())),
}
}
pub fn add_context(&self, context: String) -> ContextHandle {
let id = self.id.fetch_add(1, Ordering::Relaxed);
let mut contexts = self.contexts.write();
contexts.insert(id, context);
ContextHandle {
id,
manager: self.clone(),
}
}
pub fn context(&self) -> String {
let contexts = self.contexts.read();
format!("\n{}\n", contexts.values().join("\n"))
}
}
/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
/// the state that was set initially for the failure can be printed in the error message
pub struct ContextHandle {
id: usize,
manager: AssertionContextManager,
}
impl Drop for ContextHandle {
fn drop(&mut self) {
let mut contexts = self.manager.contexts.write();
contexts.remove(&self.id);
}
}

View file

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

View file

@ -1012,6 +1012,29 @@ impl Context for AppContext {
let entity = self.entities.read(handle);
read(entity, self)
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(View<T>, &AppContext) -> R,
) -> Result<R>
where
T: 'static,
{
let window = self
.windows
.get(window.id)
.ok_or_else(|| anyhow!("window not found"))?
.as_ref()
.unwrap();
let root_view = window.root_view.clone().unwrap();
let view = root_view
.downcast::<T>()
.map_err(|_| anyhow!("root view's type has changed"))?;
Ok(read(view, self))
}
}
/// These effects are processed at the end of each application update cycle.

View file

@ -66,6 +66,19 @@ impl Context for AsyncAppContext {
let mut lock = app.borrow_mut();
lock.update_window(window, f)
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(View<T>, &AppContext) -> R,
) -> Result<R>
where
T: 'static,
{
let app = self.app.upgrade().context("app was released")?;
let lock = app.borrow();
lock.read_window(window, read)
}
}
impl AsyncAppContext {
@ -250,6 +263,17 @@ impl Context for AsyncWindowContext {
{
self.app.read_model(handle, read)
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(View<T>, &AppContext) -> R,
) -> Result<R>
where
T: 'static,
{
self.app.read_window(window, read)
}
}
impl VisualContext for AsyncWindowContext {

View file

@ -1,6 +1,6 @@
use crate::{
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, Context, Effect, Entity, EntityId,
EventEmitter, Model, Subscription, Task, WeakModel, WindowContext,
EventEmitter, Model, Subscription, Task, View, WeakModel, WindowContext, WindowHandle,
};
use anyhow::Result;
use derive_more::{Deref, DerefMut};
@ -239,6 +239,17 @@ impl<'a, T> Context for ModelContext<'a, T> {
{
self.app.read_model(handle, read)
}
fn read_window<U, R>(
&self,
window: &WindowHandle<U>,
read: impl FnOnce(View<U>, &AppContext) -> R,
) -> Result<R>
where
U: 'static,
{
self.app.read_window(window, read)
}
}
impl<T> Borrow<AppContext> for ModelContext<'_, T> {

View file

@ -1,12 +1,12 @@
use crate::{
AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext,
Render, Result, Task, TestDispatcher, TestPlatform, ViewContext, VisualContext, WindowContext,
WindowHandle, WindowOptions,
div, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor,
Context, Div, EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model,
ModelContext, Render, Result, Task, TestDispatcher, TestPlatform, View, ViewContext,
VisualContext, WindowContext, WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt};
use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
#[derive(Clone)]
pub struct TestAppContext {
@ -58,6 +58,18 @@ impl Context for TestAppContext {
let app = self.app.borrow();
app.read_model(handle, read)
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(View<T>, &AppContext) -> R,
) -> Result<R>
where
T: 'static,
{
let app = self.app.borrow();
app.read_window(window, read)
}
}
impl TestAppContext {
@ -93,8 +105,8 @@ impl TestAppContext {
Ok(())
}
pub fn executor(&self) -> &BackgroundExecutor {
&self.background_executor
pub fn executor(&self) -> BackgroundExecutor {
self.background_executor.clone()
}
pub fn foreground_executor(&self) -> &ForegroundExecutor {
@ -120,6 +132,26 @@ impl TestAppContext {
cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
}
pub fn add_empty_window(&mut self) -> AnyWindowHandle {
let mut cx = self.app.borrow_mut();
cx.open_window(WindowOptions::default(), |cx| {
cx.build_view(|_| EmptyView {})
})
.any_handle
}
pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, VisualTestContext)
where
F: FnOnce(&mut ViewContext<V>) -> V,
V: Render,
{
let mut cx = self.app.borrow_mut();
let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
drop(cx);
let view = window.root_view(self).unwrap();
(view, VisualTestContext::from_window(*window.deref(), self))
}
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
where
Fut: Future<Output = R> + 'static,
@ -146,6 +178,11 @@ impl TestAppContext {
Some(read(lock.try_global()?, &lock))
}
pub fn set_global<G: 'static>(&mut self, global: G) {
let mut lock = self.app.borrow_mut();
lock.set_global(global);
}
pub fn update_global<G: 'static, R>(
&mut self,
update: impl FnOnce(&mut G, &mut AppContext) -> R,
@ -259,3 +296,191 @@ impl<T: Send> Model<T> {
.expect("model was dropped")
}
}
impl<V> View<V> {
pub fn condition<Evt>(
&self,
cx: &TestAppContext,
mut predicate: impl FnMut(&V, &AppContext) -> bool,
) -> impl Future<Output = ()>
where
Evt: 'static,
V: EventEmitter<Evt>,
{
use postage::prelude::{Sink as _, Stream as _};
let (tx, mut rx) = postage::mpsc::channel(1024);
let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
let mut cx = cx.app.borrow_mut();
let subscriptions = (
cx.observe(self, {
let mut tx = tx.clone();
move |_, _| {
tx.blocking_send(()).ok();
}
}),
cx.subscribe(self, {
let mut tx = tx.clone();
move |_, _: &Evt, _| {
tx.blocking_send(()).ok();
}
}),
);
let cx = cx.this.upgrade().unwrap();
let handle = self.downgrade();
async move {
crate::util::timeout(timeout_duration, async move {
loop {
{
let cx = cx.borrow();
let cx = &*cx;
if predicate(
handle
.upgrade()
.expect("view dropped with pending condition")
.read(cx),
cx,
) {
break;
}
}
// todo!(start_waiting)
// cx.borrow().foreground_executor().start_waiting();
rx.recv()
.await
.expect("view dropped with pending condition");
// cx.borrow().foreground_executor().finish_waiting();
}
})
.await
.expect("condition timed out");
drop(subscriptions);
}
}
}
use derive_more::{Deref, DerefMut};
#[derive(Deref, DerefMut)]
pub struct VisualTestContext<'a> {
#[deref]
#[deref_mut]
cx: &'a mut TestAppContext,
window: AnyWindowHandle,
}
impl<'a> VisualTestContext<'a> {
pub fn from_window(window: AnyWindowHandle, cx: &'a mut TestAppContext) -> Self {
Self { cx, window }
}
}
impl<'a> Context for VisualTestContext<'a> {
type Result<T> = <TestAppContext as Context>::Result<T>;
fn build_model<T: 'static>(
&mut self,
build_model: impl FnOnce(&mut ModelContext<'_, T>) -> T,
) -> Self::Result<Model<T>> {
self.cx.build_model(build_model)
}
fn update_model<T, R>(
&mut self,
handle: &Model<T>,
update: impl FnOnce(&mut T, &mut ModelContext<'_, T>) -> R,
) -> Self::Result<R>
where
T: 'static,
{
self.cx.update_model(handle, update)
}
fn read_model<T, R>(
&self,
handle: &Model<T>,
read: impl FnOnce(&T, &AppContext) -> R,
) -> Self::Result<R>
where
T: 'static,
{
self.cx.read_model(handle, read)
}
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{
self.cx.update_window(window, f)
}
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(View<T>, &AppContext) -> R,
) -> Result<R>
where
T: 'static,
{
self.cx.read_window(window, read)
}
}
impl<'a> VisualContext for VisualTestContext<'a> {
fn build_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: 'static + Render,
{
self.window
.update(self.cx, |_, cx| cx.build_view(build_view))
.unwrap()
}
fn update_view<V: 'static, R>(
&mut self,
view: &View<V>,
update: impl FnOnce(&mut V, &mut ViewContext<'_, V>) -> R,
) -> Self::Result<R> {
self.window
.update(self.cx, |_, cx| cx.update_view(view, update))
.unwrap()
}
fn replace_root_view<V>(
&mut self,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> Self::Result<View<V>>
where
V: Render,
{
self.window
.update(self.cx, |_, cx| cx.replace_root_view(build_view))
.unwrap()
}
}
impl AnyWindowHandle {
pub fn build_view<V: Render + 'static>(
&self,
cx: &mut TestAppContext,
build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
) -> View<V> {
self.update(cx, |_, cx| cx.build_view(build_view)).unwrap()
}
}
pub struct EmptyView {}
impl Render for EmptyView {
type Element = Div<Self>;
fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {
div()
}
}

View file

@ -167,7 +167,7 @@ impl TryFrom<&'_ str> for Rgba {
}
}
#[derive(Default, Copy, Clone, Debug, PartialEq)]
#[derive(Default, Copy, Clone, Debug)]
#[repr(C)]
pub struct Hsla {
pub h: f32,
@ -176,10 +176,63 @@ pub struct Hsla {
pub a: f32,
}
impl PartialEq for Hsla {
fn eq(&self, other: &Self) -> bool {
self.h
.total_cmp(&other.h)
.then(self.s.total_cmp(&other.s))
.then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
.is_eq()
}
}
impl PartialOrd for Hsla {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// SAFETY: The total ordering relies on this always being Some()
Some(
self.h
.total_cmp(&other.h)
.then(self.s.total_cmp(&other.s))
.then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
)
}
}
impl Ord for Hsla {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// SAFETY: The partial comparison is a total comparison
unsafe { self.partial_cmp(other).unwrap_unchecked() }
}
}
impl Hsla {
pub fn to_rgb(self) -> Rgba {
self.into()
}
pub fn red() -> Self {
red()
}
pub fn green() -> Self {
green()
}
pub fn blue() -> Self {
blue()
}
pub fn black() -> Self {
black()
}
pub fn white() -> Self {
white()
}
pub fn transparent_black() -> Self {
transparent_black()
}
}
impl Eq for Hsla {}

View file

@ -81,6 +81,7 @@ impl<V: 'static> Element<V> for Text<V> {
let text = self.text.clone();
let rem_size = cx.rem_size();
let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
let element_state = element_state.clone();
move |known_dimensions, _| {
@ -93,6 +94,10 @@ impl<V: 'static> Element<V> for Text<V> {
)
.log_err()
else {
element_state.lock().replace(TextElementState {
lines: Default::default(),
line_height,
});
return Size::default();
};
@ -131,7 +136,8 @@ impl<V: 'static> Element<V> for Text<V> {
let element_state = element_state.lock();
let element_state = element_state
.as_ref()
.expect("measurement has not been performed");
.ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))
.unwrap();
let line_height = element_state.line_height;
let mut line_origin = bounds.origin;

View file

@ -105,6 +105,14 @@ pub trait Context {
fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Result<T>
where
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T;
fn read_window<T, R>(
&self,
window: &WindowHandle<T>,
read: impl FnOnce(View<T>, &AppContext) -> R,
) -> Result<R>
where
T: 'static;
}
pub trait VisualContext: Context {

View file

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

View file

@ -1,7 +1,7 @@
use crate::{
build_action_from_type, Action, Bounds, DispatchPhase, Element, FocusEvent, FocusHandle,
FocusId, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent, Pixels,
Style, StyleRefinement, ViewContext, WindowContext,
FocusId, KeyBinding, KeyContext, KeyMatch, Keymap, Keystroke, KeystrokeMatcher, MouseDownEvent,
Pixels, Style, StyleRefinement, ViewContext, WindowContext,
};
use collections::HashMap;
use parking_lot::Mutex;
@ -145,6 +145,15 @@ impl DispatchTree {
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(
&mut self,
keystroke: &Keystroke,

View file

@ -3,9 +3,19 @@ use anyhow::Result;
use smallvec::SmallVec;
pub struct KeyBinding {
action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
pub(super) context_predicate: Option<KeyBindingContextPredicate>,
pub(crate) action: Box<dyn Action>,
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
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 {

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,13 @@
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/mmastrac/rust-ctor/issues/280
pub fn unused() {}
pub fn init() {}
actions!(
Cancel,

View file

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

View file

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

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) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/the-root",
json!({
@ -189,7 +189,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/the-root",
json!({
@ -547,7 +547,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/the-root",
json!({
@ -734,7 +734,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -826,7 +826,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/root",
json!({
@ -914,7 +914,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -1046,7 +1046,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@ -1125,7 +1125,7 @@ async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAp
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@ -1215,7 +1215,7 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "" })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@ -1279,7 +1279,7 @@ async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "", "b.js": "" }))
.await;
@ -1401,7 +1401,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
"
.unindent();
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@ -1671,7 +1671,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
"let three = 3;\n",
);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": text })).await;
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
@ -1734,7 +1734,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
.await;
@ -1813,7 +1813,7 @@ async fn test_edits_from_lsp2_with_past_version(cx: &mut gpui::TestAppContext) {
"
.unindent();
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -1959,7 +1959,7 @@ async fn test_edits_from_lsp2_with_edits_on_adjacent_lines(cx: &mut gpui::TestAp
"
.unindent();
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2067,7 +2067,7 @@ async fn test_invalid_edits_from_lsp2(cx: &mut gpui::TestAppContext) {
"
.unindent();
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2187,7 +2187,7 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
);
let mut fake_servers = language.set_fake_lsp_adapter(Default::default()).await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2299,7 +2299,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2396,7 +2396,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2451,7 +2451,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
);
let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2559,7 +2559,7 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
async fn test_save_file(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2591,7 +2591,7 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) {
async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2622,7 +2622,7 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
async fn test_save_as(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree("/dir", json!({})).await;
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
@ -2830,7 +2830,7 @@ async fn test_rescan_and_remote_updates(cx: &mut gpui::TestAppContext) {
async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2881,7 +2881,7 @@ async fn test_buffer_identity_across_renames(cx: &mut gpui::TestAppContext) {
async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -2927,7 +2927,7 @@ async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -3074,7 +3074,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
init_test(cx);
let initial_contents = "aaa\nbbbbb\nc\n";
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -3154,7 +3154,7 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -3216,7 +3216,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/the-dir",
json!({
@ -3479,7 +3479,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
}))
.await;
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -3596,7 +3596,7 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
async fn test_search(cx: &mut gpui::TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -3655,7 +3655,7 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
let search_query = "file";
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -3767,7 +3767,7 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
let search_query = "file";
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({
@ -3878,7 +3878,7 @@ async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContex
let search_query = "file";
let fs = FakeFs::new(cx.executor().clone());
let fs = FakeFs::new(cx.executor());
fs.insert_tree(
"/dir",
json!({

View file

@ -577,18 +577,18 @@ mod tests {
let client2 = Peer::new(0);
let (client1_to_server_conn, server_to_client_1_conn, _kill) =
Connection::in_memory(cx.executor().clone());
Connection::in_memory(cx.executor());
let (client1_conn_id, io_task1, client1_incoming) =
client1.add_test_connection(client1_to_server_conn, cx.executor().clone());
client1.add_test_connection(client1_to_server_conn, cx.executor());
let (_, io_task2, server_incoming1) =
server.add_test_connection(server_to_client_1_conn, cx.executor().clone());
server.add_test_connection(server_to_client_1_conn, cx.executor());
let (client2_to_server_conn, server_to_client_2_conn, _kill) =
Connection::in_memory(cx.executor().clone());
Connection::in_memory(cx.executor());
let (client2_conn_id, io_task3, client2_incoming) =
client2.add_test_connection(client2_to_server_conn, cx.executor().clone());
client2.add_test_connection(client2_to_server_conn, cx.executor());
let (_, io_task4, server_incoming2) =
server.add_test_connection(server_to_client_2_conn, cx.executor().clone());
server.add_test_connection(server_to_client_2_conn, cx.executor());
executor.spawn(io_task1).detach();
executor.spawn(io_task2).detach();
@ -763,16 +763,16 @@ mod tests {
#[gpui::test(iterations = 50)]
async fn test_dropping_request_before_completion(cx: &mut TestAppContext) {
let executor = cx.executor().clone();
let executor = cx.executor();
let server = Peer::new(0);
let client = Peer::new(0);
let (client_to_server_conn, server_to_client_conn, _kill) =
Connection::in_memory(cx.executor().clone());
Connection::in_memory(cx.executor());
let (client_to_server_conn_id, io_task1, mut client_incoming) =
client.add_test_connection(client_to_server_conn, cx.executor().clone());
client.add_test_connection(client_to_server_conn, cx.executor());
let (server_to_client_conn_id, io_task2, mut server_incoming) =
server.add_test_connection(server_to_client_conn, cx.executor().clone());
server.add_test_connection(server_to_client_conn, cx.executor());
executor.spawn(io_task1).detach();
executor.spawn(io_task2).detach();

View file

@ -48,7 +48,7 @@ fn main() {
let args = Args::parse();
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);
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::syntax::SyntaxTheme;
use crate::{ColorScale, PlayerColor, PlayerColors};
use crate::ColorScale;
use crate::{SystemColors, ThemeColors};
impl Default for PlayerColors {
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 {
pub(crate) fn neutral() -> ColorScaleSet {
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 {
pub fn default_light() -> Self {
pub fn light() -> Self {
let system = SystemColors::default();
Self {
@ -327,7 +81,7 @@ impl ThemeColors {
}
}
pub fn default_dark() -> Self {
pub fn dark() -> Self {
let system = SystemColors::default();
Self {
@ -470,7 +224,7 @@ pub fn default_color_scales() -> ColorScales {
}
}
fn gray() -> ColorScaleSet {
pub(crate) fn gray() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Gray",
light: [
@ -534,7 +288,7 @@ fn gray() -> ColorScaleSet {
.unwrap()
}
fn mauve() -> ColorScaleSet {
pub(crate) fn mauve() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Mauve",
light: [
@ -598,7 +352,7 @@ fn mauve() -> ColorScaleSet {
.unwrap()
}
fn slate() -> ColorScaleSet {
pub(crate) fn slate() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Slate",
light: [
@ -662,7 +416,7 @@ fn slate() -> ColorScaleSet {
.unwrap()
}
fn sage() -> ColorScaleSet {
pub(crate) fn sage() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sage",
light: [
@ -726,7 +480,7 @@ fn sage() -> ColorScaleSet {
.unwrap()
}
fn olive() -> ColorScaleSet {
pub(crate) fn olive() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Olive",
light: [
@ -790,7 +544,7 @@ fn olive() -> ColorScaleSet {
.unwrap()
}
fn sand() -> ColorScaleSet {
pub(crate) fn sand() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sand",
light: [
@ -854,7 +608,7 @@ fn sand() -> ColorScaleSet {
.unwrap()
}
fn gold() -> ColorScaleSet {
pub(crate) fn gold() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Gold",
light: [
@ -918,7 +672,7 @@ fn gold() -> ColorScaleSet {
.unwrap()
}
fn bronze() -> ColorScaleSet {
pub(crate) fn bronze() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Bronze",
light: [
@ -982,7 +736,7 @@ fn bronze() -> ColorScaleSet {
.unwrap()
}
fn brown() -> ColorScaleSet {
pub(crate) fn brown() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Brown",
light: [
@ -1046,7 +800,7 @@ fn brown() -> ColorScaleSet {
.unwrap()
}
fn yellow() -> ColorScaleSet {
pub(crate) fn yellow() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Yellow",
light: [
@ -1110,7 +864,7 @@ fn yellow() -> ColorScaleSet {
.unwrap()
}
fn amber() -> ColorScaleSet {
pub(crate) fn amber() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Amber",
light: [
@ -1174,7 +928,7 @@ fn amber() -> ColorScaleSet {
.unwrap()
}
fn orange() -> ColorScaleSet {
pub(crate) fn orange() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Orange",
light: [
@ -1238,7 +992,7 @@ fn orange() -> ColorScaleSet {
.unwrap()
}
fn tomato() -> ColorScaleSet {
pub(crate) fn tomato() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Tomato",
light: [
@ -1302,7 +1056,7 @@ fn tomato() -> ColorScaleSet {
.unwrap()
}
fn red() -> ColorScaleSet {
pub(crate) fn red() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Red",
light: [
@ -1366,7 +1120,7 @@ fn red() -> ColorScaleSet {
.unwrap()
}
fn ruby() -> ColorScaleSet {
pub(crate) fn ruby() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Ruby",
light: [
@ -1430,7 +1184,7 @@ fn ruby() -> ColorScaleSet {
.unwrap()
}
fn crimson() -> ColorScaleSet {
pub(crate) fn crimson() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Crimson",
light: [
@ -1494,7 +1248,7 @@ fn crimson() -> ColorScaleSet {
.unwrap()
}
fn pink() -> ColorScaleSet {
pub(crate) fn pink() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Pink",
light: [
@ -1558,7 +1312,7 @@ fn pink() -> ColorScaleSet {
.unwrap()
}
fn plum() -> ColorScaleSet {
pub(crate) fn plum() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Plum",
light: [
@ -1622,7 +1376,7 @@ fn plum() -> ColorScaleSet {
.unwrap()
}
fn purple() -> ColorScaleSet {
pub(crate) fn purple() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Purple",
light: [
@ -1686,7 +1440,7 @@ fn purple() -> ColorScaleSet {
.unwrap()
}
fn violet() -> ColorScaleSet {
pub(crate) fn violet() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Violet",
light: [
@ -1750,7 +1504,7 @@ fn violet() -> ColorScaleSet {
.unwrap()
}
fn iris() -> ColorScaleSet {
pub(crate) fn iris() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Iris",
light: [
@ -1814,7 +1568,7 @@ fn iris() -> ColorScaleSet {
.unwrap()
}
fn indigo() -> ColorScaleSet {
pub(crate) fn indigo() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Indigo",
light: [
@ -1878,7 +1632,7 @@ fn indigo() -> ColorScaleSet {
.unwrap()
}
fn blue() -> ColorScaleSet {
pub(crate) fn blue() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Blue",
light: [
@ -1942,7 +1696,7 @@ fn blue() -> ColorScaleSet {
.unwrap()
}
fn cyan() -> ColorScaleSet {
pub(crate) fn cyan() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Cyan",
light: [
@ -2006,7 +1760,7 @@ fn cyan() -> ColorScaleSet {
.unwrap()
}
fn teal() -> ColorScaleSet {
pub(crate) fn teal() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Teal",
light: [
@ -2070,7 +1824,7 @@ fn teal() -> ColorScaleSet {
.unwrap()
}
fn jade() -> ColorScaleSet {
pub(crate) fn jade() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Jade",
light: [
@ -2134,7 +1888,7 @@ fn jade() -> ColorScaleSet {
.unwrap()
}
fn green() -> ColorScaleSet {
pub(crate) fn green() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Green",
light: [
@ -2198,7 +1952,7 @@ fn green() -> ColorScaleSet {
.unwrap()
}
fn grass() -> ColorScaleSet {
pub(crate) fn grass() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Grass",
light: [
@ -2262,7 +2016,7 @@ fn grass() -> ColorScaleSet {
.unwrap()
}
fn lime() -> ColorScaleSet {
pub(crate) fn lime() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Lime",
light: [
@ -2326,7 +2080,7 @@ fn lime() -> ColorScaleSet {
.unwrap()
}
fn mint() -> ColorScaleSet {
pub(crate) fn mint() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Mint",
light: [
@ -2390,7 +2144,7 @@ fn mint() -> ColorScaleSet {
.unwrap()
}
fn sky() -> ColorScaleSet {
pub(crate) fn sky() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Sky",
light: [
@ -2454,7 +2208,7 @@ fn sky() -> ColorScaleSet {
.unwrap()
}
fn black() -> ColorScaleSet {
pub(crate) fn black() -> ColorScaleSet {
StaticColorScaleSet {
scale: "Black",
light: [
@ -2518,7 +2272,7 @@ fn black() -> ColorScaleSet {
.unwrap()
}
fn white() -> ColorScaleSet {
pub(crate) fn white() -> ColorScaleSet {
StaticColorScaleSet {
scale: "White",
light: [

View file

@ -1,58 +1,56 @@
use std::sync::Arc;
use crate::{
colors::{StatusColors, SystemColors, ThemeColors, ThemeStyles},
default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
one_themes::{one_dark, one_family},
Theme, ThemeFamily,
};
fn zed_pro_daylight() -> Theme {
Theme {
id: "zed_pro_daylight".to_string(),
name: "Zed Pro Daylight".into(),
appearance: Appearance::Light,
styles: ThemeStyles {
system: SystemColors::default(),
colors: ThemeColors::default_light(),
status: StatusColors::default(),
player: PlayerColors::default_light(),
syntax: Arc::new(SyntaxTheme::default_light()),
},
}
}
// fn zed_pro_daylight() -> Theme {
// Theme {
// id: "zed_pro_daylight".to_string(),
// name: "Zed Pro Daylight".into(),
// appearance: Appearance::Light,
// styles: ThemeStyles {
// system: SystemColors::default(),
// colors: ThemeColors::light(),
// status: StatusColors::light(),
// player: PlayerColors::light(),
// syntax: Arc::new(SyntaxTheme::light()),
// },
// }
// }
pub(crate) fn zed_pro_moonlight() -> Theme {
Theme {
id: "zed_pro_moonlight".to_string(),
name: "Zed Pro Moonlight".into(),
appearance: Appearance::Dark,
styles: ThemeStyles {
system: SystemColors::default(),
colors: ThemeColors::default_dark(),
status: StatusColors::default(),
player: PlayerColors::default(),
syntax: Arc::new(SyntaxTheme::default_dark()),
},
}
}
// pub(crate) fn zed_pro_moonlight() -> Theme {
// Theme {
// id: "zed_pro_moonlight".to_string(),
// name: "Zed Pro Moonlight".into(),
// appearance: Appearance::Dark,
// styles: ThemeStyles {
// system: SystemColors::default(),
// colors: ThemeColors::dark(),
// status: StatusColors::dark(),
// player: PlayerColors::dark(),
// syntax: Arc::new(SyntaxTheme::dark()),
// },
// }
// }
pub fn zed_pro_family() -> ThemeFamily {
ThemeFamily {
id: "zed_pro".to_string(),
name: "Zed Pro".into(),
author: "Zed Team".into(),
themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
scales: default_color_scales(),
}
}
// pub fn zed_pro_family() -> ThemeFamily {
// ThemeFamily {
// id: "zed_pro".to_string(),
// name: "Zed Pro".into(),
// author: "Zed Team".into(),
// themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
// scales: default_color_scales(),
// }
// }
impl Default for ThemeFamily {
fn default() -> Self {
zed_pro_family()
one_family()
}
}
impl Default for Theme {
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 crate::{
zed_pro_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme,
ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
one_themes::one_family, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors,
Theme, ThemeColors, ThemeFamily, ThemeStyles, UserTheme, UserThemeFamily,
};
pub struct ThemeRegistry {
@ -38,17 +38,17 @@ impl ThemeRegistry {
fn insert_user_themes(&mut self, themes: impl IntoIterator<Item = UserTheme>) {
self.insert_themes(themes.into_iter().map(|user_theme| {
let mut theme_colors = match user_theme.appearance {
Appearance::Light => ThemeColors::default_light(),
Appearance::Dark => ThemeColors::default_dark(),
Appearance::Light => ThemeColors::light(),
Appearance::Dark => ThemeColors::dark(),
};
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);
let mut syntax_colors = match user_theme.appearance {
Appearance::Light => SyntaxTheme::default_light(),
Appearance::Dark => SyntaxTheme::default_dark(),
Appearance::Light => SyntaxTheme::light(),
Appearance::Dark => SyntaxTheme::dark(),
};
if let Some(user_syntax) = user_theme.styles.syntax {
syntax_colors.highlights = user_syntax
@ -76,7 +76,10 @@ impl ThemeRegistry {
system: SystemColors::default(),
colors: theme_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),
},
}
@ -105,7 +108,7 @@ impl Default for ThemeRegistry {
themes: HashMap::default(),
};
this.insert_theme_families([zed_pro_family()]);
this.insert_theme_families([one_family()]);
#[cfg(not(feature = "importing-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 anyhow::Result;
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(),
active_theme: themes
.get(defaults.theme.as_ref().unwrap())
.or(themes.get("Zed Pro Moonlight"))
.or(themes.get(&one_dark().name))
.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 refineable::Refineable;
use std::sync::Arc;
#[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,
}
#[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,
}
use crate::{PlayerColors, StatusColors, SyntaxTheme, SystemColors};
#[derive(Refineable, Clone, Debug)]
#[refineable(Debug, serde::Deserialize)]
@ -259,7 +236,7 @@ mod tests {
#[test]
fn override_a_single_theme_color() {
let mut colors = ThemeColors::default_light();
let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff);
@ -277,7 +254,7 @@ mod tests {
#[test]
fn override_multiple_theme_colors() {
let mut colors = ThemeColors::default_light();
let mut colors = ThemeColors::light();
let magenta: Hsla = gpui::rgb(0xff00ff);
let green: Hsla = gpui::rgb(0x00ff00);

View file

@ -16,6 +16,107 @@ pub struct PlayerColor {
#[derive(Clone)]
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 {
pub fn local(&self) -> PlayerColor {
// todo!("use a valid color");
@ -36,6 +137,8 @@ impl PlayerColors {
#[cfg(feature = "stories")]
pub use stories::*;
use crate::{amber, blue, jade, lime, orange, pink, purple, red};
#[cfg(feature = "stories")]
mod stories {
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_theme;
mod players;
mod one_themes;
mod registry;
mod scale;
mod settings;
mod syntax;
mod styles;
#[cfg(not(feature = "importing-themes"))]
mod themes;
mod user_theme;
@ -13,14 +12,12 @@ mod user_theme;
use std::sync::Arc;
use ::settings::Settings;
pub use colors::*;
pub use default_colors::*;
pub use default_theme::*;
pub use players::*;
pub use registry::*;
pub use scale::*;
pub use settings::*;
pub use syntax::*;
pub use styles::*;
#[cfg(not(feature = "importing-themes"))]
pub use themes::*;
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 strum::{EnumIter, IntoEnumIterator};
use gpui::Action;
use strum::EnumIter;
use crate::prelude::*;
#[derive(Component)]
pub struct Keybinding {
pub struct KeyBinding {
/// A keybinding consists of a key and a set of modifier keys.
/// More then one keybinding produces a chord.
///
/// This should always contain at least one element.
keybinding: Vec<(String, ModifierKeys)>,
key_binding: gpui::KeyBinding,
}
impl Keybinding {
pub fn new(key: String, modifiers: ModifierKeys) -> Self {
Self {
keybinding: vec![(key, modifiers)],
}
impl KeyBinding {
pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
// todo! this last is arbitrary, we want to prefer users key bindings over defaults,
// 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(
first_note: (String, ModifierKeys),
second_note: (String, ModifierKeys),
) -> Self {
Self {
keybinding: vec![first_note, second_note],
}
pub fn new(key_binding: gpui::KeyBinding) -> Self {
Self { key_binding }
}
fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
div()
.flex()
.gap_2()
.children(self.keybinding.iter().map(|(key, modifiers)| {
.children(self.key_binding.keystrokes().iter().map(|keystroke| {
div()
.flex()
.gap_1()
.children(ModifierKey::iter().filter_map(|modifier| {
if modifiers.0.contains(&modifier) {
Some(Key::new(modifier.glyph().to_string()))
} else {
None
}
}))
.child(Key::new(key.clone()))
.when(keystroke.modifiers.control, |el| el.child(Key::new("^")))
.when(keystroke.modifiers.alt, |el| el.child(Key::new("")))
.when(keystroke.modifiers.command, |el| el.child(Key::new("")))
.when(keystroke.modifiers.shift, |el| el.child(Key::new("")))
.child(Key::new(keystroke.key.clone()))
}))
}
}
@ -81,76 +73,6 @@ pub enum ModifierKey {
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")]
pub use stories::*;
@ -158,29 +80,38 @@ pub use stories::*;
mod stories {
use super::*;
use crate::Story;
use gpui::{Div, Render};
use gpui::{action, Div, Render};
use itertools::Itertools;
pub struct KeybindingStory;
#[action]
struct NoAction {}
pub fn binding(key: &str) -> gpui::KeyBinding {
gpui::KeyBinding::new(key, NoAction {}, None)
}
impl Render for KeybindingStory {
type Element = Div<Self>;
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)
.child(Story::title_for::<_, Keybinding>(cx))
.child(Story::title_for::<_, KeyBinding>(cx))
.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(
div()
.flex()
.gap_3()
.children(ModifierKey::iter().map(|modifier| {
Keybinding::new("C".to_string(), ModifierKeys::new().add(modifier))
})),
.child(KeyBinding::new(binding("ctrl-c")))
.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(
@ -194,29 +125,17 @@ mod stories {
.gap_4()
.py_3()
.children(chunk.map(|permutation| {
let mut modifiers = ModifierKeys::new();
for modifier in permutation {
modifiers = modifiers.add(modifier);
}
Keybinding::new("X".to_string(), modifiers)
KeyBinding::new(binding(&*(permutation.join("-") + "-x")))
}))
}),
),
)
.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(Keybinding::new_chord(
("A".to_string(), ModifierKeys::new()),
("Z".to_string(), ModifierKeys::new()),
))
.child(KeyBinding::new(binding("a z")))
.child(Story::label(cx, "Chord with Modifier"))
.child(Keybinding::new_chord(
("A".to_string(), ModifierKeys::new().control(true)),
("Z".to_string(), ModifierKeys::new().shift(true)),
))
.child(KeyBinding::new(binding("ctrl-a shift-z")))
}
}
}

View file

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

View file

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

View file

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

View file

@ -1401,20 +1401,32 @@ impl Pane {
// .on_drop(|_view, state: View<DraggedTab>, cx| {
// eprintln!("{:?}", state.read(cx));
// })
.px_2()
.py_0p5()
.flex()
.items_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)
.hover(|h| h.bg(tab_hover_bg))
.active(|a| a.bg(tab_active_bg))
.border_color(cx.theme().colors().border)
.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(
div()
.px_1()
.flex()
.items_center()
.gap_1p5()
.gap_1()
.text_color(text_color)
.children(if item.has_conflict(cx) {
Some(

View file

@ -1130,15 +1130,16 @@ impl Workspace {
// }))
// }
// pub fn prepare_to_close(
// &mut self,
// quitting: bool,
// cx: &mut ViewContext<Self>,
// ) -> Task<Result<bool>> {
pub fn prepare_to_close(
&mut self,
quitting: bool,
cx: &mut ViewContext<Self>,
) -> Task<Result<bool>> {
//todo!(saveing)
// let active_call = self.active_call().cloned();
// let window = cx.window();
// cx.spawn(|this, mut cx| async move {
cx.spawn(|this, mut cx| async move {
// let workspace_count = cx
// .windows()
// .into_iter()
@ -1170,13 +1171,15 @@ impl Workspace {
// }
// }
// Ok(this
Ok(
false, // this
// .update(&mut cx, |this, cx| {
// this.save_all_internal(SaveIntent::Close, cx)
// })?
// .await?)
// })
// }
// .await?
)
})
}
// fn save_all(
// &mut self,
@ -4062,24 +4065,24 @@ impl ViewId {
}
}
// pub trait WorkspaceHandle {
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
// }
pub trait WorkspaceHandle {
fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
}
// impl WorkspaceHandle for View<Workspace> {
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
// self.read(cx)
// .worktrees(cx)
// .flat_map(|worktree| {
// let worktree_id = worktree.read(cx).id();
// worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
// worktree_id,
// path: f.path.clone(),
// })
// })
// .collect::<Vec<_>>()
// }
// }
impl WorkspaceHandle for View<Workspace> {
fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
self.read(cx)
.worktrees(cx)
.flat_map(|worktree| {
let worktree_id = worktree.read(cx).id();
worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
worktree_id,
path: f.path.clone(),
})
})
.collect::<Vec<_>>()
}
}
// impl std::fmt::Debug for OpenPaths {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {

View file

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

View file

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