diff --git a/Cargo.lock b/Cargo.lock index dbdf7f5774..85599036a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4277,6 +4277,7 @@ dependencies = [ "async-tar", "futures 0.3.28", "gpui", + "log", "parking_lot 0.11.2", "serde", "serde_derive", diff --git a/assets/settings/default.json b/assets/settings/default.json index c69d8089bc..c413db5788 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -73,6 +73,16 @@ // Whether to show git diff indicators in the scrollbar. "git_diff": true }, + // Inlay hint related settings + "inlay_hints": { + // Global switch to toggle hints on and off, switched off by default. + "enabled": false, + // Toggle certain types of hints on and off, all switched on by default. + "show_type_hints": true, + "show_parameter_hints": true, + // Corresponds to null/None LSP hint type value. + "show_other_hints": true + }, "project_panel": { // Whether to show the git status in the project panel. "git_status": true, diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index f795d7321c..8b46d7cfc5 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -207,16 +207,11 @@ impl ActivityIndicator { let mut checking_for_update = SmallVec::<[_; 3]>::new(); let mut failed = SmallVec::<[_; 3]>::new(); for status in &self.statuses { + let name = status.name.clone(); match status.status { - LanguageServerBinaryStatus::CheckingForUpdate => { - checking_for_update.push(status.name.clone()); - } - LanguageServerBinaryStatus::Downloading => { - downloading.push(status.name.clone()); - } - LanguageServerBinaryStatus::Failed { .. } => { - failed.push(status.name.clone()); - } + LanguageServerBinaryStatus::CheckingForUpdate => checking_for_update.push(name), + LanguageServerBinaryStatus::Downloading => downloading.push(name), + LanguageServerBinaryStatus::Failed { .. } => failed.push(name), LanguageServerBinaryStatus::Downloaded | LanguageServerBinaryStatus::Cached => {} } } diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index a5be6e7d62..14d785307d 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -201,6 +201,7 @@ impl Server { .add_message_handler(update_language_server) .add_message_handler(update_diagnostic_summary) .add_message_handler(update_worktree_settings) + .add_message_handler(refresh_inlay_hints) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) @@ -226,6 +227,7 @@ impl Server { .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) + .add_request_handler(forward_project_request::) .add_message_handler(create_buffer_for_peer) .add_request_handler(update_buffer) .add_message_handler(update_buffer_file) @@ -1574,6 +1576,10 @@ async fn update_worktree_settings( Ok(()) } +async fn refresh_inlay_hints(request: proto::RefreshInlayHints, session: Session) -> Result<()> { + broadcast_project_message(request.project_id, request, session).await +} + async fn start_language_server( request: proto::StartLanguageServer, session: Session, @@ -1750,7 +1756,15 @@ async fn buffer_reloaded(request: proto::BufferReloaded, session: Session) -> Re } async fn buffer_saved(request: proto::BufferSaved, session: Session) -> Result<()> { - let project_id = ProjectId::from_proto(request.project_id); + broadcast_project_message(request.project_id, request, session).await +} + +async fn broadcast_project_message( + project_id: u64, + request: T, + session: Session, +) -> Result<()> { + let project_id = ProjectId::from_proto(project_id); let project_connection_ids = session .db() .await diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 3ddef94104..b20844a065 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -18,7 +18,7 @@ use gpui::{ }; use indoc::indoc; use language::{ - language_settings::{AllLanguageSettings, Formatter}, + language_settings::{AllLanguageSettings, Formatter, InlayHintKind, InlayHintSettings}, tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language, LanguageConfig, OffsetRangeExt, Point, Rope, }; @@ -34,7 +34,7 @@ use std::{ path::{Path, PathBuf}, rc::Rc, sync::{ - atomic::{AtomicBool, Ordering::SeqCst}, + atomic::{AtomicBool, AtomicU32, Ordering::SeqCst}, Arc, }, }; @@ -7800,6 +7800,572 @@ async fn test_on_input_format_from_guest_to_host( }); } +#[gpui::test] +async fn test_mutual_editor_inlay_hint_cache_update( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + let mut server = TestServer::start(&deterministic).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::(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::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, + }) + }); + }); + }); + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + + 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 inlays 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); + cx_a.foreground().start_waiting(); + + 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 next_call_id = Arc::new(AtomicU32::new(0)); + let editor_a = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + fake_language_server + .handle_request::(move |params, _| { + let task_next_call_id = Arc::clone(&next_call_id); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + let mut current_call_id = Arc::clone(&task_next_call_id).fetch_add(1, SeqCst); + let mut new_hints = Vec::with_capacity(current_call_id as usize); + loop { + new_hints.push(lsp::InlayHint { + position: lsp::Position::new(0, current_call_id), + label: lsp::InlayHintLabel::String(current_call_id.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + if current_call_id == 0 { + break; + } + current_call_id -= 1; + } + Ok(Some(new_hints)) + } + }) + .next() + .await + .unwrap(); + + cx_a.foreground().finish_waiting(); + cx_a.foreground().run_until_parked(); + + let mut edits_made = 1; + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec!["0".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.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Host editor update the cache version after every cache/view change", + ); + }); + let workspace_b = client_b.build_workspace(&project_b, 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::() + .unwrap(); + + cx_b.foreground().run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0".to_string(), "1".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.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Guest editor update the cache version after every cache/view change" + ); + }); + + 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); + edits_made += 1; + }); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec!["0".to_string(), "1".to_string(), "2".to_string()], + extract_hint_labels(editor), + "Host should get hints from the 1st edit and 1st LSP query" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!(inlay_cache.version, edits_made); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string() + ], + extract_hint_labels(editor), + "Guest should get hints the 1st edit and 2nd LSP query" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!(inlay_cache.version, edits_made); + }); + + 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); + edits_made += 1; + }); + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string(), + "4".to_string() + ], + extract_hint_labels(editor), + "Host should get hints from 3rd edit, 5th LSP query: \ +4th query was made by guest (but not applied) due to cache invalidation logic" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!(inlay_cache.version, edits_made); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string(), + "4".to_string(), + "5".to_string(), + ], + extract_hint_labels(editor), + "Guest should get hints from 3rd edit, 6th LSP query" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!(inlay_cache.version, edits_made); + }); + + fake_language_server + .request::(()) + .await + .expect("inlay refresh request failed"); + edits_made += 1; + cx_a.foreground().run_until_parked(); + cx_b.foreground().run_until_parked(); + editor_a.update(cx_a, |editor, _| { + assert_eq!( + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string(), + "4".to_string(), + "5".to_string(), + "6".to_string(), + ], + extract_hint_labels(editor), + "Host should react to /refresh LSP request and get new hints from 7th LSP query" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Host should accepted all edits and bump its cache version every time" + ); + }); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec![ + "0".to_string(), + "1".to_string(), + "2".to_string(), + "3".to_string(), + "4".to_string(), + "5".to_string(), + "6".to_string(), + "7".to_string(), + ], + extract_hint_labels(editor), + "Guest should get a /refresh LSP request propagated by host and get new hints from 8th LSP query" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!( + inlay_cache.version, + edits_made, + "Guest should accepted all edits and bump its cache version every time" + ); + }); +} + +#[gpui::test] +async fn test_inlay_hint_refresh_is_forwarded( + deterministic: Arc, + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, +) { + deterministic.forbid_parking(); + let mut server = TestServer::start(&deterministic).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::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: false, + 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::(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: true, + show_parameter_hints: false, + show_other_hints: true, + }) + }); + }); + }); + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + + 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 inlays 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); + let workspace_b = client_b.build_workspace(&project_b, 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::() + .unwrap(); + + let editor_b = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + let fake_language_server = fake_language_servers.next().await.unwrap(); + let next_call_id = Arc::new(AtomicU32::new(0)); + fake_language_server + .handle_request::(move |params, _| { + let task_next_call_id = Arc::clone(&next_call_id); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + let mut current_call_id = Arc::clone(&task_next_call_id).fetch_add(1, SeqCst); + let mut new_hints = Vec::with_capacity(current_call_id as usize); + loop { + new_hints.push(lsp::InlayHint { + position: lsp::Position::new(0, current_call_id), + label: lsp::InlayHintLabel::String(current_call_id.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + if current_call_id == 0 { + break; + } + current_call_id -= 1; + } + Ok(Some(new_hints)) + } + }) + .next() + .await + .unwrap(); + cx_a.foreground().finish_waiting(); + cx_b.foreground().finish_waiting(); + + cx_a.foreground().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.allowed_hint_kinds, allowed_hint_kinds, + "Host should have allowed hint kinds set despite hints are off" + ); + assert_eq!( + inlay_cache.version, 0, + "Host should not increment its cache version due to no changes", + ); + }); + + let mut edits_made = 1; + cx_b.foreground().run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0".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.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Guest editor update the cache version after every cache/view change" + ); + }); + + fake_language_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx_a.foreground().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.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 0, + "Host should not increment its cache version due to no changes", + ); + }); + + edits_made += 1; + cx_b.foreground().run_until_parked(); + editor_b.update(cx_b, |editor, _| { + assert_eq!( + vec!["0".to_string(), "1".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.allowed_hint_kinds, allowed_hint_kinds, + "Inlay kinds settings never change during the test" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Guest should accepted all edits and bump its cache version every time" + ); + }); +} + #[derive(Debug, Eq, PartialEq)] struct RoomParticipants { remote: Vec, @@ -7823,3 +8389,17 @@ fn room_participants(room: &ModelHandle, cx: &mut TestAppContext) -> RoomP RoomParticipants { remote, pending } }) } + +fn extract_hint_labels(editor: &Editor) -> Vec { + let mut labels = Vec::new(); + for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { + let excerpt_hints = excerpt_hints.read(); + for (_, inlay) in excerpt_hints.hints.iter() { + match &inlay.label { + project::InlayHintLabel::String(s) => labels.push(s.to_string()), + _ => unreachable!(), + } + } + } + labels +} diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 2ab8928166..5caebb9f0c 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -317,7 +317,7 @@ impl CollabTitlebarItem { ), ] }; - user_menu.show(Default::default(), AnchorCorner::TopRight, items, cx); + user_menu.toggle(Default::default(), AnchorCorner::TopRight, items, cx); }); } @@ -683,6 +683,9 @@ impl CollabTitlebarItem { .into_any() }) .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, move |_, this, cx| { + this.user_menu.update(cx, |menu, _| menu.delay_cancel()); + }) .on_click(MouseButton::Left, move |_, this, cx| { this.toggle_user_menu(&Default::default(), cx) }) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index de78b51e9c..296f6bc04a 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -124,6 +124,7 @@ pub struct ContextMenu { items: Vec, selected_index: Option, visible: bool, + delay_cancel: bool, previously_focused_view_id: Option, parent_view_id: usize, _actions_observation: Subscription, @@ -178,6 +179,7 @@ impl ContextMenu { pub fn new(parent_view_id: usize, cx: &mut ViewContext) -> Self { Self { show_count: 0, + delay_cancel: false, anchor_position: Default::default(), anchor_corner: AnchorCorner::TopLeft, position_mode: OverlayPositionMode::Window, @@ -232,15 +234,23 @@ impl ContextMenu { } } + pub fn delay_cancel(&mut self) { + self.delay_cancel = true; + } + fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - self.reset(cx); - let show_count = self.show_count; - cx.defer(move |this, cx| { - if cx.handle().is_focused(cx) && this.show_count == show_count { - let window_id = cx.window_id(); - (**cx).focus(window_id, this.previously_focused_view_id.take()); - } - }); + if !self.delay_cancel { + self.reset(cx); + let show_count = self.show_count; + cx.defer(move |this, cx| { + if cx.handle().is_focused(cx) && this.show_count == show_count { + let window_id = cx.window_id(); + (**cx).focus(window_id, this.previously_focused_view_id.take()); + } + }); + } else { + self.delay_cancel = false; + } } fn reset(&mut self, cx: &mut ViewContext) { @@ -293,6 +303,34 @@ impl ContextMenu { } } + pub fn toggle( + &mut self, + anchor_position: Vector2F, + anchor_corner: AnchorCorner, + items: Vec, + cx: &mut ViewContext, + ) { + if self.visible() { + self.cancel(&Cancel, cx); + } else { + let mut items = items.into_iter().peekable(); + if items.peek().is_some() { + self.items = items.collect(); + self.anchor_position = anchor_position; + self.anchor_corner = anchor_corner; + self.visible = true; + self.show_count += 1; + if !cx.is_self_focused() { + self.previously_focused_view_id = cx.focused_view_id(); + } + cx.focus_self(); + } else { + self.visible = false; + } + } + cx.notify(); + } + pub fn show( &mut self, anchor_position: Vector2F, diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index e73424f0cd..ce4938ed0d 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -15,7 +15,7 @@ use language::{ ToPointUtf16, }; use log::{debug, error}; -use lsp::{LanguageServer, LanguageServerId}; +use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId}; use node_runtime::NodeRuntime; use request::{LogMessage, StatusNotification}; use settings::SettingsStore; @@ -340,7 +340,7 @@ impl Copilot { let http = util::http::FakeHttpClient::create(|_| async { unreachable!() }); let this = cx.add_model(|cx| Self { http: http.clone(), - node_runtime: NodeRuntime::new(http, cx.background().clone()), + node_runtime: NodeRuntime::instance(http, cx.background().clone()), server: CopilotServer::Running(RunningCopilotServer { lsp: Arc::new(server), sign_in_status: SignInStatus::Authorized, @@ -361,11 +361,14 @@ impl Copilot { let start_language_server = async { let server_path = get_copilot_lsp(http).await?; let node_path = node_runtime.binary_path().await?; - let arguments: &[OsString] = &[server_path.into(), "--stdio".into()]; + let arguments: Vec = vec![server_path.into(), "--stdio".into()]; + let binary = LanguageServerBinary { + path: node_path, + arguments, + }; let server = LanguageServer::new( LanguageServerId(0), - &node_path, - arguments, + binary, Path::new("/"), None, cx.clone(), diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 9b0581492f..5576451b1b 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -102,6 +102,9 @@ impl View for CopilotButton { } }) .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, |_, this, cx| { + this.popup_menu.update(cx, |menu, _| menu.delay_cancel()); + }) .on_click(MouseButton::Left, { let status = status.clone(); move |_, this, cx| match status { @@ -186,7 +189,7 @@ impl CopilotButton { })); self.popup_menu.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::BottomRight, menu_options, @@ -266,7 +269,7 @@ impl CopilotButton { menu_options.push(ContextMenuItem::action("Sign Out", SignOut)); self.popup_menu.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::BottomRight, menu_options, diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a594af51a6..714dc74509 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,26 +1,26 @@ mod block_map; mod fold_map; -mod suggestion_map; +mod inlay_map; mod tab_map; mod wrap_map; -use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; +use crate::{Anchor, AnchorRangeExt, InlayId, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; -use fold_map::{FoldMap, FoldOffset}; +use fold_map::FoldMap; use gpui::{ color::Color, fonts::{FontId, HighlightStyle}, Entity, ModelContext, ModelHandle, }; +use inlay_map::InlayMap; use language::{ language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription, }; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; -pub use suggestion_map::Suggestion; -use suggestion_map::SuggestionMap; use sum_tree::{Bias, TreeMap}; use tab_map::TabMap; +use text::Rope; use wrap_map::WrapMap; pub use block_map::{ @@ -28,6 +28,8 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; +pub use self::inlay_map::{Inlay, InlayProperties}; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { Folded, @@ -44,7 +46,7 @@ pub struct DisplayMap { buffer: ModelHandle, buffer_subscription: BufferSubscription, fold_map: FoldMap, - suggestion_map: SuggestionMap, + inlay_map: InlayMap, tab_map: TabMap, wrap_map: ModelHandle, block_map: BlockMap, @@ -69,8 +71,8 @@ impl DisplayMap { let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let tab_size = Self::tab_size(&buffer, cx); - let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (suggestion_map, snapshot) = SuggestionMap::new(snapshot); + let (inlay_map, snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + let (fold_map, snapshot) = FoldMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); let block_map = BlockMap::new(snapshot, buffer_header_height, excerpt_header_height); @@ -79,7 +81,7 @@ impl DisplayMap { buffer, buffer_subscription, fold_map, - suggestion_map, + inlay_map, tab_map, wrap_map, block_map, @@ -88,16 +90,13 @@ impl DisplayMap { } } - pub fn snapshot(&self, cx: &mut ModelContext) -> DisplaySnapshot { + pub fn snapshot(&mut self, cx: &mut ModelContext) -> DisplaySnapshot { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); - let (fold_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits); - let (suggestion_snapshot, edits) = self.suggestion_map.sync(fold_snapshot.clone(), edits); - + let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); + let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); - let (tab_snapshot, edits) = self - .tab_map - .sync(suggestion_snapshot.clone(), edits, tab_size); + let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(tab_snapshot.clone(), edits, cx)); @@ -106,7 +105,7 @@ impl DisplayMap { DisplaySnapshot { buffer_snapshot: self.buffer.read(cx).snapshot(cx), fold_snapshot, - suggestion_snapshot, + inlay_snapshot, tab_snapshot, wrap_snapshot, block_snapshot, @@ -132,15 +131,14 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.fold(ranges); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -157,15 +155,14 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); self.block_map.read(snapshot, edits); let (snapshot, edits) = fold_map.unfold(ranges, inclusive); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -181,8 +178,8 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -199,8 +196,8 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); - let (snapshot, edits) = self.suggestion_map.sync(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self .wrap_map @@ -231,32 +228,6 @@ impl DisplayMap { self.text_highlights.remove(&Some(type_id)) } - pub fn has_suggestion(&self) -> bool { - self.suggestion_map.has_suggestion() - } - - pub fn replace_suggestion( - &self, - new_suggestion: Option>, - cx: &mut ModelContext, - ) -> Option> - where - T: ToPoint, - { - let snapshot = self.buffer.read(cx).snapshot(cx); - let edits = self.buffer_subscription.consume().into_inner(); - let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.fold_map.read(snapshot, edits); - let (snapshot, edits, old_suggestion) = - self.suggestion_map.replace(new_suggestion, snapshot, edits); - let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); - let (snapshot, edits) = self - .wrap_map - .update(cx, |map, cx| map.sync(snapshot, edits, cx)); - self.block_map.read(snapshot, edits); - old_suggestion - } - pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext) -> bool { self.wrap_map .update(cx, |map, cx| map.set_font(font_id, font_size, cx)) @@ -271,6 +242,39 @@ impl DisplayMap { .update(cx, |map, cx| map.set_wrap_width(width, cx)) } + pub fn current_inlays(&self) -> impl Iterator { + self.inlay_map.current_inlays() + } + + pub fn splice_inlays>( + &mut self, + to_remove: Vec, + to_insert: Vec<(InlayId, InlayProperties)>, + cx: &mut ModelContext, + ) { + if to_remove.is_empty() && to_insert.is_empty() { + return; + } + let buffer_snapshot = self.buffer.read(cx).snapshot(cx); + let edits = self.buffer_subscription.consume().into_inner(); + let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); + let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.read(snapshot, edits); + + let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.read(snapshot, edits); + } + fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { let language = buffer .read(cx) @@ -288,7 +292,7 @@ impl DisplayMap { pub struct DisplaySnapshot { pub buffer_snapshot: MultiBufferSnapshot, fold_snapshot: fold_map::FoldSnapshot, - suggestion_snapshot: suggestion_map::SuggestionSnapshot, + inlay_snapshot: inlay_map::InlaySnapshot, tab_snapshot: tab_map::TabSnapshot, wrap_snapshot: wrap_map::WrapSnapshot, block_snapshot: block_map::BlockSnapshot, @@ -316,9 +320,11 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Left); - *fold_point.column_mut() = 0; - point = fold_point.to_buffer_point(&self.fold_snapshot); + let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left); + fold_point.0.column = 0; + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Left); *display_point.column_mut() = 0; @@ -332,9 +338,11 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: Point) -> (Point, DisplayPoint) { loop { - let mut fold_point = self.fold_snapshot.to_fold_point(point, Bias::Right); - *fold_point.column_mut() = self.fold_snapshot.line_len(fold_point.row()); - point = fold_point.to_buffer_point(&self.fold_snapshot); + let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right); + fold_point.0.column = self.fold_snapshot.line_len(fold_point.row()); + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Right); *display_point.column_mut() = self.line_len(display_point.row()); @@ -364,9 +372,9 @@ impl DisplaySnapshot { } fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { - let fold_point = self.fold_snapshot.to_fold_point(point, bias); - let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); - let tab_point = self.tab_snapshot.to_tab_point(suggestion_point); + let inlay_point = self.inlay_snapshot.to_inlay_point(point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + let tab_point = self.tab_snapshot.to_tab_point(fold_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); DisplayPoint(block_point) @@ -376,9 +384,9 @@ impl DisplaySnapshot { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); - let suggestion_point = self.tab_snapshot.to_suggestion_point(tab_point, bias).0; - let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); - fold_point.to_buffer_point(&self.fold_snapshot) + let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; + let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + self.inlay_snapshot.to_buffer_point(inlay_point) } pub fn max_point(&self) -> DisplayPoint { @@ -388,7 +396,13 @@ impl DisplaySnapshot { /// Returns text chunks starting at the given display row until the end of the file pub fn text_chunks(&self, display_row: u32) -> impl Iterator { self.block_snapshot - .chunks(display_row..self.max_point().row() + 1, false, None, None) + .chunks( + display_row..self.max_point().row() + 1, + false, + None, + None, + None, + ) .map(|h| h.text) } @@ -396,7 +410,7 @@ impl DisplaySnapshot { pub fn reverse_text_chunks(&self, display_row: u32) -> impl Iterator { (0..=display_row).into_iter().rev().flat_map(|row| { self.block_snapshot - .chunks(row..row + 1, false, None, None) + .chunks(row..row + 1, false, None, None, None) .map(|h| h.text) .collect::>() .into_iter() @@ -408,13 +422,15 @@ impl DisplaySnapshot { &self, display_rows: Range, language_aware: bool, - suggestion_highlight: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> DisplayChunks<'_> { self.block_snapshot.chunks( display_rows, language_aware, Some(&self.text_highlights), - suggestion_highlight, + hint_highlights, + suggestion_highlights, ) } @@ -790,9 +806,10 @@ impl DisplayPoint { pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize { let wrap_point = map.block_snapshot.to_wrap_point(self.0); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); - let suggestion_point = map.tab_snapshot.to_suggestion_point(tab_point, bias).0; - let fold_point = map.suggestion_snapshot.to_fold_point(suggestion_point); - fold_point.to_buffer_offset(&map.fold_snapshot) + let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; + let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot); + map.inlay_snapshot + .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point)) } } @@ -1706,7 +1723,7 @@ pub mod tests { ) -> Vec<(String, Option, Option)> { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut chunks: Vec<(String, Option, Option)> = Vec::new(); - for chunk in snapshot.chunks(rows, true, None) { + for chunk in snapshot.chunks(rows, true, None, None) { let syntax_color = chunk .syntax_highlight_id .and_then(|id| id.style(theme)?.color); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index b20ecaef1c..4b76ded3d5 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -573,9 +573,15 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(0..self.transforms.summary().output_rows, false, None, None) - .map(|chunk| chunk.text) - .collect() + self.chunks( + 0..self.transforms.summary().output_rows, + false, + None, + None, + None, + ) + .map(|chunk| chunk.text) + .collect() } pub fn chunks<'a>( @@ -583,7 +589,8 @@ impl BlockSnapshot { rows: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> BlockChunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); @@ -616,7 +623,8 @@ impl BlockSnapshot { input_start..input_end, language_aware, text_highlights, - suggestion_highlight, + hint_highlights, + suggestion_highlights, ), input_chunk: Default::default(), transforms: cursor, @@ -989,7 +997,7 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { #[cfg(test)] mod tests { use super::*; - use crate::display_map::suggestion_map::SuggestionMap; + use crate::display_map::inlay_map::InlayMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use crate::multi_buffer::MultiBuffer; use gpui::{elements::Empty, Element}; @@ -1030,9 +1038,9 @@ mod tests { let buffer = MultiBuffer::build_simple(text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, None, cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1175,12 +1183,11 @@ mod tests { buffer.snapshot(cx) }); - let (fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot, subscription.consume().into_inner()); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, 4.try_into().unwrap()); + tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1205,9 +1212,9 @@ mod tests { let buffer = MultiBuffer::build_simple(text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 1.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(wraps_snapshot.clone(), 1, 1); @@ -1277,9 +1284,9 @@ mod tests { }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - let (fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (tab_map, tab_snapshot) = TabMap::new(suggestion_snapshot, tab_size); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = WrapMap::new(tab_snapshot, font_id, font_size, wrap_width, cx); let mut block_map = BlockMap::new( @@ -1332,12 +1339,11 @@ mod tests { }) .collect::>(); - let (fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot.clone(), vec![]); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), vec![]); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1357,12 +1363,11 @@ mod tests { }) .collect(); - let (fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot.clone(), vec![]); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), vec![]); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1381,11 +1386,10 @@ mod tests { } } - let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); - let (tab_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) }); @@ -1499,6 +1503,7 @@ mod tests { false, None, None, + None, ) .map(|chunk| chunk.text) .collect::(); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 36cfeba4a1..0b1523fe75 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,19 +1,15 @@ -use super::TextHighlights; -use crate::{ - multi_buffer::MultiBufferRows, Anchor, AnchorRangeExt, MultiBufferChunks, MultiBufferSnapshot, - ToOffset, +use super::{ + inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, + TextHighlights, }; -use collections::BTreeMap; +use crate::{Anchor, AnchorRangeExt, MultiBufferSnapshot, ToOffset}; use gpui::{color::Color, fonts::HighlightStyle}; use language::{Chunk, Edit, Point, TextSummary}; -use parking_lot::Mutex; use std::{ any::TypeId, cmp::{self, Ordering}, - iter::{self, Peekable}, - ops::{Range, Sub}, - sync::atomic::{AtomicUsize, Ordering::SeqCst}, - vec, + iter, + ops::{Add, AddAssign, Range, Sub}, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; @@ -29,28 +25,24 @@ impl FoldPoint { self.0.row } + pub fn column(self) -> u32 { + self.0.column + } + pub fn row_mut(&mut self) -> &mut u32 { &mut self.0.row } + #[cfg(test)] pub fn column_mut(&mut self) -> &mut u32 { &mut self.0.column } - pub fn to_buffer_point(self, snapshot: &FoldSnapshot) -> Point { - let mut cursor = snapshot.transforms.cursor::<(FoldPoint, Point)>(); + pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint { + let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&self, Bias::Right, &()); let overshoot = self.0 - cursor.start().0 .0; - cursor.start().1 + overshoot - } - - pub fn to_buffer_offset(self, snapshot: &FoldSnapshot) -> usize { - let mut cursor = snapshot.transforms.cursor::<(FoldPoint, Point)>(); - cursor.seek(&self, Bias::Right, &()); - let overshoot = self.0 - cursor.start().0 .0; - snapshot - .buffer_snapshot - .point_to_offset(cursor.start().1 + overshoot) + InlayPoint(cursor.start().1 .0 + overshoot) } pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { @@ -63,10 +55,10 @@ impl FoldPoint { if !overshoot.is_zero() { let transform = cursor.item().expect("display point out of range"); assert!(transform.output_text.is_none()); - let end_buffer_offset = snapshot - .buffer_snapshot - .point_to_offset(cursor.start().1.input.lines + overshoot); - offset += end_buffer_offset - cursor.start().1.input.len; + let end_inlay_offset = snapshot + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot)); + offset += end_inlay_offset.0 - cursor.start().1.input.len; } FoldOffset(offset) } @@ -87,8 +79,9 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut folds = Vec::new(); - let buffer = self.0.buffer.lock().clone(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); for range in ranges.into_iter() { + let buffer = &snapshot.buffer; let range = range.start.to_offset(&buffer)..range.end.to_offset(&buffer); // Ignore any empty ranges. @@ -103,35 +96,32 @@ impl<'a> FoldMapWriter<'a> { } folds.push(fold); - edits.push(text::Edit { - old: range.clone(), - new: range, + + let inlay_range = + snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, }); } - folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, &buffer)); + let buffer = &snapshot.buffer; + folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(a, b, buffer)); - self.0.folds = { + self.0.snapshot.folds = { let mut new_tree = SumTree::new(); - let mut cursor = self.0.folds.cursor::(); + let mut cursor = self.0.snapshot.folds.cursor::(); for fold in folds { - new_tree.append(cursor.slice(&fold, Bias::Right, &buffer), &buffer); - new_tree.push(fold, &buffer); + new_tree.append(cursor.slice(&fold, Bias::Right, buffer), buffer); + new_tree.push(fold, buffer); } - new_tree.append(cursor.suffix(&buffer), &buffer); + new_tree.append(cursor.suffix(buffer), buffer); new_tree }; - consolidate_buffer_edits(&mut edits); - let edits = self.0.sync(buffer.clone(), edits); - let snapshot = FoldSnapshot { - transforms: self.0.transforms.lock().clone(), - folds: self.0.folds.clone(), - buffer_snapshot: buffer, - version: self.0.version.load(SeqCst), - ellipses_color: self.0.ellipses_color, - }; - (snapshot, edits) + consolidate_inlay_edits(&mut edits); + let edits = self.0.sync(snapshot.clone(), edits); + (self.0.snapshot.clone(), edits) } pub fn unfold( @@ -141,110 +131,93 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut fold_ixs_to_delete = Vec::new(); - let buffer = self.0.buffer.lock().clone(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); + let buffer = &snapshot.buffer; for range in ranges.into_iter() { // Remove intersecting folds and add their ranges to edits that are passed to sync. - let mut folds_cursor = intersecting_folds(&buffer, &self.0.folds, range, inclusive); + let mut folds_cursor = + intersecting_folds(&snapshot, &self.0.snapshot.folds, range, inclusive); while let Some(fold) = folds_cursor.item() { - let offset_range = fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer); + let offset_range = fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer); if offset_range.end > offset_range.start { - edits.push(text::Edit { - old: offset_range.clone(), - new: offset_range, + let inlay_range = snapshot.to_inlay_offset(offset_range.start) + ..snapshot.to_inlay_offset(offset_range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, }); } fold_ixs_to_delete.push(*folds_cursor.start()); - folds_cursor.next(&buffer); + folds_cursor.next(buffer); } } fold_ixs_to_delete.sort_unstable(); fold_ixs_to_delete.dedup(); - self.0.folds = { - let mut cursor = self.0.folds.cursor::(); + self.0.snapshot.folds = { + let mut cursor = self.0.snapshot.folds.cursor::(); let mut folds = SumTree::new(); for fold_ix in fold_ixs_to_delete { - folds.append(cursor.slice(&fold_ix, Bias::Right, &buffer), &buffer); - cursor.next(&buffer); + folds.append(cursor.slice(&fold_ix, Bias::Right, buffer), buffer); + cursor.next(buffer); } - folds.append(cursor.suffix(&buffer), &buffer); + folds.append(cursor.suffix(buffer), buffer); folds }; - consolidate_buffer_edits(&mut edits); - let edits = self.0.sync(buffer.clone(), edits); - let snapshot = FoldSnapshot { - transforms: self.0.transforms.lock().clone(), - folds: self.0.folds.clone(), - buffer_snapshot: buffer, - version: self.0.version.load(SeqCst), - ellipses_color: self.0.ellipses_color, - }; - (snapshot, edits) + consolidate_inlay_edits(&mut edits); + let edits = self.0.sync(snapshot.clone(), edits); + (self.0.snapshot.clone(), edits) } } pub struct FoldMap { - buffer: Mutex, - transforms: Mutex>, - folds: SumTree, - version: AtomicUsize, + snapshot: FoldSnapshot, ellipses_color: Option, } impl FoldMap { - pub fn new(buffer: MultiBufferSnapshot) -> (Self, FoldSnapshot) { + pub fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { let this = Self { - buffer: Mutex::new(buffer.clone()), - folds: Default::default(), - transforms: Mutex::new(SumTree::from_item( - Transform { - summary: TransformSummary { - input: buffer.text_summary(), - output: buffer.text_summary(), + snapshot: FoldSnapshot { + folds: Default::default(), + transforms: SumTree::from_item( + Transform { + summary: TransformSummary { + input: inlay_snapshot.text_summary(), + output: inlay_snapshot.text_summary(), + }, + output_text: None, }, - output_text: None, - }, - &(), - )), - ellipses_color: None, - version: Default::default(), - }; - - let snapshot = FoldSnapshot { - transforms: this.transforms.lock().clone(), - folds: this.folds.clone(), - buffer_snapshot: this.buffer.lock().clone(), - version: this.version.load(SeqCst), + &(), + ), + inlay_snapshot: inlay_snapshot.clone(), + version: 0, + ellipses_color: None, + }, ellipses_color: None, }; + let snapshot = this.snapshot.clone(); (this, snapshot) } pub fn read( - &self, - buffer: MultiBufferSnapshot, - edits: Vec>, + &mut self, + inlay_snapshot: InlaySnapshot, + edits: Vec, ) -> (FoldSnapshot, Vec) { - let edits = self.sync(buffer, edits); + let edits = self.sync(inlay_snapshot, edits); self.check_invariants(); - let snapshot = FoldSnapshot { - transforms: self.transforms.lock().clone(), - folds: self.folds.clone(), - buffer_snapshot: self.buffer.lock().clone(), - version: self.version.load(SeqCst), - ellipses_color: self.ellipses_color, - }; - (snapshot, edits) + (self.snapshot.clone(), edits) } pub fn write( &mut self, - buffer: MultiBufferSnapshot, - edits: Vec>, + inlay_snapshot: InlaySnapshot, + edits: Vec, ) -> (FoldMapWriter, FoldSnapshot, Vec) { - let (snapshot, edits) = self.read(buffer, edits); + let (snapshot, edits) = self.read(inlay_snapshot, edits); (FoldMapWriter(self), snapshot, edits) } @@ -260,15 +233,17 @@ impl FoldMap { fn check_invariants(&self) { if cfg!(test) { assert_eq!( - self.transforms.lock().summary().input.len, - self.buffer.lock().len(), - "transform tree does not match buffer's length" + self.snapshot.transforms.summary().input.len, + self.snapshot.inlay_snapshot.len().0, + "transform tree does not match inlay snapshot's length" ); - let mut folds = self.folds.iter().peekable(); + let mut folds = self.snapshot.folds.iter().peekable(); while let Some(fold) = folds.next() { if let Some(next_fold) = folds.peek() { - let comparison = fold.0.cmp(&next_fold.0, &self.buffer.lock()); + let comparison = fold + .0 + .cmp(&next_fold.0, &self.snapshot.inlay_snapshot.buffer); assert!(comparison.is_le()); } } @@ -276,50 +251,42 @@ impl FoldMap { } fn sync( - &self, - new_buffer: MultiBufferSnapshot, - buffer_edits: Vec>, + &mut self, + inlay_snapshot: InlaySnapshot, + inlay_edits: Vec, ) -> Vec { - if buffer_edits.is_empty() { - let mut buffer = self.buffer.lock(); - if buffer.edit_count() != new_buffer.edit_count() - || buffer.parse_count() != new_buffer.parse_count() - || buffer.diagnostics_update_count() != new_buffer.diagnostics_update_count() - || buffer.git_diff_update_count() != new_buffer.git_diff_update_count() - || buffer.trailing_excerpt_update_count() - != new_buffer.trailing_excerpt_update_count() - { - self.version.fetch_add(1, SeqCst); + if inlay_edits.is_empty() { + if self.snapshot.inlay_snapshot.version != inlay_snapshot.version { + self.snapshot.version += 1; } - *buffer = new_buffer; + self.snapshot.inlay_snapshot = inlay_snapshot; Vec::new() } else { - let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); + let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); let mut new_transforms = SumTree::new(); - let mut transforms = self.transforms.lock(); - let mut cursor = transforms.cursor::(); - cursor.seek(&0, Bias::Right, &()); + let mut cursor = self.snapshot.transforms.cursor::(); + cursor.seek(&InlayOffset(0), Bias::Right, &()); - while let Some(mut edit) = buffer_edits_iter.next() { + while let Some(mut edit) = inlay_edits_iter.next() { new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); - edit.new.start -= edit.old.start - cursor.start(); + edit.new.start -= edit.old.start - *cursor.start(); edit.old.start = *cursor.start(); cursor.seek(&edit.old.end, Bias::Right, &()); cursor.next(&()); - let mut delta = edit.new.len() as isize - edit.old.len() as isize; + let mut delta = edit.new_len().0 as isize - edit.old_len().0 as isize; loop { edit.old.end = *cursor.start(); - if let Some(next_edit) = buffer_edits_iter.peek() { + if let Some(next_edit) = inlay_edits_iter.peek() { if next_edit.old.start > edit.old.end { break; } - let next_edit = buffer_edits_iter.next().unwrap(); - delta += next_edit.new.len() as isize - next_edit.old.len() as isize; + let next_edit = inlay_edits_iter.next().unwrap(); + delta += next_edit.new_len().0 as isize - next_edit.old_len().0 as isize; if next_edit.old.end >= edit.old.end { edit.old.end = next_edit.old.end; @@ -331,19 +298,29 @@ impl FoldMap { } } - edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; + edit.new.end = + InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); - let anchor = new_buffer.anchor_before(edit.new.start); - let mut folds_cursor = self.folds.cursor::(); - folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &new_buffer); + let anchor = inlay_snapshot + .buffer + .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); + let mut folds_cursor = self.snapshot.folds.cursor::(); + folds_cursor.seek( + &Fold(anchor..Anchor::max()), + Bias::Left, + &inlay_snapshot.buffer, + ); let mut folds = iter::from_fn({ - let buffer = &new_buffer; + let inlay_snapshot = &inlay_snapshot; move || { - let item = folds_cursor - .item() - .map(|f| f.0.start.to_offset(buffer)..f.0.end.to_offset(buffer)); - folds_cursor.next(buffer); + let item = folds_cursor.item().map(|f| { + let buffer_start = f.0.start.to_offset(&inlay_snapshot.buffer); + let buffer_end = f.0.end.to_offset(&inlay_snapshot.buffer); + inlay_snapshot.to_inlay_offset(buffer_start) + ..inlay_snapshot.to_inlay_offset(buffer_end) + }); + folds_cursor.next(&inlay_snapshot.buffer); item } }) @@ -353,7 +330,7 @@ impl FoldMap { let mut fold = folds.next().unwrap(); let sum = new_transforms.summary(); - assert!(fold.start >= sum.input.len); + assert!(fold.start.0 >= sum.input.len); while folds .peek() @@ -365,9 +342,9 @@ impl FoldMap { } } - if fold.start > sum.input.len { - let text_summary = new_buffer - .text_summary_for_range::(sum.input.len..fold.start); + if fold.start.0 > sum.input.len { + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..fold.start); new_transforms.push( Transform { summary: TransformSummary { @@ -386,7 +363,8 @@ impl FoldMap { Transform { summary: TransformSummary { output: TextSummary::from(output_text), - input: new_buffer.text_summary_for_range(fold.start..fold.end), + input: inlay_snapshot + .text_summary_for_range(fold.start..fold.end), }, output_text: Some(output_text), }, @@ -396,9 +374,9 @@ impl FoldMap { } let sum = new_transforms.summary(); - if sum.input.len < edit.new.end { - let text_summary = new_buffer - .text_summary_for_range::(sum.input.len..edit.new.end); + if sum.input.len < edit.new.end.0 { + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end); new_transforms.push( Transform { summary: TransformSummary { @@ -414,7 +392,7 @@ impl FoldMap { new_transforms.append(cursor.suffix(&()), &()); if new_transforms.is_empty() { - let text_summary = new_buffer.text_summary(); + let text_summary = inlay_snapshot.text_summary(); new_transforms.push( Transform { summary: TransformSummary { @@ -429,18 +407,21 @@ impl FoldMap { drop(cursor); - let mut fold_edits = Vec::with_capacity(buffer_edits.len()); + let mut fold_edits = Vec::with_capacity(inlay_edits.len()); { - let mut old_transforms = transforms.cursor::<(usize, FoldOffset)>(); - let mut new_transforms = new_transforms.cursor::<(usize, FoldOffset)>(); + let mut old_transforms = self + .snapshot + .transforms + .cursor::<(InlayOffset, FoldOffset)>(); + let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(); - for mut edit in buffer_edits { + for mut edit in inlay_edits { old_transforms.seek(&edit.old.start, Bias::Left, &()); if old_transforms.item().map_or(false, |t| t.is_fold()) { edit.old.start = old_transforms.start().0; } let old_start = - old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); + old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0).0; old_transforms.seek_forward(&edit.old.end, Bias::Right, &()); if old_transforms.item().map_or(false, |t| t.is_fold()) { @@ -448,14 +429,14 @@ impl FoldMap { edit.old.end = old_transforms.start().0; } let old_end = - old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); + old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0).0; new_transforms.seek(&edit.new.start, Bias::Left, &()); if new_transforms.item().map_or(false, |t| t.is_fold()) { edit.new.start = new_transforms.start().0; } let new_start = - new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); + new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0).0; new_transforms.seek_forward(&edit.new.end, Bias::Right, &()); if new_transforms.item().map_or(false, |t| t.is_fold()) { @@ -463,7 +444,7 @@ impl FoldMap { edit.new.end = new_transforms.start().0; } let new_end = - new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); + new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0).0; fold_edits.push(FoldEdit { old: FoldOffset(old_start)..FoldOffset(old_end), @@ -474,9 +455,9 @@ impl FoldMap { consolidate_fold_edits(&mut fold_edits); } - *transforms = new_transforms; - *self.buffer.lock() = new_buffer; - self.version.fetch_add(1, SeqCst); + self.snapshot.transforms = new_transforms; + self.snapshot.inlay_snapshot = inlay_snapshot; + self.snapshot.version += 1; fold_edits } } @@ -486,32 +467,28 @@ impl FoldMap { pub struct FoldSnapshot { transforms: SumTree, folds: SumTree, - buffer_snapshot: MultiBufferSnapshot, + pub inlay_snapshot: InlaySnapshot, pub version: usize, pub ellipses_color: Option, } impl FoldSnapshot { - pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - &self.buffer_snapshot - } - #[cfg(test)] pub fn text(&self) -> String { - self.chunks(FoldOffset(0)..self.len(), false, None) + self.chunks(FoldOffset(0)..self.len(), false, None, None, None) .map(|c| c.text) .collect() } #[cfg(test)] pub fn fold_count(&self) -> usize { - self.folds.items(&self.buffer_snapshot).len() + self.folds.items(&self.inlay_snapshot.buffer).len() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>(); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&range.start, Bias::Right, &()); if let Some(transform) = cursor.item() { let start_in_transform = range.start.0 - cursor.start().0 .0; @@ -522,11 +499,15 @@ impl FoldSnapshot { [start_in_transform.column as usize..end_in_transform.column as usize], ); } else { - let buffer_start = cursor.start().1 + start_in_transform; - let buffer_end = cursor.start().1 + end_in_transform; + let inlay_start = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform)); + let inlay_end = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); summary = self - .buffer_snapshot - .text_summary_for_range(buffer_start..buffer_end); + .inlay_snapshot + .text_summary_for_range(inlay_start..inlay_end); } } @@ -540,11 +521,13 @@ impl FoldSnapshot { if let Some(output_text) = transform.output_text { summary += TextSummary::from(&output_text[..end_in_transform.column as usize]); } else { - let buffer_start = cursor.start().1; - let buffer_end = cursor.start().1 + end_in_transform; + let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1); + let inlay_end = self + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); summary += self - .buffer_snapshot - .text_summary_for_range::(buffer_start..buffer_end); + .inlay_snapshot + .text_summary_for_range(inlay_start..inlay_end); } } } @@ -552,8 +535,8 @@ impl FoldSnapshot { summary } - pub fn to_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(Point, FoldPoint)>(); + pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(); cursor.seek(&point, Bias::Right, &()); if cursor.item().map_or(false, |t| t.is_fold()) { if bias == Bias::Left || point == cursor.start().0 { @@ -562,7 +545,7 @@ impl FoldSnapshot { cursor.end(&()).1 } } else { - let overshoot = point - cursor.start().0; + let overshoot = point.0 - cursor.start().0 .0; FoldPoint(cmp::min( cursor.start().1 .0 + overshoot, cursor.end(&()).1 .0, @@ -590,12 +573,12 @@ impl FoldSnapshot { } let fold_point = FoldPoint::new(start_row, 0); - let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>(); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&fold_point, Bias::Left, &()); let overshoot = fold_point.0 - cursor.start().0 .0; - let buffer_point = cursor.start().1 + overshoot; - let input_buffer_rows = self.buffer_snapshot.buffer_rows(buffer_point.row); + let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot); + let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row()); FoldBufferRows { fold_point, @@ -617,10 +600,10 @@ impl FoldSnapshot { where T: ToOffset, { - let mut folds = intersecting_folds(&self.buffer_snapshot, &self.folds, range, false); + let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false); iter::from_fn(move || { let item = folds.item().map(|f| &f.0); - folds.next(&self.buffer_snapshot); + folds.next(&self.inlay_snapshot.buffer); item }) } @@ -629,26 +612,39 @@ impl FoldSnapshot { where T: ToOffset, { - let offset = offset.to_offset(&self.buffer_snapshot); - let mut cursor = self.transforms.cursor::(); - cursor.seek(&offset, Bias::Right, &()); + let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer); + let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset); + let mut cursor = self.transforms.cursor::(); + cursor.seek(&inlay_offset, Bias::Right, &()); cursor.item().map_or(false, |t| t.output_text.is_some()) } pub fn is_line_folded(&self, buffer_row: u32) -> bool { - let mut cursor = self.transforms.cursor::(); - cursor.seek(&Point::new(buffer_row, 0), Bias::Right, &()); - while let Some(transform) = cursor.item() { - if transform.output_text.is_some() { - return true; + let mut inlay_point = self + .inlay_snapshot + .to_inlay_point(Point::new(buffer_row, 0)); + let mut cursor = self.transforms.cursor::(); + cursor.seek(&inlay_point, Bias::Right, &()); + loop { + match cursor.item() { + Some(transform) => { + let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point); + if buffer_point.row != buffer_row { + return false; + } else if transform.output_text.is_some() { + return true; + } + } + None => return false, } - if cursor.end(&()).row == buffer_row { - cursor.next(&()) + + if cursor.end(&()).row() == inlay_point.row() { + cursor.next(&()); } else { - break; + inlay_point.0 += Point::new(1, 0); + cursor.seek(&inlay_point, Bias::Right, &()); } } - false } pub fn chunks<'a>( @@ -656,127 +652,56 @@ impl FoldSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, + hint_highlights: Option, + suggestion_highlights: Option, ) -> FoldChunks<'a> { - let mut highlight_endpoints = Vec::new(); - let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>(); + let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(); - let buffer_end = { + let inlay_end = { transform_cursor.seek(&range.end, Bias::Right, &()); let overshoot = range.end.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + overshoot + transform_cursor.start().1 + InlayOffset(overshoot) }; - let buffer_start = { + let inlay_start = { transform_cursor.seek(&range.start, Bias::Right, &()); let overshoot = range.start.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + overshoot + transform_cursor.start().1 + InlayOffset(overshoot) }; - if let Some(text_highlights) = text_highlights { - if !text_highlights.is_empty() { - while transform_cursor.start().0 < range.end { - if !transform_cursor.item().unwrap().is_fold() { - let transform_start = self - .buffer_snapshot - .anchor_after(cmp::max(buffer_start, transform_cursor.start().1)); - - let transform_end = { - let overshoot = range.end.0 - transform_cursor.start().0 .0; - self.buffer_snapshot.anchor_before(cmp::min( - transform_cursor.end(&()).1, - transform_cursor.start().1 + overshoot, - )) - }; - - for (tag, highlights) in text_highlights.iter() { - let style = highlights.0; - let ranges = &highlights.1; - - let start_ix = match ranges.binary_search_by(|probe| { - let cmp = probe.end.cmp(&transform_start, self.buffer_snapshot()); - if cmp.is_gt() { - Ordering::Greater - } else { - Ordering::Less - } - }) { - Ok(i) | Err(i) => i, - }; - for range in &ranges[start_ix..] { - if range - .start - .cmp(&transform_end, &self.buffer_snapshot) - .is_ge() - { - break; - } - - highlight_endpoints.push(HighlightEndpoint { - offset: range.start.to_offset(&self.buffer_snapshot), - is_start: true, - tag: *tag, - style, - }); - highlight_endpoints.push(HighlightEndpoint { - offset: range.end.to_offset(&self.buffer_snapshot), - is_start: false, - tag: *tag, - style, - }); - } - } - } - - transform_cursor.next(&()); - } - highlight_endpoints.sort(); - transform_cursor.seek(&range.start, Bias::Right, &()); - } - } - FoldChunks { transform_cursor, - buffer_chunks: self - .buffer_snapshot - .chunks(buffer_start..buffer_end, language_aware), - buffer_chunk: None, - buffer_offset: buffer_start, + inlay_chunks: self.inlay_snapshot.chunks( + inlay_start..inlay_end, + language_aware, + text_highlights, + hint_highlights, + suggestion_highlights, + ), + inlay_chunk: None, + inlay_offset: inlay_start, output_offset: range.start.0, max_output_offset: range.end.0, - highlight_endpoints: highlight_endpoints.into_iter().peekable(), - active_highlights: Default::default(), ellipses_color: self.ellipses_color, } } + pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { + self.chunks(start.to_offset(self)..self.len(), false, None, None, None) + .flat_map(|chunk| chunk.text.chars()) + } + #[cfg(test)] pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset { - let mut cursor = self.transforms.cursor::<(FoldOffset, usize)>(); - cursor.seek(&offset, Bias::Right, &()); - if let Some(transform) = cursor.item() { - let transform_start = cursor.start().0 .0; - if transform.output_text.is_some() { - if offset.0 == transform_start || matches!(bias, Bias::Left) { - FoldOffset(transform_start) - } else { - FoldOffset(cursor.end(&()).0 .0) - } - } else { - let overshoot = offset.0 - transform_start; - let buffer_offset = cursor.start().1 + overshoot; - let clipped_buffer_offset = self.buffer_snapshot.clip_offset(buffer_offset, bias); - FoldOffset( - (offset.0 as isize + (clipped_buffer_offset as isize - buffer_offset as isize)) - as usize, - ) - } + if offset > self.len() { + self.len() } else { - FoldOffset(self.transforms.summary().output.len) + self.clip_point(offset.to_point(self), bias).to_offset(self) } } pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(FoldPoint, Point)>(); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(); cursor.seek(&point, Bias::Right, &()); if let Some(transform) = cursor.item() { let transform_start = cursor.start().0 .0; @@ -787,11 +712,10 @@ impl FoldSnapshot { FoldPoint(cursor.end(&()).0 .0) } } else { - let overshoot = point.0 - transform_start; - let buffer_position = cursor.start().1 + overshoot; - let clipped_buffer_position = - self.buffer_snapshot.clip_point(buffer_position, bias); - FoldPoint(cursor.start().0 .0 + (clipped_buffer_position - cursor.start().1)) + let overshoot = InlayPoint(point.0 - transform_start); + let inlay_point = cursor.start().1 + overshoot; + let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias); + FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0) } } else { FoldPoint(self.transforms.summary().output.lines) @@ -800,7 +724,7 @@ impl FoldSnapshot { } fn intersecting_folds<'a, T>( - buffer: &'a MultiBufferSnapshot, + inlay_snapshot: &'a InlaySnapshot, folds: &'a SumTree, range: Range, inclusive: bool, @@ -808,6 +732,7 @@ fn intersecting_folds<'a, T>( where T: ToOffset, { + let buffer = &inlay_snapshot.buffer; let start = buffer.anchor_before(range.start.to_offset(buffer)); let end = buffer.anchor_after(range.end.to_offset(buffer)); let mut cursor = folds.filter::<_, usize>(move |summary| { @@ -824,7 +749,7 @@ where cursor } -fn consolidate_buffer_edits(edits: &mut Vec>) { +fn consolidate_inlay_edits(edits: &mut Vec) { edits.sort_unstable_by(|a, b| { a.old .start @@ -952,7 +877,7 @@ impl Default for FoldSummary { impl sum_tree::Summary for FoldSummary { type Context = MultiBufferSnapshot; - fn add_summary(&mut self, other: &Self, buffer: &MultiBufferSnapshot) { + fn add_summary(&mut self, other: &Self, buffer: &Self::Context) { if other.min_start.cmp(&self.min_start, buffer) == Ordering::Less { self.min_start = other.min_start.clone(); } @@ -996,8 +921,8 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { #[derive(Clone)] pub struct FoldBufferRows<'a> { - cursor: Cursor<'a, Transform, (FoldPoint, Point)>, - input_buffer_rows: MultiBufferRows<'a>, + cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>, + input_buffer_rows: InlayBufferRows<'a>, fold_point: FoldPoint, } @@ -1016,7 +941,7 @@ impl<'a> Iterator for FoldBufferRows<'a> { if self.cursor.item().is_some() { if traversed_fold { - self.input_buffer_rows.seek(self.cursor.start().1.row); + self.input_buffer_rows.seek(self.cursor.start().1.row()); self.input_buffer_rows.next(); } *self.fold_point.row_mut() += 1; @@ -1028,14 +953,12 @@ impl<'a> Iterator for FoldBufferRows<'a> { } pub struct FoldChunks<'a> { - transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>, - buffer_chunks: MultiBufferChunks<'a>, - buffer_chunk: Option<(usize, Chunk<'a>)>, - buffer_offset: usize, + transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>, + inlay_chunks: InlayChunks<'a>, + inlay_chunk: Option<(InlayOffset, Chunk<'a>)>, + inlay_offset: InlayOffset, output_offset: usize, max_output_offset: usize, - highlight_endpoints: Peekable>, - active_highlights: BTreeMap, HighlightStyle>, ellipses_color: Option, } @@ -1052,11 +975,11 @@ impl<'a> Iterator for FoldChunks<'a> { // If we're in a fold, then return the fold's display text and // advance the transform and buffer cursors to the end of the fold. if let Some(output_text) = transform.output_text { - self.buffer_chunk.take(); - self.buffer_offset += transform.summary.input.len; - self.buffer_chunks.seek(self.buffer_offset); + self.inlay_chunk.take(); + self.inlay_offset += InlayOffset(transform.summary.input.len); + self.inlay_chunks.seek(self.inlay_offset); - while self.buffer_offset >= self.transform_cursor.end(&()).1 + while self.inlay_offset >= self.transform_cursor.end(&()).1 && self.transform_cursor.item().is_some() { self.transform_cursor.next(&()); @@ -1073,53 +996,28 @@ impl<'a> Iterator for FoldChunks<'a> { }); } - let mut next_highlight_endpoint = usize::MAX; - while let Some(endpoint) = self.highlight_endpoints.peek().copied() { - if endpoint.offset <= self.buffer_offset { - if endpoint.is_start { - self.active_highlights.insert(endpoint.tag, endpoint.style); - } else { - self.active_highlights.remove(&endpoint.tag); - } - self.highlight_endpoints.next(); - } else { - next_highlight_endpoint = endpoint.offset; - break; - } - } - // Retrieve a chunk from the current location in the buffer. - if self.buffer_chunk.is_none() { - let chunk_offset = self.buffer_chunks.offset(); - self.buffer_chunk = self.buffer_chunks.next().map(|chunk| (chunk_offset, chunk)); + if self.inlay_chunk.is_none() { + let chunk_offset = self.inlay_chunks.offset(); + self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk)); } // Otherwise, take a chunk from the buffer's text. - if let Some((buffer_chunk_start, mut chunk)) = self.buffer_chunk { - let buffer_chunk_end = buffer_chunk_start + chunk.text.len(); + if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk { + let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len()); let transform_end = self.transform_cursor.end(&()).1; - let chunk_end = buffer_chunk_end - .min(transform_end) - .min(next_highlight_endpoint); + let chunk_end = buffer_chunk_end.min(transform_end); chunk.text = &chunk.text - [self.buffer_offset - buffer_chunk_start..chunk_end - buffer_chunk_start]; - - if !self.active_highlights.is_empty() { - let mut highlight_style = HighlightStyle::default(); - for active_highlight in self.active_highlights.values() { - highlight_style.highlight(*active_highlight); - } - chunk.highlight_style = Some(highlight_style); - } + [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; if chunk_end == transform_end { self.transform_cursor.next(&()); } else if chunk_end == buffer_chunk_end { - self.buffer_chunk.take(); + self.inlay_chunk.take(); } - self.buffer_offset = chunk_end; + self.inlay_offset = chunk_end; self.output_offset += chunk.text.len(); return Some(chunk); } @@ -1130,7 +1028,7 @@ impl<'a> Iterator for FoldChunks<'a> { #[derive(Copy, Clone, Eq, PartialEq)] struct HighlightEndpoint { - offset: usize, + offset: InlayOffset, is_start: bool, tag: Option, style: HighlightStyle, @@ -1162,12 +1060,34 @@ impl FoldOffset { let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) { Point::new(0, (self.0 - cursor.start().0 .0) as u32) } else { - let buffer_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; - let buffer_point = snapshot.buffer_snapshot.offset_to_point(buffer_offset); - buffer_point - cursor.start().1.input.lines + let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; + let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset)); + inlay_point.0 - cursor.start().1.input.lines }; FoldPoint(cursor.start().1.output.lines + overshoot) } + + #[cfg(test)] + pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { + let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(); + cursor.seek(&self, Bias::Right, &()); + let overshoot = self.0 - cursor.start().0 .0; + InlayOffset(cursor.start().1 .0 + overshoot) + } +} + +impl Add for FoldOffset { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for FoldOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } } impl Sub for FoldOffset { @@ -1184,15 +1104,15 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += &summary.input.lines; + self.0 += &summary.input.lines; } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += &summary.input.len; + self.0 += &summary.input.len; } } @@ -1201,12 +1121,12 @@ pub type FoldEdit = Edit; #[cfg(test)] mod tests { use super::*; - use crate::{MultiBuffer, ToPoint}; + use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint}; use collections::HashSet; use rand::prelude::*; use settings::SettingsStore; - use std::{cmp::Reverse, env, mem, sync::Arc}; - use sum_tree::TreeMap; + use std::{env, mem}; + use text::Patch; use util::test::sample_text; use util::RandomCharIter; use Bias::{Left, Right}; @@ -1217,9 +1137,10 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot, vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot, vec![]); let (snapshot2, edits) = writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(2, 4)..Point::new(4, 1), @@ -1250,7 +1171,10 @@ mod tests { ); buffer.snapshot(cx) }); - let (snapshot3, edits) = map.read(buffer_snapshot, subscription.consume().into_inner()); + + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee"); assert_eq!( edits, @@ -1270,17 +1194,19 @@ mod tests { buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx); buffer.snapshot(cx) }); - let (snapshot4, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner()); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits); assert_eq!(snapshot4.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false); - let (snapshot5, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot5.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true); - let (snapshot6, _) = map.read(buffer_snapshot, vec![]); + let (snapshot6, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee"); } @@ -1290,35 +1216,36 @@ mod tests { let buffer = MultiBuffer::build_simple("abcdefghijkl", cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); { - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![5..8]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "abcde⋯ijkl"); // Create an fold adjacent to the start of the first fold. - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![0..1, 2..5]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯ijkl"); // Create an fold adjacent to the end of the first fold. - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![11..11, 8..10]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯kl"); } { - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let mut map = FoldMap::new(inlay_snapshot.clone()).0; // Create two adjacent folds. - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![0..2, 2..5]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "⋯fghijkl"); // Edit within one of the folds. @@ -1326,7 +1253,9 @@ mod tests { buffer.edit([(0..1, "12345")], None, cx); buffer.snapshot(cx) }); - let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner()); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot.text(), "12345⋯fghijkl"); } } @@ -1335,15 +1264,16 @@ mod tests { fn test_overlapping_folds(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(0, 4)..Point::new(1, 0), Point::new(1, 2)..Point::new(3, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1353,21 +1283,24 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee"); let buffer_snapshot = buffer.update(cx, |buffer, cx| { buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx); buffer.snapshot(cx) }); - let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner()); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1375,16 +1308,17 @@ mod tests { fn test_folds_in_range(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(0, 4)..Point::new(1, 0), Point::new(1, 2)..Point::new(3, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); let fold_ranges = snapshot .folds_in_range(Point::new(1, 0)..Point::new(1, 3)) .map(|fold| fold.start.to_point(&buffer_snapshot)..fold.end.to_point(&buffer_snapshot)) @@ -1413,37 +1347,25 @@ mod tests { MultiBuffer::build_random(&mut rng, cx) }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut initial_snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); + let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); let mut snapshot_edits = Vec::new(); - let mut highlights = TreeMap::default(); - let highlight_count = rng.gen_range(0_usize..10); - let mut highlight_ranges = (0..highlight_count) - .map(|_| buffer_snapshot.random_byte_range(0, &mut rng)) - .collect::>(); - highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end))); - log::info!("highlighting ranges {:?}", highlight_ranges); - let highlight_ranges = highlight_ranges - .into_iter() - .map(|range| { - buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end) - }) - .collect::>(); - - highlights.insert( - Some(TypeId::of::<()>()), - Arc::new((HighlightStyle::default(), highlight_ranges)), - ); - + let mut next_inlay_id = 0; for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); let mut buffer_edits = Vec::new(); + let mut inlay_edits = Vec::new(); match rng.gen_range(0..=100) { - 0..=59 => { + 0..=39 => { snapshot_edits.extend(map.randomly_mutate(&mut rng)); } + 40..=59 => { + let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + inlay_edits = edits; + } _ => buffer.update(cx, |buffer, cx| { let subscription = buffer.subscribe(); let edit_count = rng.gen_range(1..=5); @@ -1455,12 +1377,21 @@ mod tests { }), }; - let (snapshot, edits) = map.read(buffer_snapshot.clone(), buffer_edits); + let (inlay_snapshot, new_inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + log::info!("inlay text {:?}", inlay_snapshot.text()); + + let inlay_edits = Patch::new(inlay_edits) + .compose(new_inlay_edits) + .into_inner(); + let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits); snapshot_edits.push((snapshot.clone(), edits)); - let mut expected_text: String = buffer_snapshot.text().to_string(); + let mut expected_text: String = inlay_snapshot.text().to_string(); for fold_range in map.merged_fold_ranges().into_iter().rev() { - expected_text.replace_range(fold_range.start..fold_range.end, "⋯"); + let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start); + let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end); + expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯"); } assert_eq!(snapshot.text(), expected_text); @@ -1473,16 +1404,20 @@ mod tests { let mut prev_row = 0; let mut expected_buffer_rows = Vec::new(); for fold_range in map.merged_fold_ranges().into_iter() { - let fold_start = buffer_snapshot.offset_to_point(fold_range.start).row; - let fold_end = buffer_snapshot.offset_to_point(fold_range.end).row; + let fold_start = inlay_snapshot + .to_point(inlay_snapshot.to_inlay_offset(fold_range.start)) + .row(); + let fold_end = inlay_snapshot + .to_point(inlay_snapshot.to_inlay_offset(fold_range.end)) + .row(); expected_buffer_rows.extend( - buffer_snapshot + inlay_snapshot .buffer_rows(prev_row) .take((1 + fold_start - prev_row) as usize), ); prev_row = 1 + fold_end; } - expected_buffer_rows.extend(buffer_snapshot.buffer_rows(prev_row)); + expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row)); assert_eq!( expected_buffer_rows.len(), @@ -1508,19 +1443,19 @@ mod tests { let mut fold_offset = FoldOffset(0); let mut char_column = 0; for c in expected_text.chars() { - let buffer_point = fold_point.to_buffer_point(&snapshot); - let buffer_offset = buffer_point.to_offset(&buffer_snapshot); + let inlay_point = fold_point.to_inlay_point(&snapshot); + let inlay_offset = fold_offset.to_inlay_offset(&snapshot); assert_eq!( - snapshot.to_fold_point(buffer_point, Right), + snapshot.to_fold_point(inlay_point, Right), fold_point, "{:?} -> fold point", - buffer_point, + inlay_point, ); assert_eq!( - fold_point.to_buffer_offset(&snapshot), - buffer_offset, - "fold_point.to_buffer_offset({:?})", - fold_point, + inlay_snapshot.to_offset(inlay_point), + inlay_offset, + "inlay_snapshot.to_offset({:?})", + inlay_point, ); assert_eq!( fold_point.to_offset(&snapshot), @@ -1561,7 +1496,7 @@ mod tests { let text = &expected_text[start.0..end.0]; assert_eq!( snapshot - .chunks(start..end, false, Some(&highlights)) + .chunks(start..end, false, None, None, None) .map(|c| c.text) .collect::(), text, @@ -1570,9 +1505,6 @@ mod tests { let mut fold_row = 0; while fold_row < expected_buffer_rows.len() as u32 { - fold_row = snapshot - .clip_point(FoldPoint::new(fold_row, 0), Bias::Right) - .row(); assert_eq!( snapshot.buffer_rows(fold_row).collect::>(), expected_buffer_rows[(fold_row as usize)..], @@ -1582,13 +1514,31 @@ mod tests { fold_row += 1; } - let fold_start_rows = map + let folded_buffer_rows = map .merged_fold_ranges() .iter() - .map(|range| range.start.to_point(&buffer_snapshot).row) + .flat_map(|range| { + let start_row = range.start.to_point(&buffer_snapshot).row; + let end = range.end.to_point(&buffer_snapshot); + if end.column == 0 { + start_row..end.row + } else { + start_row..end.row + 1 + } + }) .collect::>(); - for row in fold_start_rows { - assert!(snapshot.is_line_folded(row)); + for row in 0..=buffer_snapshot.max_point().row { + assert_eq!( + snapshot.is_line_folded(row), + folded_buffer_rows.contains(&row), + "expected buffer row {}{} to be folded", + row, + if folded_buffer_rows.contains(&row) { + "" + } else { + " not" + } + ); } for _ in 0..5 { @@ -1596,6 +1546,7 @@ mod tests { buffer_snapshot.clip_offset(rng.gen_range(0..=buffer_snapshot.len()), Right); let start = buffer_snapshot.clip_offset(rng.gen_range(0..=end), Left); let expected_folds = map + .snapshot .folds .items(&buffer_snapshot) .into_iter() @@ -1659,15 +1610,16 @@ mod tests { let buffer = MultiBuffer::build_simple(&text, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let mut map = FoldMap::new(buffer_snapshot.clone()).0; + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ Point::new(0, 2)..Point::new(2, 2), Point::new(3, 1)..Point::new(4, 1), ]); - let (snapshot, _) = map.read(buffer_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n"); assert_eq!( snapshot.buffer_rows(0).collect::>(), @@ -1682,13 +1634,14 @@ mod tests { impl FoldMap { fn merged_fold_ranges(&self) -> Vec> { - let buffer = self.buffer.lock().clone(); - let mut folds = self.folds.items(&buffer); + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = &inlay_snapshot.buffer; + let mut folds = self.snapshot.folds.items(buffer); // Ensure sorting doesn't change how folds get merged and displayed. - folds.sort_by(|a, b| a.0.cmp(&b.0, &buffer)); + folds.sort_by(|a, b| a.0.cmp(&b.0, buffer)); let mut fold_ranges = folds .iter() - .map(|fold| fold.0.start.to_offset(&buffer)..fold.0.end.to_offset(&buffer)) + .map(|fold| fold.0.start.to_offset(buffer)..fold.0.end.to_offset(buffer)) .peekable(); let mut merged_ranges = Vec::new(); @@ -1716,8 +1669,9 @@ mod tests { ) -> Vec<(FoldSnapshot, Vec)> { let mut snapshot_edits = Vec::new(); match rng.gen_range(0..=100) { - 0..=39 if !self.folds.is_empty() => { - let buffer = self.buffer.lock().clone(); + 0..=39 if !self.snapshot.folds.is_empty() => { + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = &inlay_snapshot.buffer; let mut to_unfold = Vec::new(); for _ in 0..rng.gen_range(1..=3) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -1725,13 +1679,14 @@ mod tests { to_unfold.push(start..end); } log::info!("unfolding {:?}", to_unfold); - let (mut writer, snapshot, edits) = self.write(buffer, vec![]); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_unfold); snapshot_edits.push((snapshot, edits)); } _ => { - let buffer = self.buffer.lock().clone(); + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = &inlay_snapshot.buffer; let mut to_fold = Vec::new(); for _ in 0..rng.gen_range(1..=2) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -1739,7 +1694,7 @@ mod tests { to_fold.push(start..end); } log::info!("folding {:?}", to_fold); - let (mut writer, snapshot, edits) = self.write(buffer, vec![]); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_fold); snapshot_edits.push((snapshot, edits)); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs new file mode 100644 index 0000000000..affb75f58d --- /dev/null +++ b/crates/editor/src/display_map/inlay_map.rs @@ -0,0 +1,1697 @@ +use crate::{ + multi_buffer::{MultiBufferChunks, MultiBufferRows}, + Anchor, InlayId, MultiBufferSnapshot, ToOffset, +}; +use collections::{BTreeMap, BTreeSet, HashMap}; +use gpui::fonts::HighlightStyle; +use language::{Chunk, Edit, Point, Rope, TextSummary}; +use std::{ + any::TypeId, + cmp, + iter::Peekable, + ops::{Add, AddAssign, Range, Sub, SubAssign}, + vec, +}; +use sum_tree::{Bias, Cursor, SumTree}; +use text::Patch; + +use super::TextHighlights; + +pub struct InlayMap { + snapshot: InlaySnapshot, + inlays_by_id: HashMap, + inlays: Vec, +} + +#[derive(Clone)] +pub struct InlaySnapshot { + pub buffer: MultiBufferSnapshot, + transforms: SumTree, + pub version: usize, +} + +#[derive(Clone, Debug)] +enum Transform { + Isomorphic(TextSummary), + Inlay(Inlay), +} + +#[derive(Debug, Clone)] +pub struct Inlay { + pub id: InlayId, + pub position: Anchor, + pub text: text::Rope, +} + +#[derive(Debug, Clone)] +pub struct InlayProperties { + pub position: Anchor, + pub text: T, +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + match self { + Transform::Isomorphic(summary) => TransformSummary { + input: summary.clone(), + output: summary.clone(), + }, + Transform::Inlay(inlay) => TransformSummary { + input: TextSummary::default(), + output: inlay.text.summary(), + }, + } + } +} + +#[derive(Clone, Debug, Default)] +struct TransformSummary { + input: TextSummary, + output: TextSummary, +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + self.input += &other.input; + self.output += &other.output; + } +} + +pub type InlayEdit = Edit; + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct InlayOffset(pub usize); + +impl Add for InlayOffset { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for InlayOffset { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl AddAssign for InlayOffset { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + +impl SubAssign for InlayOffset { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.len; + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct InlayPoint(pub Point); + +impl Add for InlayPoint { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for InlayPoint { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += &summary.output.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *self += &summary.input.len; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *self += &summary.input.lines; + } +} + +#[derive(Clone)] +pub struct InlayBufferRows<'a> { + transforms: Cursor<'a, Transform, (InlayPoint, Point)>, + buffer_rows: MultiBufferRows<'a>, + inlay_row: u32, + max_buffer_row: u32, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +struct HighlightEndpoint { + offset: InlayOffset, + is_start: bool, + tag: Option, + style: HighlightStyle, +} + +impl PartialOrd for HighlightEndpoint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HighlightEndpoint { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.offset + .cmp(&other.offset) + .then_with(|| other.is_start.cmp(&self.is_start)) + } +} + +pub struct InlayChunks<'a> { + transforms: Cursor<'a, Transform, (InlayOffset, usize)>, + buffer_chunks: MultiBufferChunks<'a>, + buffer_chunk: Option>, + inlay_chunks: Option>, + output_offset: InlayOffset, + max_output_offset: InlayOffset, + hint_highlight_style: Option, + suggestion_highlight_style: Option, + highlight_endpoints: Peekable>, + active_highlights: BTreeMap, HighlightStyle>, + snapshot: &'a InlaySnapshot, +} + +impl<'a> InlayChunks<'a> { + pub fn seek(&mut self, offset: InlayOffset) { + self.transforms.seek(&offset, Bias::Right, &()); + + let buffer_offset = self.snapshot.to_buffer_offset(offset); + self.buffer_chunks.seek(buffer_offset); + self.inlay_chunks = None; + self.buffer_chunk = None; + self.output_offset = offset; + } + + pub fn offset(&self) -> InlayOffset { + self.output_offset + } +} + +impl<'a> Iterator for InlayChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.output_offset == self.max_output_offset { + return None; + } + + let mut next_highlight_endpoint = InlayOffset(usize::MAX); + while let Some(endpoint) = self.highlight_endpoints.peek().copied() { + if endpoint.offset <= self.output_offset { + if endpoint.is_start { + self.active_highlights.insert(endpoint.tag, endpoint.style); + } else { + self.active_highlights.remove(&endpoint.tag); + } + self.highlight_endpoints.next(); + } else { + next_highlight_endpoint = endpoint.offset; + break; + } + } + + let chunk = match self.transforms.item()? { + Transform::Isomorphic(_) => { + let chunk = self + .buffer_chunk + .get_or_insert_with(|| self.buffer_chunks.next().unwrap()); + if chunk.text.is_empty() { + *chunk = self.buffer_chunks.next().unwrap(); + } + + let (prefix, suffix) = chunk.text.split_at( + chunk + .text + .len() + .min(self.transforms.end(&()).0 .0 - self.output_offset.0) + .min(next_highlight_endpoint.0 - self.output_offset.0), + ); + + chunk.text = suffix; + self.output_offset.0 += prefix.len(); + let mut prefix = Chunk { + text: prefix, + ..chunk.clone() + }; + if !self.active_highlights.is_empty() { + let mut highlight_style = HighlightStyle::default(); + for active_highlight in self.active_highlights.values() { + highlight_style.highlight(*active_highlight); + } + prefix.highlight_style = Some(highlight_style); + } + prefix + } + Transform::Inlay(inlay) => { + let inlay_chunks = self.inlay_chunks.get_or_insert_with(|| { + let start = self.output_offset - self.transforms.start().0; + let end = cmp::min(self.max_output_offset, self.transforms.end(&()).0) + - self.transforms.start().0; + inlay.text.chunks_in_range(start.0..end.0) + }); + + let chunk = inlay_chunks.next().unwrap(); + self.output_offset.0 += chunk.len(); + let highlight_style = match inlay.id { + InlayId::Suggestion(_) => self.suggestion_highlight_style, + InlayId::Hint(_) => self.hint_highlight_style, + }; + Chunk { + text: chunk, + highlight_style, + ..Default::default() + } + } + }; + + if self.output_offset == self.transforms.end(&()).0 { + self.inlay_chunks = None; + self.transforms.next(&()); + } + + Some(chunk) + } +} + +impl<'a> InlayBufferRows<'a> { + pub fn seek(&mut self, row: u32) { + let inlay_point = InlayPoint::new(row, 0); + self.transforms.seek(&inlay_point, Bias::Left, &()); + + let mut buffer_point = self.transforms.start().1; + let buffer_row = if row == 0 { + 0 + } else { + match self.transforms.item() { + Some(Transform::Isomorphic(_)) => { + buffer_point += inlay_point.0 - self.transforms.start().0 .0; + buffer_point.row + } + _ => cmp::min(buffer_point.row + 1, self.max_buffer_row), + } + }; + self.inlay_row = inlay_point.row(); + self.buffer_rows.seek(buffer_row); + } +} + +impl<'a> Iterator for InlayBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + let buffer_row = if self.inlay_row == 0 { + self.buffer_rows.next().unwrap() + } else { + match self.transforms.item()? { + Transform::Inlay(_) => None, + Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(), + } + }; + + self.inlay_row += 1; + self.transforms + .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &()); + + Some(buffer_row) + } +} + +impl InlayPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } + + pub fn row(self) -> u32 { + self.0.row + } +} + +impl InlayMap { + pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) { + let version = 0; + let snapshot = InlaySnapshot { + buffer: buffer.clone(), + transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()), + version, + }; + + ( + Self { + snapshot: snapshot.clone(), + inlays_by_id: HashMap::default(), + inlays: Vec::new(), + }, + snapshot, + ) + } + + pub fn sync( + &mut self, + buffer_snapshot: MultiBufferSnapshot, + mut buffer_edits: Vec>, + ) -> (InlaySnapshot, Vec) { + let mut snapshot = &mut self.snapshot; + + if buffer_edits.is_empty() { + if snapshot.buffer.trailing_excerpt_update_count() + != buffer_snapshot.trailing_excerpt_update_count() + { + buffer_edits.push(Edit { + old: snapshot.buffer.len()..snapshot.buffer.len(), + new: buffer_snapshot.len()..buffer_snapshot.len(), + }); + } + } + + if buffer_edits.is_empty() { + if snapshot.buffer.edit_count() != buffer_snapshot.edit_count() + || snapshot.buffer.parse_count() != buffer_snapshot.parse_count() + || snapshot.buffer.diagnostics_update_count() + != buffer_snapshot.diagnostics_update_count() + || snapshot.buffer.git_diff_update_count() + != buffer_snapshot.git_diff_update_count() + || snapshot.buffer.trailing_excerpt_update_count() + != buffer_snapshot.trailing_excerpt_update_count() + { + snapshot.version += 1; + } + + snapshot.buffer = buffer_snapshot; + (snapshot.clone(), Vec::new()) + } else { + let mut inlay_edits = Patch::default(); + let mut new_transforms = SumTree::new(); + let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(); + let mut buffer_edits_iter = buffer_edits.iter().peekable(); + while let Some(buffer_edit) = buffer_edits_iter.next() { + new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); + if let Some(Transform::Isomorphic(transform)) = cursor.item() { + if cursor.end(&()).0 == buffer_edit.old.start { + push_isomorphic(&mut new_transforms, transform.clone()); + cursor.next(&()); + } + } + + // Remove all the inlays and transforms contained by the edit. + let old_start = + cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0); + cursor.seek(&buffer_edit.old.end, Bias::Right, &()); + let old_end = + cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0); + + // Push the unchanged prefix. + let prefix_start = new_transforms.summary().input.len; + let prefix_end = buffer_edit.new.start; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), + ); + let new_start = InlayOffset(new_transforms.summary().output.len); + + let start_ix = match self.inlays.binary_search_by(|probe| { + probe + .position + .to_offset(&buffer_snapshot) + .cmp(&buffer_edit.new.start) + .then(std::cmp::Ordering::Greater) + }) { + Ok(ix) | Err(ix) => ix, + }; + + for inlay in &self.inlays[start_ix..] { + let buffer_offset = inlay.position.to_offset(&buffer_snapshot); + if buffer_offset > buffer_edit.new.end { + break; + } + + let prefix_start = new_transforms.summary().input.len; + let prefix_end = buffer_offset; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), + ); + + if inlay.position.is_valid(&buffer_snapshot) { + new_transforms.push(Transform::Inlay(inlay.clone()), &()); + } + } + + // Apply the rest of the edit. + let transform_start = new_transforms.summary().input.len; + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end), + ); + let new_end = InlayOffset(new_transforms.summary().output.len); + inlay_edits.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + // If the next edit doesn't intersect the current isomorphic transform, then + // we can push its remainder. + if buffer_edits_iter + .peek() + .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) + { + let transform_start = new_transforms.summary().input.len; + let transform_end = + buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end); + push_isomorphic( + &mut new_transforms, + buffer_snapshot.text_summary_for_range(transform_start..transform_end), + ); + cursor.next(&()); + } + } + + new_transforms.append(cursor.suffix(&()), &()); + if new_transforms.is_empty() { + new_transforms.push(Transform::Isomorphic(Default::default()), &()); + } + + drop(cursor); + snapshot.transforms = new_transforms; + snapshot.version += 1; + snapshot.buffer = buffer_snapshot; + snapshot.check_invariants(); + + (snapshot.clone(), inlay_edits.into_inner()) + } + } + + pub fn splice>( + &mut self, + to_remove: Vec, + to_insert: Vec<(InlayId, InlayProperties)>, + ) -> (InlaySnapshot, Vec) { + let snapshot = &mut self.snapshot; + let mut edits = BTreeSet::new(); + + self.inlays.retain(|inlay| !to_remove.contains(&inlay.id)); + for inlay_id in to_remove { + if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) { + let offset = inlay.position.to_offset(&snapshot.buffer); + edits.insert(offset); + } + } + + for (existing_id, properties) in to_insert { + let inlay = Inlay { + id: existing_id, + position: properties.position, + text: properties.text.into(), + }; + + // Avoid inserting empty inlays. + if inlay.text.is_empty() { + continue; + } + + self.inlays_by_id.insert(inlay.id, inlay.clone()); + match self + .inlays + .binary_search_by(|probe| probe.position.cmp(&inlay.position, &snapshot.buffer)) + { + Ok(ix) | Err(ix) => { + self.inlays.insert(ix, inlay.clone()); + } + } + + let offset = inlay.position.to_offset(&snapshot.buffer); + edits.insert(offset); + } + + let buffer_edits = edits + .into_iter() + .map(|offset| Edit { + old: offset..offset, + new: offset..offset, + }) + .collect(); + let buffer_snapshot = snapshot.buffer.clone(); + drop(snapshot); + let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits); + (snapshot, edits) + } + + pub fn current_inlays(&self) -> impl Iterator { + self.inlays.iter() + } + + #[cfg(test)] + pub(crate) fn randomly_mutate( + &mut self, + next_inlay_id: &mut usize, + rng: &mut rand::rngs::StdRng, + ) -> (InlaySnapshot, Vec) { + use rand::prelude::*; + use util::post_inc; + + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + let snapshot = &mut self.snapshot; + for i in 0..rng.gen_range(1..=5) { + if self.inlays.is_empty() || rng.gen() { + let position = snapshot.buffer.random_byte_range(0, rng).start; + let bias = if rng.gen() { Bias::Left } else { Bias::Right }; + let len = if rng.gen_bool(0.01) { + 0 + } else { + rng.gen_range(1..=5) + }; + let text = util::RandomCharIter::new(&mut *rng) + .filter(|ch| *ch != '\r') + .take(len) + .collect::(); + log::info!( + "creating inlay at buffer offset {} with bias {:?} and text {:?}", + position, + bias, + text + ); + + let inlay_id = if i % 2 == 0 { + InlayId::Hint(post_inc(next_inlay_id)) + } else { + InlayId::Suggestion(post_inc(next_inlay_id)) + }; + to_insert.push(( + inlay_id, + InlayProperties { + position: snapshot.buffer.anchor_at(position, bias), + text, + }, + )); + } else { + to_remove.push(*self.inlays_by_id.keys().choose(rng).unwrap()); + } + } + log::info!("removing inlays: {:?}", to_remove); + + drop(snapshot); + let (snapshot, edits) = self.splice(to_remove, to_insert); + (snapshot, edits) + } +} + +impl InlaySnapshot { + pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { + let mut cursor = self + .transforms + .cursor::<(InlayOffset, (InlayPoint, usize))>(); + cursor.seek(&offset, Bias::Right, &()); + let overshoot = offset.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let buffer_offset_start = cursor.start().1 .1; + let buffer_offset_end = buffer_offset_start + overshoot; + let buffer_start = self.buffer.offset_to_point(buffer_offset_start); + let buffer_end = self.buffer.offset_to_point(buffer_offset_end); + InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start)) + } + Some(Transform::Inlay(inlay)) => { + let overshoot = inlay.text.offset_to_point(overshoot); + InlayPoint(cursor.start().1 .0 .0 + overshoot) + } + None => self.max_point(), + } + } + + pub fn len(&self) -> InlayOffset { + InlayOffset(self.transforms.summary().output.len) + } + + pub fn max_point(&self) -> InlayPoint { + InlayPoint(self.transforms.summary().output.lines) + } + + pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { + let mut cursor = self + .transforms + .cursor::<(InlayPoint, (InlayOffset, Point))>(); + cursor.seek(&point, Bias::Right, &()); + let overshoot = point.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let buffer_point_start = cursor.start().1 .1; + let buffer_point_end = buffer_point_start + overshoot; + let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start); + let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end); + InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start)) + } + Some(Transform::Inlay(inlay)) => { + let overshoot = inlay.text.point_to_offset(overshoot); + InlayOffset(cursor.start().1 .0 .0 + overshoot) + } + None => self.len(), + } + } + + pub fn to_buffer_point(&self, point: InlayPoint) -> Point { + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); + cursor.seek(&point, Bias::Right, &()); + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let overshoot = point.0 - cursor.start().0 .0; + cursor.start().1 + overshoot + } + Some(Transform::Inlay(_)) => cursor.start().1, + None => self.buffer.max_point(), + } + } + + pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize { + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); + cursor.seek(&offset, Bias::Right, &()); + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let overshoot = offset - cursor.start().0; + cursor.start().1 + overshoot.0 + } + Some(Transform::Inlay(_)) => cursor.start().1, + None => self.buffer.len(), + } + } + + pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { + let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(); + cursor.seek(&offset, Bias::Left, &()); + loop { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + if offset == cursor.end(&()).0 { + while let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + break; + } else { + cursor.next(&()); + } + } + return cursor.end(&()).1; + } else { + let overshoot = offset - cursor.start().0; + return InlayOffset(cursor.start().1 .0 + overshoot); + } + } + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + cursor.next(&()); + } else { + return cursor.start().1; + } + } + None => { + return self.len(); + } + } + } + } + + pub fn to_inlay_point(&self, point: Point) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(); + cursor.seek(&point, Bias::Left, &()); + loop { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + if point == cursor.end(&()).0 { + while let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + break; + } else { + cursor.next(&()); + } + } + return cursor.end(&()).1; + } else { + let overshoot = point - cursor.start().0; + return InlayPoint(cursor.start().1 .0 + overshoot); + } + } + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + cursor.next(&()); + } else { + return cursor.start().1; + } + } + None => { + return self.max_point(); + } + } + } + } + + pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); + cursor.seek(&point, Bias::Left, &()); + loop { + match cursor.item() { + Some(Transform::Isomorphic(transform)) => { + if cursor.start().0 == point { + if let Some(Transform::Inlay(inlay)) = cursor.prev_item() { + if inlay.position.bias() == Bias::Left { + return point; + } else if bias == Bias::Left { + cursor.prev(&()); + } else if transform.first_line_chars == 0 { + point.0 += Point::new(1, 0); + } else { + point.0 += Point::new(0, 1); + } + } else { + return point; + } + } else if cursor.end(&()).0 == point { + if let Some(Transform::Inlay(inlay)) = cursor.next_item() { + if inlay.position.bias() == Bias::Right { + return point; + } else if bias == Bias::Right { + cursor.next(&()); + } else if point.0.column == 0 { + point.0.row -= 1; + point.0.column = self.line_len(point.0.row); + } else { + point.0.column -= 1; + } + } else { + return point; + } + } else { + let overshoot = point.0 - cursor.start().0 .0; + let buffer_point = cursor.start().1 + overshoot; + let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias); + let clipped_overshoot = clipped_buffer_point - cursor.start().1; + let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot); + if clipped_point == point { + return clipped_point; + } else { + point = clipped_point; + } + } + } + Some(Transform::Inlay(inlay)) => { + if point == cursor.start().0 && inlay.position.bias() == Bias::Right { + match cursor.prev_item() { + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Left { + return point; + } + } + _ => return point, + } + } else if point == cursor.end(&()).0 && inlay.position.bias() == Bias::Left { + match cursor.next_item() { + Some(Transform::Inlay(inlay)) => { + if inlay.position.bias() == Bias::Right { + return point; + } + } + _ => return point, + } + } + + if bias == Bias::Left { + point = cursor.start().0; + cursor.prev(&()); + } else { + cursor.next(&()); + point = cursor.start().0; + } + } + None => { + bias = bias.invert(); + if bias == Bias::Left { + point = cursor.start().0; + cursor.prev(&()); + } else { + cursor.next(&()); + point = cursor.start().0; + } + } + } + } + } + + pub fn text_summary(&self) -> TextSummary { + self.transforms.summary().output.clone() + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + let mut summary = TextSummary::default(); + + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); + cursor.seek(&range.start, Bias::Right, &()); + + let overshoot = range.start.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let buffer_start = cursor.start().1; + let suffix_start = buffer_start + overshoot; + let suffix_end = + buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0); + summary = self.buffer.text_summary_for_range(suffix_start..suffix_end); + cursor.next(&()); + } + Some(Transform::Inlay(inlay)) => { + let suffix_start = overshoot; + let suffix_end = cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0; + summary = inlay.text.cursor(suffix_start).summary(suffix_end); + cursor.next(&()); + } + None => {} + } + + if range.end > cursor.start().0 { + summary += cursor + .summary::<_, TransformSummary>(&range.end, Bias::Right, &()) + .output; + + let overshoot = range.end.0 - cursor.start().0 .0; + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + let prefix_start = cursor.start().1; + let prefix_end = prefix_start + overshoot; + summary += self + .buffer + .text_summary_for_range::(prefix_start..prefix_end); + } + Some(Transform::Inlay(inlay)) => { + let prefix_end = overshoot; + summary += inlay.text.cursor(0).summary::(prefix_end); + } + None => {} + } + } + + summary + } + + pub fn buffer_rows<'a>(&'a self, row: u32) -> InlayBufferRows<'a> { + let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(); + let inlay_point = InlayPoint::new(row, 0); + cursor.seek(&inlay_point, Bias::Left, &()); + + let max_buffer_row = self.buffer.max_point().row; + let mut buffer_point = cursor.start().1; + let buffer_row = if row == 0 { + 0 + } else { + match cursor.item() { + Some(Transform::Isomorphic(_)) => { + buffer_point += inlay_point.0 - cursor.start().0 .0; + buffer_point.row + } + _ => cmp::min(buffer_point.row + 1, max_buffer_row), + } + }; + + InlayBufferRows { + transforms: cursor, + inlay_row: inlay_point.row(), + buffer_rows: self.buffer.buffer_rows(buffer_row), + max_buffer_row, + } + } + + pub fn line_len(&self, row: u32) -> u32 { + let line_start = self.to_offset(InlayPoint::new(row, 0)).0; + let line_end = if row >= self.max_point().row() { + self.len().0 + } else { + self.to_offset(InlayPoint::new(row + 1, 0)).0 - 1 + }; + (line_end - line_start) as u32 + } + + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + text_highlights: Option<&'a TextHighlights>, + hint_highlights: Option, + suggestion_highlights: Option, + ) -> InlayChunks<'a> { + let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(); + cursor.seek(&range.start, Bias::Right, &()); + + let mut highlight_endpoints = Vec::new(); + if let Some(text_highlights) = text_highlights { + if !text_highlights.is_empty() { + while cursor.start().0 < range.end { + if true { + let transform_start = self.buffer.anchor_after( + self.to_buffer_offset(cmp::max(range.start, cursor.start().0)), + ); + + let transform_end = { + let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0); + self.buffer.anchor_before(self.to_buffer_offset(cmp::min( + cursor.end(&()).0, + cursor.start().0 + overshoot, + ))) + }; + + for (tag, highlights) in text_highlights.iter() { + let style = highlights.0; + let ranges = &highlights.1; + + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&transform_start, &self.buffer); + if cmp.is_gt() { + cmp::Ordering::Greater + } else { + cmp::Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + for range in &ranges[start_ix..] { + if range.start.cmp(&transform_end, &self.buffer).is_ge() { + break; + } + + highlight_endpoints.push(HighlightEndpoint { + offset: self + .to_inlay_offset(range.start.to_offset(&self.buffer)), + is_start: true, + tag: *tag, + style, + }); + highlight_endpoints.push(HighlightEndpoint { + offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)), + is_start: false, + tag: *tag, + style, + }); + } + } + } + + cursor.next(&()); + } + highlight_endpoints.sort(); + cursor.seek(&range.start, Bias::Right, &()); + } + } + + let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); + let buffer_chunks = self.buffer.chunks(buffer_range, language_aware); + + InlayChunks { + transforms: cursor, + buffer_chunks, + inlay_chunks: None, + buffer_chunk: None, + output_offset: range.start, + max_output_offset: range.end, + hint_highlight_style: hint_highlights, + suggestion_highlight_style: suggestion_highlights, + highlight_endpoints: highlight_endpoints.into_iter().peekable(), + active_highlights: Default::default(), + snapshot: self, + } + } + + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks(Default::default()..self.len(), false, None, None, None) + .map(|chunk| chunk.text) + .collect() + } + + fn check_invariants(&self) { + #[cfg(any(debug_assertions, feature = "test-support"))] + { + assert_eq!(self.transforms.summary().input, self.buffer.text_summary()); + let mut transforms = self.transforms.iter().peekable(); + while let Some(transform) = transforms.next() { + let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_)); + if let Some(next_transform) = transforms.peek() { + let next_transform_is_isomorphic = + matches!(next_transform, Transform::Isomorphic(_)); + assert!( + !transform_is_isomorphic || !next_transform_is_isomorphic, + "two adjacent isomorphic transforms" + ); + } + } + } + } +} + +fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { + if summary.len == 0 { + return; + } + + let mut summary = Some(summary); + sum_tree.update_last( + |transform| { + if let Transform::Isomorphic(transform) = transform { + *transform += summary.take().unwrap(); + } + }, + &(), + ); + + if let Some(summary) = summary { + sum_tree.push(Transform::Isomorphic(summary), &()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{InlayId, MultiBuffer}; + use gpui::AppContext; + use rand::prelude::*; + use settings::SettingsStore; + use std::{cmp::Reverse, env, sync::Arc}; + use sum_tree::TreeMap; + use text::Patch; + use util::post_inc; + + #[gpui::test] + fn test_basic_inlays(cx: &mut AppContext) { + let buffer = MultiBuffer::build_simple("abcdefghi", cx); + let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + assert_eq!(inlay_snapshot.text(), "abcdefghi"); + let mut next_inlay_id = 0; + + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![( + InlayId::Hint(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|123|", + }, + )], + ); + assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 0)), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 1)), + InlayPoint::new(0, 1) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 2)), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 3)), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 4)), + InlayPoint::new(0, 9) + ); + assert_eq!( + inlay_snapshot.to_inlay_point(Point::new(0, 5)), + InlayPoint::new(0, 10) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left), + InlayPoint::new(0, 3) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right), + InlayPoint::new(0, 9) + ); + + // Edits before or after the inlay should not affect it. + buffer.update(cx, |buffer, cx| { + buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx) + }); + let (inlay_snapshot, _) = inlay_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi"); + + // An edit surrounding the inlay should invalidate it. + buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx)); + let (inlay_snapshot, _) = inlay_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); + + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![ + ( + InlayId::Hint(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(3), + text: "|123|", + }, + ), + ( + InlayId::Suggestion(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_after(3), + text: "|456|", + }, + ), + ], + ); + assert_eq!(inlay_snapshot.text(), "abx|123||456|yDzefghi"); + + // Edits ending where the inlay starts should not move it if it has a left bias. + buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx)); + let (inlay_snapshot, _) = inlay_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + ); + assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Left), + InlayPoint::new(0, 0) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 0), Bias::Right), + InlayPoint::new(0, 0) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Left), + InlayPoint::new(0, 1) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 1), Bias::Right), + InlayPoint::new(0, 1) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 2), Bias::Right), + InlayPoint::new(0, 2) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 3), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 4), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 5), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 6), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Left), + InlayPoint::new(0, 2) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 7), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Left), + InlayPoint::new(0, 8) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 8), Bias::Right), + InlayPoint::new(0, 8) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Left), + InlayPoint::new(0, 9) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 9), Bias::Right), + InlayPoint::new(0, 9) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Left), + InlayPoint::new(0, 10) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 10), Bias::Right), + InlayPoint::new(0, 10) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 11), Bias::Right), + InlayPoint::new(0, 11) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 12), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 13), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 14), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 15), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Left), + InlayPoint::new(0, 11) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 16), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Left), + InlayPoint::new(0, 17) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 17), Bias::Right), + InlayPoint::new(0, 17) + ); + + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Left), + InlayPoint::new(0, 18) + ); + assert_eq!( + inlay_snapshot.clip_point(InlayPoint::new(0, 18), Bias::Right), + InlayPoint::new(0, 18) + ); + + // The inlays can be manually removed. + let (inlay_snapshot, _) = inlay_map + .splice::(inlay_map.inlays_by_id.keys().copied().collect(), Vec::new()); + assert_eq!(inlay_snapshot.text(), "abxJKLyDzefghi"); + } + + #[gpui::test] + fn test_inlay_buffer_rows(cx: &mut AppContext) { + let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); + let mut next_inlay_id = 0; + + let (inlay_snapshot, _) = inlay_map.splice( + Vec::new(), + vec![ + ( + InlayId::Hint(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(0), + text: "|123|\n", + }, + ), + ( + InlayId::Hint(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(4), + text: "|456|", + }, + ), + ( + InlayId::Suggestion(post_inc(&mut next_inlay_id)), + InlayProperties { + position: buffer.read(cx).snapshot(cx).anchor_before(7), + text: "\n|567|\n", + }, + ), + ], + ); + assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi"); + assert_eq!( + inlay_snapshot.buffer_rows(0).collect::>(), + vec![Some(0), None, Some(1), None, None, Some(2)] + ); + } + + #[gpui::test(iterations = 100)] + fn test_random_inlays(cx: &mut AppContext, mut rng: StdRng) { + init_test(cx); + + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let len = rng.gen_range(0..30); + let buffer = if rng.gen() { + let text = util::RandomCharIter::new(&mut rng) + .take(len) + .collect::(); + MultiBuffer::build_simple(&text, cx) + } else { + MultiBuffer::build_random(&mut rng, cx) + }; + let mut buffer_snapshot = buffer.read(cx).snapshot(cx); + let mut next_inlay_id = 0; + log::info!("buffer text: {:?}", buffer_snapshot.text()); + + let mut highlights = TreeMap::default(); + let highlight_count = rng.gen_range(0_usize..10); + let mut highlight_ranges = (0..highlight_count) + .map(|_| buffer_snapshot.random_byte_range(0, &mut rng)) + .collect::>(); + highlight_ranges.sort_by_key(|range| (range.start, Reverse(range.end))); + log::info!("highlighting ranges {:?}", highlight_ranges); + let highlight_ranges = highlight_ranges + .into_iter() + .map(|range| { + buffer_snapshot.anchor_before(range.start)..buffer_snapshot.anchor_after(range.end) + }) + .collect::>(); + + highlights.insert( + Some(TypeId::of::<()>()), + Arc::new((HighlightStyle::default(), highlight_ranges)), + ); + + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + for _ in 0..operations { + let mut inlay_edits = Patch::default(); + + let mut prev_inlay_text = inlay_snapshot.text(); + let mut buffer_edits = Vec::new(); + match rng.gen_range(0..=100) { + 0..=50 => { + let (snapshot, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + log::info!("mutated text: {:?}", snapshot.text()); + inlay_edits = Patch::new(edits); + } + _ => buffer.update(cx, |buffer, cx| { + let subscription = buffer.subscribe(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_mutate(&mut rng, edit_count, cx); + buffer_snapshot = buffer.snapshot(cx); + let edits = subscription.consume().into_inner(); + log::info!("editing {:?}", edits); + buffer_edits.extend(edits); + }), + }; + + let (new_inlay_snapshot, new_inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + inlay_snapshot = new_inlay_snapshot; + inlay_edits = inlay_edits.compose(new_inlay_edits); + + log::info!("buffer text: {:?}", buffer_snapshot.text()); + log::info!("inlay text: {:?}", inlay_snapshot.text()); + + let inlays = inlay_map + .inlays + .iter() + .filter(|inlay| inlay.position.is_valid(&buffer_snapshot)) + .map(|inlay| { + let offset = inlay.position.to_offset(&buffer_snapshot); + (offset, inlay.clone()) + }) + .collect::>(); + let mut expected_text = Rope::from(buffer_snapshot.text().as_str()); + for (offset, inlay) in inlays.into_iter().rev() { + expected_text.replace(offset..offset, &inlay.text.to_string()); + } + assert_eq!(inlay_snapshot.text(), expected_text.to_string()); + + let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::>(); + assert_eq!( + expected_buffer_rows.len() as u32, + expected_text.max_point().row + 1 + ); + for row_start in 0..expected_buffer_rows.len() { + assert_eq!( + inlay_snapshot + .buffer_rows(row_start as u32) + .collect::>(), + &expected_buffer_rows[row_start..], + "incorrect buffer rows starting at {}", + row_start + ); + } + + for _ in 0..5 { + let mut end = rng.gen_range(0..=inlay_snapshot.len().0); + end = expected_text.clip_offset(end, Bias::Right); + let mut start = rng.gen_range(0..=end); + start = expected_text.clip_offset(start, Bias::Right); + + let actual_text = inlay_snapshot + .chunks( + InlayOffset(start)..InlayOffset(end), + false, + Some(&highlights), + None, + None, + ) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!( + actual_text, + expected_text.slice(start..end).to_string(), + "incorrect text in range {:?}", + start..end + ); + + assert_eq!( + inlay_snapshot.text_summary_for_range(InlayOffset(start)..InlayOffset(end)), + expected_text.slice(start..end).summary() + ); + } + + for edit in inlay_edits { + prev_inlay_text.replace_range( + edit.new.start.0..edit.new.start.0 + edit.old_len().0, + &inlay_snapshot.text()[edit.new.start.0..edit.new.end.0], + ); + } + assert_eq!(prev_inlay_text, inlay_snapshot.text()); + + assert_eq!(expected_text.max_point(), inlay_snapshot.max_point().0); + assert_eq!(expected_text.len(), inlay_snapshot.len().0); + + let mut buffer_point = Point::default(); + let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + let mut buffer_chars = buffer_snapshot.chars_at(0); + loop { + // Ensure conversion from buffer coordinates to inlay coordinates + // is consistent. + let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); + assert_eq!( + inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)), + inlay_point + ); + + // No matter which bias we clip an inlay point with, it doesn't move + // because it was constructed from a buffer point. + assert_eq!( + inlay_snapshot.clip_point(inlay_point, Bias::Left), + inlay_point, + "invalid inlay point for buffer point {:?} when clipped left", + buffer_point + ); + assert_eq!( + inlay_snapshot.clip_point(inlay_point, Bias::Right), + inlay_point, + "invalid inlay point for buffer point {:?} when clipped right", + buffer_point + ); + + if let Some(ch) = buffer_chars.next() { + if ch == '\n' { + buffer_point += Point::new(1, 0); + } else { + buffer_point += Point::new(0, ch.len_utf8() as u32); + } + + // Ensure that moving forward in the buffer always moves the inlay point forward as well. + let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + assert!(new_inlay_point > inlay_point); + inlay_point = new_inlay_point; + } else { + break; + } + } + + let mut inlay_point = InlayPoint::default(); + let mut inlay_offset = InlayOffset::default(); + for ch in expected_text.chars() { + assert_eq!( + inlay_snapshot.to_offset(inlay_point), + inlay_offset, + "invalid to_offset({:?})", + inlay_point + ); + assert_eq!( + inlay_snapshot.to_point(inlay_offset), + inlay_point, + "invalid to_point({:?})", + inlay_offset + ); + + let mut bytes = [0; 4]; + for byte in ch.encode_utf8(&mut bytes).as_bytes() { + inlay_offset.0 += 1; + if *byte == b'\n' { + inlay_point.0 += Point::new(1, 0); + } else { + inlay_point.0 += Point::new(0, 1); + } + + let clipped_left_point = inlay_snapshot.clip_point(inlay_point, Bias::Left); + let clipped_right_point = inlay_snapshot.clip_point(inlay_point, Bias::Right); + assert!( + clipped_left_point <= clipped_right_point, + "inlay point {:?} when clipped left is greater than when clipped right ({:?} > {:?})", + inlay_point, + clipped_left_point, + clipped_right_point + ); + + // Ensure the clipped points are at valid text locations. + assert_eq!( + clipped_left_point.0, + expected_text.clip_point(clipped_left_point.0, Bias::Left) + ); + assert_eq!( + clipped_right_point.0, + expected_text.clip_point(clipped_right_point.0, Bias::Right) + ); + + // Ensure the clipped points never overshoot the end of the map. + assert!(clipped_left_point <= inlay_snapshot.max_point()); + assert!(clipped_right_point <= inlay_snapshot.max_point()); + + // Ensure the clipped points are at valid buffer locations. + assert_eq!( + inlay_snapshot + .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)), + clipped_left_point, + "to_buffer_point({:?}) = {:?}", + clipped_left_point, + inlay_snapshot.to_buffer_point(clipped_left_point), + ); + assert_eq!( + inlay_snapshot + .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)), + clipped_right_point, + "to_buffer_point({:?}) = {:?}", + clipped_right_point, + inlay_snapshot.to_buffer_point(clipped_right_point), + ); + } + } + } + } + + fn init_test(cx: &mut AppContext) { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + } +} diff --git a/crates/editor/src/display_map/suggestion_map.rs b/crates/editor/src/display_map/suggestion_map.rs deleted file mode 100644 index eac903d0af..0000000000 --- a/crates/editor/src/display_map/suggestion_map.rs +++ /dev/null @@ -1,871 +0,0 @@ -use super::{ - fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldPoint, FoldSnapshot}, - TextHighlights, -}; -use crate::{MultiBufferSnapshot, ToPoint}; -use gpui::fonts::HighlightStyle; -use language::{Bias, Chunk, Edit, Patch, Point, Rope, TextSummary}; -use parking_lot::Mutex; -use std::{ - cmp, - ops::{Add, AddAssign, Range, Sub}, -}; -use util::post_inc; - -pub type SuggestionEdit = Edit; - -#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct SuggestionOffset(pub usize); - -impl Add for SuggestionOffset { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl Sub for SuggestionOffset { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl AddAssign for SuggestionOffset { - fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0; - } -} - -#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct SuggestionPoint(pub Point); - -impl SuggestionPoint { - pub fn new(row: u32, column: u32) -> Self { - Self(Point::new(row, column)) - } - - pub fn row(self) -> u32 { - self.0.row - } - - pub fn column(self) -> u32 { - self.0.column - } -} - -#[derive(Clone, Debug)] -pub struct Suggestion { - pub position: T, - pub text: Rope, -} - -pub struct SuggestionMap(Mutex); - -impl SuggestionMap { - pub fn new(fold_snapshot: FoldSnapshot) -> (Self, SuggestionSnapshot) { - let snapshot = SuggestionSnapshot { - fold_snapshot, - suggestion: None, - version: 0, - }; - (Self(Mutex::new(snapshot.clone())), snapshot) - } - - pub fn replace( - &self, - new_suggestion: Option>, - fold_snapshot: FoldSnapshot, - fold_edits: Vec, - ) -> ( - SuggestionSnapshot, - Vec, - Option>, - ) - where - T: ToPoint, - { - let new_suggestion = new_suggestion.map(|new_suggestion| { - let buffer_point = new_suggestion - .position - .to_point(fold_snapshot.buffer_snapshot()); - let fold_point = fold_snapshot.to_fold_point(buffer_point, Bias::Left); - let fold_offset = fold_point.to_offset(&fold_snapshot); - Suggestion { - position: fold_offset, - text: new_suggestion.text, - } - }); - - let (_, edits) = self.sync(fold_snapshot, fold_edits); - let mut snapshot = self.0.lock(); - - let mut patch = Patch::new(edits); - let old_suggestion = snapshot.suggestion.take(); - if let Some(suggestion) = &old_suggestion { - patch = patch.compose([SuggestionEdit { - old: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()), - new: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0), - }]); - } - - if let Some(suggestion) = new_suggestion.as_ref() { - patch = patch.compose([SuggestionEdit { - old: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0), - new: SuggestionOffset(suggestion.position.0) - ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()), - }]); - } - - snapshot.suggestion = new_suggestion; - snapshot.version += 1; - (snapshot.clone(), patch.into_inner(), old_suggestion) - } - - pub fn sync( - &self, - fold_snapshot: FoldSnapshot, - fold_edits: Vec, - ) -> (SuggestionSnapshot, Vec) { - let mut snapshot = self.0.lock(); - - if snapshot.fold_snapshot.version != fold_snapshot.version { - snapshot.version += 1; - } - - let mut suggestion_edits = Vec::new(); - - let mut suggestion_old_len = 0; - let mut suggestion_new_len = 0; - for fold_edit in fold_edits { - let start = fold_edit.new.start; - let end = FoldOffset(start.0 + fold_edit.old_len().0); - if let Some(suggestion) = snapshot.suggestion.as_mut() { - if end <= suggestion.position { - suggestion.position.0 += fold_edit.new_len().0; - suggestion.position.0 -= fold_edit.old_len().0; - } else if start > suggestion.position { - suggestion_old_len = suggestion.text.len(); - suggestion_new_len = suggestion_old_len; - } else { - suggestion_old_len = suggestion.text.len(); - snapshot.suggestion.take(); - suggestion_edits.push(SuggestionEdit { - old: SuggestionOffset(fold_edit.old.start.0) - ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len), - new: SuggestionOffset(fold_edit.new.start.0) - ..SuggestionOffset(fold_edit.new.end.0), - }); - continue; - } - } - - suggestion_edits.push(SuggestionEdit { - old: SuggestionOffset(fold_edit.old.start.0 + suggestion_old_len) - ..SuggestionOffset(fold_edit.old.end.0 + suggestion_old_len), - new: SuggestionOffset(fold_edit.new.start.0 + suggestion_new_len) - ..SuggestionOffset(fold_edit.new.end.0 + suggestion_new_len), - }); - } - snapshot.fold_snapshot = fold_snapshot; - - (snapshot.clone(), suggestion_edits) - } - - pub fn has_suggestion(&self) -> bool { - let snapshot = self.0.lock(); - snapshot.suggestion.is_some() - } -} - -#[derive(Clone)] -pub struct SuggestionSnapshot { - pub fold_snapshot: FoldSnapshot, - pub suggestion: Option>, - pub version: usize, -} - -impl SuggestionSnapshot { - pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.fold_snapshot.buffer_snapshot() - } - - pub fn max_point(&self) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_point = suggestion.position.to_point(&self.fold_snapshot); - let mut max_point = suggestion_point.0; - max_point += suggestion.text.max_point(); - max_point += self.fold_snapshot.max_point().0 - suggestion_point.0; - SuggestionPoint(max_point) - } else { - SuggestionPoint(self.fold_snapshot.max_point().0) - } - } - - pub fn len(&self) -> SuggestionOffset { - if let Some(suggestion) = self.suggestion.as_ref() { - let mut len = suggestion.position.0; - len += suggestion.text.len(); - len += self.fold_snapshot.len().0 - suggestion.position.0; - SuggestionOffset(len) - } else { - SuggestionOffset(self.fold_snapshot.len().0) - } - } - - pub fn line_len(&self, row: u32) -> u32 { - if let Some(suggestion) = &self.suggestion { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - - if row < suggestion_start.row { - self.fold_snapshot.line_len(row) - } else if row > suggestion_end.row { - self.fold_snapshot - .line_len(suggestion_start.row + (row - suggestion_end.row)) - } else { - let mut result = suggestion.text.line_len(row - suggestion_start.row); - if row == suggestion_start.row { - result += suggestion_start.column; - } - if row == suggestion_end.row { - result += - self.fold_snapshot.line_len(suggestion_start.row) - suggestion_start.column; - } - result - } - } else { - self.fold_snapshot.line_len(row) - } - } - - pub fn clip_point(&self, point: SuggestionPoint, bias: Bias) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - if point.0 <= suggestion_start { - SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0) - } else if point.0 > suggestion_end { - let fold_point = self.fold_snapshot.clip_point( - FoldPoint(suggestion_start + (point.0 - suggestion_end)), - bias, - ); - let suggestion_point = suggestion_end + (fold_point.0 - suggestion_start); - if bias == Bias::Left && suggestion_point == suggestion_end { - SuggestionPoint(suggestion_start) - } else { - SuggestionPoint(suggestion_point) - } - } else if bias == Bias::Left || suggestion_start == self.fold_snapshot.max_point().0 { - SuggestionPoint(suggestion_start) - } else { - let fold_point = if self.fold_snapshot.line_len(suggestion_start.row) - > suggestion_start.column - { - FoldPoint(suggestion_start + Point::new(0, 1)) - } else { - FoldPoint(suggestion_start + Point::new(1, 0)) - }; - let clipped_fold_point = self.fold_snapshot.clip_point(fold_point, bias); - SuggestionPoint(suggestion_end + (clipped_fold_point.0 - suggestion_start)) - } - } else { - SuggestionPoint(self.fold_snapshot.clip_point(FoldPoint(point.0), bias).0) - } - } - - pub fn to_offset(&self, point: SuggestionPoint) -> SuggestionOffset { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - - if point.0 <= suggestion_start { - SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0) - } else if point.0 > suggestion_end { - let fold_offset = FoldPoint(suggestion_start + (point.0 - suggestion_end)) - .to_offset(&self.fold_snapshot); - SuggestionOffset(fold_offset.0 + suggestion.text.len()) - } else { - let offset_in_suggestion = - suggestion.text.point_to_offset(point.0 - suggestion_start); - SuggestionOffset(suggestion.position.0 + offset_in_suggestion) - } - } else { - SuggestionOffset(FoldPoint(point.0).to_offset(&self.fold_snapshot).0) - } - } - - pub fn to_point(&self, offset: SuggestionOffset) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_point_start = suggestion.position.to_point(&self.fold_snapshot).0; - if offset.0 <= suggestion.position.0 { - SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0) - } else if offset.0 > (suggestion.position.0 + suggestion.text.len()) { - let fold_point = FoldOffset(offset.0 - suggestion.text.len()) - .to_point(&self.fold_snapshot) - .0; - - SuggestionPoint( - suggestion_point_start - + suggestion.text.max_point() - + (fold_point - suggestion_point_start), - ) - } else { - let point_in_suggestion = suggestion - .text - .offset_to_point(offset.0 - suggestion.position.0); - SuggestionPoint(suggestion_point_start + point_in_suggestion) - } - } else { - SuggestionPoint(FoldOffset(offset.0).to_point(&self.fold_snapshot).0) - } - } - - pub fn to_fold_point(&self, point: SuggestionPoint) -> FoldPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - - if point.0 <= suggestion_start { - FoldPoint(point.0) - } else if point.0 > suggestion_end { - FoldPoint(suggestion_start + (point.0 - suggestion_end)) - } else { - FoldPoint(suggestion_start) - } - } else { - FoldPoint(point.0) - } - } - - pub fn to_suggestion_point(&self, point: FoldPoint) -> SuggestionPoint { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - - if point.0 <= suggestion_start { - SuggestionPoint(point.0) - } else { - let suggestion_end = suggestion_start + suggestion.text.max_point(); - SuggestionPoint(suggestion_end + (point.0 - suggestion_start)) - } - } else { - SuggestionPoint(point.0) - } - } - - pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&self.fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - let mut summary = TextSummary::default(); - - let prefix_range = - cmp::min(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_start); - if prefix_range.start < prefix_range.end { - summary += self.fold_snapshot.text_summary_for_range( - FoldPoint(prefix_range.start)..FoldPoint(prefix_range.end), - ); - } - - let suggestion_range = - cmp::max(range.start.0, suggestion_start)..cmp::min(range.end.0, suggestion_end); - if suggestion_range.start < suggestion_range.end { - let point_range = suggestion_range.start - suggestion_start - ..suggestion_range.end - suggestion_start; - let offset_range = suggestion.text.point_to_offset(point_range.start) - ..suggestion.text.point_to_offset(point_range.end); - summary += suggestion - .text - .cursor(offset_range.start) - .summary::(offset_range.end); - } - - let suffix_range = cmp::max(range.start.0, suggestion_end)..range.end.0; - if suffix_range.start < suffix_range.end { - let start = suggestion_start + (suffix_range.start - suggestion_end); - let end = suggestion_start + (suffix_range.end - suggestion_end); - summary += self - .fold_snapshot - .text_summary_for_range(FoldPoint(start)..FoldPoint(end)); - } - - summary - } else { - self.fold_snapshot - .text_summary_for_range(FoldPoint(range.start.0)..FoldPoint(range.end.0)) - } - } - - pub fn chars_at(&self, start: SuggestionPoint) -> impl '_ + Iterator { - let start = self.to_offset(start); - self.chunks(start..self.len(), false, None, None) - .flat_map(|chunk| chunk.text.chars()) - } - - pub fn chunks<'a>( - &'a self, - range: Range, - language_aware: bool, - text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, - ) -> SuggestionChunks<'a> { - if let Some(suggestion) = self.suggestion.as_ref() { - let suggestion_range = - suggestion.position.0..suggestion.position.0 + suggestion.text.len(); - - let prefix_chunks = if range.start.0 < suggestion_range.start { - Some(self.fold_snapshot.chunks( - FoldOffset(range.start.0) - ..cmp::min(FoldOffset(suggestion_range.start), FoldOffset(range.end.0)), - language_aware, - text_highlights, - )) - } else { - None - }; - - let clipped_suggestion_range = cmp::max(range.start.0, suggestion_range.start) - ..cmp::min(range.end.0, suggestion_range.end); - let suggestion_chunks = if clipped_suggestion_range.start < clipped_suggestion_range.end - { - let start = clipped_suggestion_range.start - suggestion_range.start; - let end = clipped_suggestion_range.end - suggestion_range.start; - Some(suggestion.text.chunks_in_range(start..end)) - } else { - None - }; - - let suffix_chunks = if range.end.0 > suggestion_range.end { - let start = cmp::max(suggestion_range.end, range.start.0) - suggestion_range.len(); - let end = range.end.0 - suggestion_range.len(); - Some(self.fold_snapshot.chunks( - FoldOffset(start)..FoldOffset(end), - language_aware, - text_highlights, - )) - } else { - None - }; - - SuggestionChunks { - prefix_chunks, - suggestion_chunks, - suffix_chunks, - highlight_style: suggestion_highlight, - } - } else { - SuggestionChunks { - prefix_chunks: Some(self.fold_snapshot.chunks( - FoldOffset(range.start.0)..FoldOffset(range.end.0), - language_aware, - text_highlights, - )), - suggestion_chunks: None, - suffix_chunks: None, - highlight_style: None, - } - } - } - - pub fn buffer_rows<'a>(&'a self, row: u32) -> SuggestionBufferRows<'a> { - let suggestion_range = if let Some(suggestion) = self.suggestion.as_ref() { - let start = suggestion.position.to_point(&self.fold_snapshot).0; - let end = start + suggestion.text.max_point(); - start.row..end.row - } else { - u32::MAX..u32::MAX - }; - - let fold_buffer_rows = if row <= suggestion_range.start { - self.fold_snapshot.buffer_rows(row) - } else if row > suggestion_range.end { - self.fold_snapshot - .buffer_rows(row - (suggestion_range.end - suggestion_range.start)) - } else { - let mut rows = self.fold_snapshot.buffer_rows(suggestion_range.start); - rows.next(); - rows - }; - - SuggestionBufferRows { - current_row: row, - suggestion_row_start: suggestion_range.start, - suggestion_row_end: suggestion_range.end, - fold_buffer_rows, - } - } - - #[cfg(test)] - pub fn text(&self) -> String { - self.chunks(Default::default()..self.len(), false, None, None) - .map(|chunk| chunk.text) - .collect() - } -} - -pub struct SuggestionChunks<'a> { - prefix_chunks: Option>, - suggestion_chunks: Option>, - suffix_chunks: Option>, - highlight_style: Option, -} - -impl<'a> Iterator for SuggestionChunks<'a> { - type Item = Chunk<'a>; - - fn next(&mut self) -> Option { - if let Some(chunks) = self.prefix_chunks.as_mut() { - if let Some(chunk) = chunks.next() { - return Some(chunk); - } else { - self.prefix_chunks = None; - } - } - - if let Some(chunks) = self.suggestion_chunks.as_mut() { - if let Some(chunk) = chunks.next() { - return Some(Chunk { - text: chunk, - highlight_style: self.highlight_style, - ..Default::default() - }); - } else { - self.suggestion_chunks = None; - } - } - - if let Some(chunks) = self.suffix_chunks.as_mut() { - if let Some(chunk) = chunks.next() { - return Some(chunk); - } else { - self.suffix_chunks = None; - } - } - - None - } -} - -#[derive(Clone)] -pub struct SuggestionBufferRows<'a> { - current_row: u32, - suggestion_row_start: u32, - suggestion_row_end: u32, - fold_buffer_rows: FoldBufferRows<'a>, -} - -impl<'a> Iterator for SuggestionBufferRows<'a> { - type Item = Option; - - fn next(&mut self) -> Option { - let row = post_inc(&mut self.current_row); - if row <= self.suggestion_row_start || row > self.suggestion_row_end { - self.fold_buffer_rows.next() - } else { - Some(None) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{display_map::fold_map::FoldMap, MultiBuffer}; - use gpui::AppContext; - use rand::{prelude::StdRng, Rng}; - use settings::SettingsStore; - use std::{ - env, - ops::{Bound, RangeBounds}, - }; - - #[gpui::test] - fn test_basic(cx: &mut AppContext) { - let buffer = MultiBuffer::build_simple("abcdefghi", cx); - let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (mut fold_map, fold_snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx)); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - assert_eq!(suggestion_snapshot.text(), "abcdefghi"); - - let (suggestion_snapshot, _, _) = suggestion_map.replace( - Some(Suggestion { - position: 3, - text: "123\n456".into(), - }), - fold_snapshot, - Default::default(), - ); - assert_eq!(suggestion_snapshot.text(), "abc123\n456defghi"); - - buffer.update(cx, |buffer, cx| { - buffer.edit( - [(0..0, "ABC"), (3..3, "DEF"), (4..4, "GHI"), (9..9, "JKL")], - None, - cx, - ) - }); - let (fold_snapshot, fold_edits) = fold_map.read( - buffer.read(cx).snapshot(cx), - buffer_edits.consume().into_inner(), - ); - let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); - assert_eq!(suggestion_snapshot.text(), "ABCabcDEF123\n456dGHIefghiJKL"); - - let (mut fold_map_writer, _, _) = - fold_map.write(buffer.read(cx).snapshot(cx), Default::default()); - let (fold_snapshot, fold_edits) = fold_map_writer.fold([0..3]); - let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits); - assert_eq!(suggestion_snapshot.text(), "⋯abcDEF123\n456dGHIefghiJKL"); - - let (mut fold_map_writer, _, _) = - fold_map.write(buffer.read(cx).snapshot(cx), Default::default()); - let (fold_snapshot, fold_edits) = fold_map_writer.fold([6..10]); - let (suggestion_snapshot, _) = suggestion_map.sync(fold_snapshot, fold_edits); - assert_eq!(suggestion_snapshot.text(), "⋯abc⋯GHIefghiJKL"); - } - - #[gpui::test(iterations = 100)] - fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) { - init_test(cx); - - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); - - let len = rng.gen_range(0..30); - let buffer = if rng.gen() { - let text = util::RandomCharIter::new(&mut rng) - .take(len) - .collect::(); - MultiBuffer::build_simple(&text, cx) - } else { - MultiBuffer::build_random(&mut rng, cx) - }; - let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - log::info!("buffer text: {:?}", buffer_snapshot.text()); - - let (mut fold_map, mut fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (suggestion_map, mut suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - - for _ in 0..operations { - let mut suggestion_edits = Patch::default(); - - let mut prev_suggestion_text = suggestion_snapshot.text(); - let mut buffer_edits = Vec::new(); - match rng.gen_range(0..=100) { - 0..=29 => { - let (_, edits) = suggestion_map.randomly_mutate(&mut rng); - suggestion_edits = suggestion_edits.compose(edits); - } - 30..=59 => { - for (new_fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { - fold_snapshot = new_fold_snapshot; - let (_, edits) = suggestion_map.sync(fold_snapshot.clone(), fold_edits); - suggestion_edits = suggestion_edits.compose(edits); - } - } - _ => buffer.update(cx, |buffer, cx| { - let subscription = buffer.subscribe(); - let edit_count = rng.gen_range(1..=5); - buffer.randomly_mutate(&mut rng, edit_count, cx); - buffer_snapshot = buffer.snapshot(cx); - let edits = subscription.consume().into_inner(); - log::info!("editing {:?}", edits); - buffer_edits.extend(edits); - }), - }; - - let (new_fold_snapshot, fold_edits) = - fold_map.read(buffer_snapshot.clone(), buffer_edits); - fold_snapshot = new_fold_snapshot; - let (new_suggestion_snapshot, edits) = - suggestion_map.sync(fold_snapshot.clone(), fold_edits); - suggestion_snapshot = new_suggestion_snapshot; - suggestion_edits = suggestion_edits.compose(edits); - - log::info!("buffer text: {:?}", buffer_snapshot.text()); - log::info!("folds text: {:?}", fold_snapshot.text()); - log::info!("suggestions text: {:?}", suggestion_snapshot.text()); - - let mut expected_text = Rope::from(fold_snapshot.text().as_str()); - let mut expected_buffer_rows = fold_snapshot.buffer_rows(0).collect::>(); - if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { - expected_text.replace( - suggestion.position.0..suggestion.position.0, - &suggestion.text.to_string(), - ); - let suggestion_start = suggestion.position.to_point(&fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - expected_buffer_rows.splice( - (suggestion_start.row + 1) as usize..(suggestion_start.row + 1) as usize, - (0..suggestion_end.row - suggestion_start.row).map(|_| None), - ); - } - assert_eq!(suggestion_snapshot.text(), expected_text.to_string()); - for row_start in 0..expected_buffer_rows.len() { - assert_eq!( - suggestion_snapshot - .buffer_rows(row_start as u32) - .collect::>(), - &expected_buffer_rows[row_start..], - "incorrect buffer rows starting at {}", - row_start - ); - } - - for _ in 0..5 { - let mut end = rng.gen_range(0..=suggestion_snapshot.len().0); - end = expected_text.clip_offset(end, Bias::Right); - let mut start = rng.gen_range(0..=end); - start = expected_text.clip_offset(start, Bias::Right); - - let actual_text = suggestion_snapshot - .chunks( - SuggestionOffset(start)..SuggestionOffset(end), - false, - None, - None, - ) - .map(|chunk| chunk.text) - .collect::(); - assert_eq!( - actual_text, - expected_text.slice(start..end).to_string(), - "incorrect text in range {:?}", - start..end - ); - - let start_point = SuggestionPoint(expected_text.offset_to_point(start)); - let end_point = SuggestionPoint(expected_text.offset_to_point(end)); - assert_eq!( - suggestion_snapshot.text_summary_for_range(start_point..end_point), - expected_text.slice(start..end).summary() - ); - } - - for edit in suggestion_edits.into_inner() { - prev_suggestion_text.replace_range( - edit.new.start.0..edit.new.start.0 + edit.old_len().0, - &suggestion_snapshot.text()[edit.new.start.0..edit.new.end.0], - ); - } - assert_eq!(prev_suggestion_text, suggestion_snapshot.text()); - - assert_eq!(expected_text.max_point(), suggestion_snapshot.max_point().0); - assert_eq!(expected_text.len(), suggestion_snapshot.len().0); - - let mut suggestion_point = SuggestionPoint::default(); - let mut suggestion_offset = SuggestionOffset::default(); - for ch in expected_text.chars() { - assert_eq!( - suggestion_snapshot.to_offset(suggestion_point), - suggestion_offset, - "invalid to_offset({:?})", - suggestion_point - ); - assert_eq!( - suggestion_snapshot.to_point(suggestion_offset), - suggestion_point, - "invalid to_point({:?})", - suggestion_offset - ); - assert_eq!( - suggestion_snapshot - .to_suggestion_point(suggestion_snapshot.to_fold_point(suggestion_point)), - suggestion_snapshot.clip_point(suggestion_point, Bias::Left), - ); - - let mut bytes = [0; 4]; - for byte in ch.encode_utf8(&mut bytes).as_bytes() { - suggestion_offset.0 += 1; - if *byte == b'\n' { - suggestion_point.0 += Point::new(1, 0); - } else { - suggestion_point.0 += Point::new(0, 1); - } - - let clipped_left_point = - suggestion_snapshot.clip_point(suggestion_point, Bias::Left); - let clipped_right_point = - suggestion_snapshot.clip_point(suggestion_point, Bias::Right); - assert!( - clipped_left_point <= clipped_right_point, - "clipped left point {:?} is greater than clipped right point {:?}", - clipped_left_point, - clipped_right_point - ); - assert_eq!( - clipped_left_point.0, - expected_text.clip_point(clipped_left_point.0, Bias::Left) - ); - assert_eq!( - clipped_right_point.0, - expected_text.clip_point(clipped_right_point.0, Bias::Right) - ); - assert!(clipped_left_point <= suggestion_snapshot.max_point()); - assert!(clipped_right_point <= suggestion_snapshot.max_point()); - - if let Some(suggestion) = suggestion_snapshot.suggestion.as_ref() { - let suggestion_start = suggestion.position.to_point(&fold_snapshot).0; - let suggestion_end = suggestion_start + suggestion.text.max_point(); - let invalid_range = ( - Bound::Excluded(suggestion_start), - Bound::Included(suggestion_end), - ); - assert!( - !invalid_range.contains(&clipped_left_point.0), - "clipped left point {:?} is inside invalid suggestion range {:?}", - clipped_left_point, - invalid_range - ); - assert!( - !invalid_range.contains(&clipped_right_point.0), - "clipped right point {:?} is inside invalid suggestion range {:?}", - clipped_right_point, - invalid_range - ); - } - } - } - } - } - - fn init_test(cx: &mut AppContext) { - cx.set_global(SettingsStore::test(cx)); - theme::init((), cx); - } - - impl SuggestionMap { - pub fn randomly_mutate( - &self, - rng: &mut impl Rng, - ) -> (SuggestionSnapshot, Vec) { - let fold_snapshot = self.0.lock().fold_snapshot.clone(); - let new_suggestion = if rng.gen_bool(0.3) { - None - } else { - let index = rng.gen_range(0..=fold_snapshot.buffer_snapshot().len()); - let len = rng.gen_range(0..30); - Some(Suggestion { - position: index, - text: util::RandomCharIter::new(rng) - .take(len) - .filter(|ch| *ch != '\r') - .collect::() - .as_str() - .into(), - }) - }; - - log::info!("replacing suggestion with {:?}", new_suggestion); - let (snapshot, edits, _) = - self.replace(new_suggestion, fold_snapshot, Default::default()); - (snapshot, edits) - } - } -} diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index d97ba4f40b..ca73f6a1a7 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,80 +1,76 @@ use super::{ - suggestion_map::{self, SuggestionChunks, SuggestionEdit, SuggestionPoint, SuggestionSnapshot}, + fold_map::{self, FoldChunks, FoldEdit, FoldPoint, FoldSnapshot}, TextHighlights, }; use crate::MultiBufferSnapshot; use gpui::fonts::HighlightStyle; use language::{Chunk, Point}; -use parking_lot::Mutex; use std::{cmp, mem, num::NonZeroU32, ops::Range}; use sum_tree::Bias; const MAX_EXPANSION_COLUMN: u32 = 256; -pub struct TabMap(Mutex); +pub struct TabMap(TabSnapshot); impl TabMap { - pub fn new(input: SuggestionSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { + pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) { let snapshot = TabSnapshot { - suggestion_snapshot: input, + fold_snapshot, tab_size, max_expansion_column: MAX_EXPANSION_COLUMN, version: 0, }; - (Self(Mutex::new(snapshot.clone())), snapshot) + (Self(snapshot.clone()), snapshot) } #[cfg(test)] - pub fn set_max_expansion_column(&self, column: u32) -> TabSnapshot { - self.0.lock().max_expansion_column = column; - self.0.lock().clone() + pub fn set_max_expansion_column(&mut self, column: u32) -> TabSnapshot { + self.0.max_expansion_column = column; + self.0.clone() } pub fn sync( - &self, - suggestion_snapshot: SuggestionSnapshot, - mut suggestion_edits: Vec, + &mut self, + fold_snapshot: FoldSnapshot, + mut fold_edits: Vec, tab_size: NonZeroU32, ) -> (TabSnapshot, Vec) { - let mut old_snapshot = self.0.lock(); + let old_snapshot = &mut self.0; let mut new_snapshot = TabSnapshot { - suggestion_snapshot, + fold_snapshot, tab_size, max_expansion_column: old_snapshot.max_expansion_column, version: old_snapshot.version, }; - if old_snapshot.suggestion_snapshot.version != new_snapshot.suggestion_snapshot.version { + if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version { new_snapshot.version += 1; } - let mut tab_edits = Vec::with_capacity(suggestion_edits.len()); + let mut tab_edits = Vec::with_capacity(fold_edits.len()); if old_snapshot.tab_size == new_snapshot.tab_size { // Expand each edit to include the next tab on the same line as the edit, // and any subsequent tabs on that line that moved across the tab expansion // boundary. - for suggestion_edit in &mut suggestion_edits { - let old_end = old_snapshot - .suggestion_snapshot - .to_point(suggestion_edit.old.end); - let old_end_row_successor_offset = - old_snapshot.suggestion_snapshot.to_offset(cmp::min( - SuggestionPoint::new(old_end.row() + 1, 0), - old_snapshot.suggestion_snapshot.max_point(), - )); - let new_end = new_snapshot - .suggestion_snapshot - .to_point(suggestion_edit.new.end); + for fold_edit in &mut fold_edits { + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let old_end_row_successor_offset = cmp::min( + FoldPoint::new(old_end.row() + 1, 0), + old_snapshot.fold_snapshot.max_point(), + ) + .to_offset(&old_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); let mut offset_from_edit = 0; let mut first_tab_offset = None; let mut last_tab_with_changed_expansion_offset = None; - 'outer: for chunk in old_snapshot.suggestion_snapshot.chunks( - suggestion_edit.old.end..old_end_row_successor_offset, + 'outer: for chunk in old_snapshot.fold_snapshot.chunks( + fold_edit.old.end..old_end_row_successor_offset, false, None, None, + None, ) { for (ix, _) in chunk.text.match_indices('\t') { let offset_from_edit = offset_from_edit + (ix as u32); @@ -102,39 +98,31 @@ impl TabMap { } if let Some(offset) = last_tab_with_changed_expansion_offset.or(first_tab_offset) { - suggestion_edit.old.end.0 += offset as usize + 1; - suggestion_edit.new.end.0 += offset as usize + 1; + fold_edit.old.end.0 += offset as usize + 1; + fold_edit.new.end.0 += offset as usize + 1; } } // Combine any edits that overlap due to the expansion. let mut ix = 1; - while ix < suggestion_edits.len() { - let (prev_edits, next_edits) = suggestion_edits.split_at_mut(ix); + while ix < fold_edits.len() { + let (prev_edits, next_edits) = fold_edits.split_at_mut(ix); let prev_edit = prev_edits.last_mut().unwrap(); let edit = &next_edits[0]; if prev_edit.old.end >= edit.old.start { prev_edit.old.end = edit.old.end; prev_edit.new.end = edit.new.end; - suggestion_edits.remove(ix); + fold_edits.remove(ix); } else { ix += 1; } } - for suggestion_edit in suggestion_edits { - let old_start = old_snapshot - .suggestion_snapshot - .to_point(suggestion_edit.old.start); - let old_end = old_snapshot - .suggestion_snapshot - .to_point(suggestion_edit.old.end); - let new_start = new_snapshot - .suggestion_snapshot - .to_point(suggestion_edit.new.start); - let new_end = new_snapshot - .suggestion_snapshot - .to_point(suggestion_edit.new.end); + for fold_edit in fold_edits { + let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot); + let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot); + let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot); + let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot); tab_edits.push(TabEdit { old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end), new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end), @@ -155,7 +143,7 @@ impl TabMap { #[derive(Clone)] pub struct TabSnapshot { - pub suggestion_snapshot: SuggestionSnapshot, + pub fold_snapshot: FoldSnapshot, pub tab_size: NonZeroU32, pub max_expansion_column: u32, pub version: usize, @@ -163,18 +151,15 @@ pub struct TabSnapshot { impl TabSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.suggestion_snapshot.buffer_snapshot() + &self.fold_snapshot.inlay_snapshot.buffer } pub fn line_len(&self, row: u32) -> u32 { let max_point = self.max_point(); if row < max_point.row() { - self.to_tab_point(SuggestionPoint::new( - row, - self.suggestion_snapshot.line_len(row), - )) - .0 - .column + self.to_tab_point(FoldPoint::new(row, self.fold_snapshot.line_len(row))) + .0 + .column } else { max_point.column() } @@ -185,10 +170,10 @@ impl TabSnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - let input_start = self.to_suggestion_point(range.start, Bias::Left).0; - let input_end = self.to_suggestion_point(range.end, Bias::Right).0; + let input_start = self.to_fold_point(range.start, Bias::Left).0; + let input_end = self.to_fold_point(range.end, Bias::Right).0; let input_summary = self - .suggestion_snapshot + .fold_snapshot .text_summary_for_range(input_start..input_end); let mut first_line_chars = 0; @@ -198,7 +183,7 @@ impl TabSnapshot { self.max_point() }; for c in self - .chunks(range.start..line_end, false, None, None) + .chunks(range.start..line_end, false, None, None, None) .flat_map(|chunk| chunk.text.chars()) { if c == '\n' { @@ -217,6 +202,7 @@ impl TabSnapshot { false, None, None, + None, ) .flat_map(|chunk| chunk.text.chars()) { @@ -238,15 +224,17 @@ impl TabSnapshot { range: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> TabChunks<'a> { let (input_start, expanded_char_column, to_next_stop) = - self.to_suggestion_point(range.start, Bias::Left); + self.to_fold_point(range.start, Bias::Left); let input_column = input_start.column(); - let input_start = self.suggestion_snapshot.to_offset(input_start); + let input_start = input_start.to_offset(&self.fold_snapshot); let input_end = self - .suggestion_snapshot - .to_offset(self.to_suggestion_point(range.end, Bias::Right).0); + .to_fold_point(range.end, Bias::Right) + .0 + .to_offset(&self.fold_snapshot); let to_next_stop = if range.start.0 + Point::new(0, to_next_stop) > range.end.0 { range.end.column() - range.start.column() } else { @@ -254,11 +242,12 @@ impl TabSnapshot { }; TabChunks { - suggestion_chunks: self.suggestion_snapshot.chunks( + fold_chunks: self.fold_snapshot.chunks( input_start..input_end, language_aware, text_highlights, - suggestion_highlight, + hint_highlights, + suggestion_highlights, ), input_column, column: expanded_char_column, @@ -275,63 +264,58 @@ impl TabSnapshot { } } - pub fn buffer_rows(&self, row: u32) -> suggestion_map::SuggestionBufferRows { - self.suggestion_snapshot.buffer_rows(row) + pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> { + self.fold_snapshot.buffer_rows(row) } #[cfg(test)] pub fn text(&self) -> String { - self.chunks(TabPoint::zero()..self.max_point(), false, None, None) + self.chunks(TabPoint::zero()..self.max_point(), false, None, None, None) .map(|chunk| chunk.text) .collect() } pub fn max_point(&self) -> TabPoint { - self.to_tab_point(self.suggestion_snapshot.max_point()) + self.to_tab_point(self.fold_snapshot.max_point()) } pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint { self.to_tab_point( - self.suggestion_snapshot - .clip_point(self.to_suggestion_point(point, bias).0, bias), + self.fold_snapshot + .clip_point(self.to_fold_point(point, bias).0, bias), ) } - pub fn to_tab_point(&self, input: SuggestionPoint) -> TabPoint { - let chars = self - .suggestion_snapshot - .chars_at(SuggestionPoint::new(input.row(), 0)); + pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint { + let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0)); let expanded = self.expand_tabs(chars, input.column()); TabPoint::new(input.row(), expanded) } - pub fn to_suggestion_point(&self, output: TabPoint, bias: Bias) -> (SuggestionPoint, u32, u32) { - let chars = self - .suggestion_snapshot - .chars_at(SuggestionPoint::new(output.row(), 0)); + pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, u32, u32) { + let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0)); let expanded = output.column(); let (collapsed, expanded_char_column, to_next_stop) = self.collapse_tabs(chars, expanded, bias); ( - SuggestionPoint::new(output.row(), collapsed as u32), + FoldPoint::new(output.row(), collapsed as u32), expanded_char_column, to_next_stop, ) } pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { - let fold_point = self - .suggestion_snapshot - .fold_snapshot - .to_fold_point(point, bias); - let suggestion_point = self.suggestion_snapshot.to_suggestion_point(fold_point); - self.to_tab_point(suggestion_point) + let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + self.to_tab_point(fold_point) } pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { - let suggestion_point = self.to_suggestion_point(point, bias).0; - let fold_point = self.suggestion_snapshot.to_fold_point(suggestion_point); - fold_point.to_buffer_point(&self.suggestion_snapshot.fold_snapshot) + let fold_point = self.to_fold_point(point, bias).0; + let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + self.fold_snapshot + .inlay_snapshot + .to_buffer_point(inlay_point) } fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { @@ -490,7 +474,7 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary { const SPACES: &str = " "; pub struct TabChunks<'a> { - suggestion_chunks: SuggestionChunks<'a>, + fold_chunks: FoldChunks<'a>, chunk: Chunk<'a>, column: u32, max_expansion_column: u32, @@ -506,7 +490,7 @@ impl<'a> Iterator for TabChunks<'a> { fn next(&mut self) -> Option { if self.chunk.text.is_empty() { - if let Some(chunk) = self.suggestion_chunks.next() { + if let Some(chunk) = self.fold_chunks.next() { self.chunk = chunk; if self.inside_leading_tab { self.chunk.text = &self.chunk.text[1..]; @@ -574,7 +558,7 @@ impl<'a> Iterator for TabChunks<'a> { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap}, + display_map::{fold_map::FoldMap, inlay_map::InlayMap}, MultiBuffer, }; use rand::{prelude::StdRng, Rng}; @@ -583,9 +567,9 @@ mod tests { fn test_expand_tabs(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple("", cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 1), 4); @@ -600,9 +584,9 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), output); @@ -615,6 +599,7 @@ mod tests { false, None, None, + None, ) .map(|c| c.text) .collect::(), @@ -626,16 +611,16 @@ mod tests { let input_point = Point::new(0, ix as u32); let output_point = Point::new(0, output.find(c).unwrap() as u32); assert_eq!( - tab_snapshot.to_tab_point(SuggestionPoint(input_point)), + tab_snapshot.to_tab_point(FoldPoint(input_point)), TabPoint(output_point), "to_tab_point({input_point:?})" ); assert_eq!( tab_snapshot - .to_suggestion_point(TabPoint(output_point), Bias::Left) + .to_fold_point(TabPoint(output_point), Bias::Left) .0, - SuggestionPoint(input_point), - "to_suggestion_point({output_point:?})" + FoldPoint(input_point), + "to_fold_point({output_point:?})" ); } } @@ -648,9 +633,9 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, mut tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; assert_eq!(tab_snapshot.text(), input); @@ -662,9 +647,9 @@ mod tests { let buffer = MultiBuffer::build_simple(&input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); - let (_, suggestion_snapshot) = SuggestionMap::new(fold_snapshot); - let (_, tab_snapshot) = TabMap::new(suggestion_snapshot, 4.try_into().unwrap()); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!( chunks(&tab_snapshot, TabPoint::zero()), @@ -689,7 +674,7 @@ mod tests { let mut chunks = Vec::new(); let mut was_tab = false; let mut text = String::new(); - for chunk in snapshot.chunks(start..snapshot.max_point(), false, None, None) { + for chunk in snapshot.chunks(start..snapshot.max_point(), false, None, None, None) { if chunk.is_tab != was_tab { if !text.is_empty() { chunks.push((mem::take(&mut text), was_tab)); @@ -721,15 +706,16 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone()); fold_map.randomly_mutate(&mut rng); - let (fold_snapshot, _) = fold_map.read(buffer_snapshot, vec![]); + let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (suggestion_map, _) = SuggestionMap::new(fold_snapshot); - let (suggestion_snapshot, _) = suggestion_map.randomly_mutate(&mut rng); - log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); + let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); let text = text::Rope::from(tabs_snapshot.text().as_str()); @@ -757,7 +743,7 @@ mod tests { let expected_summary = TextSummary::from(expected_text.as_str()); assert_eq!( tabs_snapshot - .chunks(start..end, false, None, None) + .chunks(start..end, false, None, None, None) .map(|c| c.text) .collect::(), expected_text, @@ -767,7 +753,7 @@ mod tests { ); let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end); - if tab_size.get() > 1 && suggestion_snapshot.text().contains('\t') { + if tab_size.get() > 1 && inlay_snapshot.text().contains('\t') { actual_summary.longest_row = expected_summary.longest_row; actual_summary.longest_row_chars = expected_summary.longest_row_chars; } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index dad264d57d..f21c7151ad 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,5 @@ use super::{ - suggestion_map::SuggestionBufferRows, + fold_map::FoldBufferRows, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, TextHighlights, }; @@ -65,7 +65,7 @@ pub struct WrapChunks<'a> { #[derive(Clone)] pub struct WrapBufferRows<'a> { - input_buffer_rows: SuggestionBufferRows<'a>, + input_buffer_rows: FoldBufferRows<'a>, input_buffer_row: Option, output_row: u32, soft_wrapped: bool, @@ -446,6 +446,7 @@ impl WrapSnapshot { false, None, None, + None, ); let mut edit_transforms = Vec::::new(); for _ in edit.new_rows.start..edit.new_rows.end { @@ -575,7 +576,8 @@ impl WrapSnapshot { rows: Range, language_aware: bool, text_highlights: Option<&'a TextHighlights>, - suggestion_highlight: Option, + hint_highlights: Option, + suggestion_highlights: Option, ) -> WrapChunks<'a> { let output_start = WrapPoint::new(rows.start, 0); let output_end = WrapPoint::new(rows.end, 0); @@ -593,7 +595,8 @@ impl WrapSnapshot { input_start..input_end, language_aware, text_highlights, - suggestion_highlight, + hint_highlights, + suggestion_highlights, ), input_chunk: Default::default(), output_position: output_start, @@ -757,28 +760,18 @@ impl WrapSnapshot { } let text = language::Rope::from(self.text().as_str()); - let input_buffer_rows = self.buffer_snapshot().buffer_rows(0).collect::>(); + let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0); let mut expected_buffer_rows = Vec::new(); - let mut prev_fold_row = 0; + let mut prev_tab_row = 0; for display_row in 0..=self.max_point().row() { let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0)); - let suggestion_point = self - .tab_snapshot - .to_suggestion_point(tab_point, Bias::Left) - .0; - let fold_point = self - .tab_snapshot - .suggestion_snapshot - .to_fold_point(suggestion_point); - if fold_point.row() == prev_fold_row && display_row != 0 { + if tab_point.row() == prev_tab_row && display_row != 0 { expected_buffer_rows.push(None); } else { - let buffer_point = fold_point - .to_buffer_point(&self.tab_snapshot.suggestion_snapshot.fold_snapshot); - expected_buffer_rows.push(input_buffer_rows[buffer_point.row as usize]); - prev_fold_row = fold_point.row(); + expected_buffer_rows.push(input_buffer_rows.next().unwrap()); } + prev_tab_row = tab_point.row(); assert_eq!(self.line_len(display_row), text.line_len(display_row)); } @@ -1038,7 +1031,7 @@ fn consolidate_wrap_edits(edits: &mut Vec) { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, suggestion_map::SuggestionMap, tab_map::TabMap}, + display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, MultiBuffer, }; use gpui::test::observe; @@ -1089,11 +1082,11 @@ mod tests { }); let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (mut fold_map, fold_snapshot) = FoldMap::new(buffer_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone()); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone()); - log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (tab_map, _) = TabMap::new(suggestion_snapshot.clone(), tab_size); + let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); log::info!("TabMap text: {:?}", tabs_snapshot.text()); @@ -1122,6 +1115,7 @@ mod tests { ); log::info!("Wrapped text: {:?}", actual_text); + let mut next_inlay_id = 0; let mut edits = Vec::new(); for _i in 0..operations { log::info!("{} ==============================================", _i); @@ -1139,10 +1133,8 @@ mod tests { } 20..=39 => { for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) { - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1151,10 +1143,11 @@ mod tests { } } 40..=59 => { - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.randomly_mutate(&mut rng); + let (inlay_snapshot, inlay_edits) = + inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tabs_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + tab_map.sync(fold_snapshot, fold_edits, tab_size); let (mut snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); @@ -1173,13 +1166,12 @@ mod tests { } log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (fold_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits); + let (inlay_snapshot, inlay_edits) = + inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); log::info!("FoldMap text: {:?}", fold_snapshot.text()); - let (suggestion_snapshot, suggestion_edits) = - suggestion_map.sync(fold_snapshot, fold_edits); - log::info!("SuggestionMap text: {:?}", suggestion_snapshot.text()); - let (tabs_snapshot, tab_edits) = - tab_map.sync(suggestion_snapshot, suggestion_edits, tab_size); + let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); let unwrapped_text = tabs_snapshot.text(); @@ -1227,7 +1219,7 @@ mod tests { if tab_size.get() == 1 || !wrapped_snapshot .tab_snapshot - .suggestion_snapshot + .fold_snapshot .text() .contains('\t') { @@ -1328,8 +1320,14 @@ mod tests { } pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { - self.chunks(wrap_row..self.max_point().row() + 1, false, None, None) - .map(|h| h.text) + self.chunks( + wrap_row..self.max_point().row() + 1, + false, + None, + None, + None, + ) + .map(|h| h.text) } fn verify_chunks(&mut self, rng: &mut impl Rng) { @@ -1352,7 +1350,7 @@ mod tests { } let actual_text = self - .chunks(start_row..end_row, true, None, None) + .chunks(start_row..end_row, true, None, None, None) .map(|c| c.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8adf98f1bc..64332c102a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2,6 +2,7 @@ mod blink_manager; pub mod display_map; mod editor_settings; mod element; +mod inlay_hint_cache; mod git; mod highlight_matching_bracket; @@ -52,11 +53,12 @@ use gpui::{ }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; +use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; pub use language::{char_kind, CharKind}; use language::{ - language_settings::{self, all_language_settings}, + language_settings::{self, all_language_settings, InlayHintSettings}, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal, TransactionId, @@ -64,11 +66,12 @@ use language::{ use link_go_to_definition::{ hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState, }; +use log::error; +use multi_buffer::ToOffsetUtf16; pub use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint, }; -use multi_buffer::{MultiBufferChunks, ToOffsetUtf16}; use ordered_float::OrderedFloat; use project::{FormatTrigger, Location, LocationLink, Project, ProjectPath, ProjectTransaction}; use scroll::{ @@ -85,12 +88,13 @@ use std::{ cmp::{self, Ordering, Reverse}, mem, num::NonZeroU32, - ops::{Deref, DerefMut, Range}, + ops::{ControlFlow, Deref, DerefMut, Range}, path::Path, sync::Arc, time::{Duration, Instant}, }; pub use sum_tree::Bias; +use text::Rope; use theme::{DiagnosticStyle, Theme, ThemeSettings}; use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, ViewId, Workspace}; @@ -180,6 +184,12 @@ pub struct GutterHover { pub hovered: bool, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InlayId { + Suggestion(usize), + Hint(usize), +} + actions!( editor, [ @@ -535,6 +545,8 @@ pub struct Editor { gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, copilot_state: CopilotState, + inlay_hint_cache: InlayHintCache, + next_inlay_id: usize, _subscriptions: Vec, } @@ -1056,6 +1068,7 @@ pub struct CopilotState { cycled: bool, completions: Vec, active_completion_index: usize, + suggestion: Option, } impl Default for CopilotState { @@ -1067,6 +1080,7 @@ impl Default for CopilotState { completions: Default::default(), active_completion_index: 0, cycled: false, + suggestion: None, } } } @@ -1181,6 +1195,14 @@ enum GotoDefinitionKind { Type, } +#[derive(Debug, Copy, Clone)] +enum InlayRefreshReason { + SettingsChange(InlayHintSettings), + NewLinesShown, + ExcerptEdited, + RefreshRequested, +} + impl Editor { pub fn single_line( field_editor_style: Option>, @@ -1282,15 +1304,28 @@ impl Editor { let soft_wrap_mode_override = (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None); - let mut project_subscription = None; - if mode == EditorMode::Full && buffer.read(cx).is_singleton() { + let mut project_subscriptions = Vec::new(); + if mode == EditorMode::Full { if let Some(project) = project.as_ref() { - project_subscription = Some(cx.observe(project, |_, _, cx| { - cx.emit(Event::TitleChanged); - })) + if buffer.read(cx).is_singleton() { + project_subscriptions.push(cx.observe(project, |_, _, cx| { + cx.emit(Event::TitleChanged); + })); + } + project_subscriptions.push(cx.subscribe(project, |editor, _, event, cx| { + if let project::Event::RefreshInlays = event { + editor.refresh_inlays(InlayRefreshReason::RefreshRequested, cx); + }; + })); } } + let inlay_hint_settings = inlay_hint_settings( + selections.newest_anchor().head(), + &buffer.read(cx).snapshot(cx), + cx, + ); + let mut this = Self { handle: cx.weak_handle(), buffer: buffer.clone(), @@ -1324,6 +1359,7 @@ impl Editor { .add_view(|cx| context_menu::ContextMenu::new(editor_view_id, cx)), completion_tasks: Default::default(), next_completion_id: 0, + next_inlay_id: 0, available_code_actions: Default::default(), code_actions_task: Default::default(), document_highlights_task: Default::default(), @@ -1340,6 +1376,7 @@ impl Editor { hover_state: Default::default(), link_go_to_definition_state: Default::default(), copilot_state: Default::default(), + inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), @@ -1350,9 +1387,7 @@ impl Editor { ], }; - if let Some(project_subscription) = project_subscription { - this._subscriptions.push(project_subscription); - } + this._subscriptions.extend(project_subscriptions); this.end_selection(cx); this.scroll_manager.show_scrollbar(cx); @@ -1873,7 +1908,7 @@ impl Editor { s.set_pending(pending, mode); }); } else { - log::error!("update_selection dispatched with no pending selection"); + error!("update_selection dispatched with no pending selection"); return; } @@ -2577,6 +2612,106 @@ impl Editor { } } + fn refresh_inlays(&mut self, reason: InlayRefreshReason, cx: &mut ViewContext) { + if self.project.is_none() || self.mode != EditorMode::Full { + return; + } + + let invalidate_cache = match reason { + InlayRefreshReason::SettingsChange(new_settings) => { + match self.inlay_hint_cache.update_settings( + &self.buffer, + new_settings, + self.visible_inlay_hints(cx), + cx, + ) { + ControlFlow::Break(Some(InlaySplice { + to_remove, + to_insert, + })) => { + self.splice_inlay_hints(to_remove, to_insert, cx); + return; + } + ControlFlow::Break(None) => return, + ControlFlow::Continue(()) => InvalidationStrategy::RefreshRequested, + } + } + InlayRefreshReason::NewLinesShown => InvalidationStrategy::None, + InlayRefreshReason::ExcerptEdited => InvalidationStrategy::ExcerptEdited, + InlayRefreshReason::RefreshRequested => InvalidationStrategy::RefreshRequested, + }; + + self.inlay_hint_cache.refresh_inlay_hints( + self.excerpt_visible_offsets(cx), + invalidate_cache, + cx, + ) + } + + fn visible_inlay_hints(&self, cx: &ViewContext<'_, '_, Editor>) -> Vec { + self.display_map + .read(cx) + .current_inlays() + .filter(move |inlay| { + Some(inlay.id) != self.copilot_state.suggestion.as_ref().map(|h| h.id) + }) + .cloned() + .collect() + } + + fn excerpt_visible_offsets( + &self, + cx: &mut ViewContext<'_, '_, Editor>, + ) -> HashMap, Range)> { + let multi_buffer = self.buffer().read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + let multi_buffer_visible_start = self + .scroll_manager + .anchor() + .anchor + .to_point(&multi_buffer_snapshot); + let multi_buffer_visible_end = multi_buffer_snapshot.clip_point( + multi_buffer_visible_start + + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0), + Bias::Left, + ); + let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; + multi_buffer + .range_to_buffer_ranges(multi_buffer_visible_range, cx) + .into_iter() + .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) + .map(|(buffer, excerpt_visible_range, excerpt_id)| { + (excerpt_id, (buffer, excerpt_visible_range)) + }) + .collect() + } + + fn splice_inlay_hints( + &self, + to_remove: Vec, + to_insert: Vec<(Anchor, InlayId, project::InlayHint)>, + cx: &mut ViewContext, + ) { + let buffer = self.buffer.read(cx).read(cx); + let new_inlays = to_insert + .into_iter() + .map(|(position, id, hint)| { + let mut text = hint.text(); + if hint.padding_right { + text.push(' '); + } + if hint.padding_left { + text.insert(0, ' '); + } + (id, InlayProperties { position, text }) + }) + .collect(); + drop(buffer); + self.display_map.update(cx, |display_map, cx| { + display_map.splice_inlays(to_remove, new_inlays, cx); + }); + } + fn trigger_on_type_formatting( &self, input: String, @@ -3227,10 +3362,7 @@ impl Editor { } fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { - if let Some(suggestion) = self - .display_map - .update(cx, |map, cx| map.replace_suggestion::(None, cx)) - { + if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { if let Some((copilot, completion)) = Copilot::global(cx).zip(self.copilot_state.active_completion()) { @@ -3249,7 +3381,7 @@ impl Editor { } fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext) -> bool { - if self.has_active_copilot_suggestion(cx) { + if let Some(suggestion) = self.take_active_copilot_suggestion(cx) { if let Some(copilot) = Copilot::global(cx) { copilot .update(cx, |copilot, cx| { @@ -3260,8 +3392,9 @@ impl Editor { self.report_copilot_event(None, false, cx) } - self.display_map - .update(cx, |map, cx| map.replace_suggestion::(None, cx)); + self.display_map.update(cx, |map, cx| { + map.splice_inlays::<&str>(vec![suggestion.id], Vec::new(), cx) + }); cx.notify(); true } else { @@ -3282,7 +3415,26 @@ impl Editor { } fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { - self.display_map.read(cx).has_suggestion() + if let Some(suggestion) = self.copilot_state.suggestion.as_ref() { + let buffer = self.buffer.read(cx).read(cx); + suggestion.position.is_valid(&buffer) + } else { + false + } + } + + fn take_active_copilot_suggestion(&mut self, cx: &mut ViewContext) -> Option { + let suggestion = self.copilot_state.suggestion.take()?; + self.display_map.update(cx, |map, cx| { + map.splice_inlays::<&str>(vec![suggestion.id], Default::default(), cx); + }); + let buffer = self.buffer.read(cx).read(cx); + + if suggestion.position.is_valid(&buffer) { + Some(suggestion) + } else { + None + } } fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext) { @@ -3299,14 +3451,27 @@ impl Editor { .copilot_state .text_for_active_completion(cursor, &snapshot) { + let text = Rope::from(text); + let mut to_remove = Vec::new(); + if let Some(suggestion) = self.copilot_state.suggestion.take() { + to_remove.push(suggestion.id); + } + + let suggestion_inlay_id = InlayId::Suggestion(post_inc(&mut self.next_inlay_id)); + let to_insert = vec![( + suggestion_inlay_id, + InlayProperties { + position: cursor, + text: text.clone(), + }, + )]; self.display_map.update(cx, move |map, cx| { - map.replace_suggestion( - Some(Suggestion { - position: cursor, - text: text.trim_end().into(), - }), - cx, - ) + map.splice_inlays(to_remove, to_insert, cx) + }); + self.copilot_state.suggestion = Some(Inlay { + id: suggestion_inlay_id, + position: cursor, + text, }); cx.notify(); } else { @@ -6641,7 +6806,7 @@ impl Editor { if let Some((_, end_selections)) = self.selection_history.transaction_mut(tx_id) { *end_selections = Some(self.selections.disjoint_anchors()); } else { - log::error!("unexpectedly ended a transaction that wasn't started by this editor"); + error!("unexpectedly ended a transaction that wasn't started by this editor"); } cx.emit(Event::Edited); @@ -7103,6 +7268,7 @@ impl Editor { self.update_visible_copilot_suggestion(cx); } cx.emit(Event::BufferEdited); + self.refresh_inlays(InlayRefreshReason::ExcerptEdited, cx); } multi_buffer::Event::ExcerptsAdded { buffer, @@ -7127,7 +7293,7 @@ impl Editor { self.refresh_active_diagnostics(cx); } _ => {} - } + }; } fn on_display_map_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { @@ -7136,6 +7302,14 @@ impl Editor { fn settings_changed(&mut self, cx: &mut ViewContext) { self.refresh_copilot_suggestions(true, cx); + self.refresh_inlays( + InlayRefreshReason::SettingsChange(inlay_hint_settings( + self.selections.newest_anchor().head(), + &self.buffer.read(cx).snapshot(cx), + cx, + )), + cx, + ); } pub fn set_searchable(&mut self, searchable: bool) { @@ -7425,6 +7599,23 @@ impl Editor { let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else { return; }; cx.write_to_clipboard(ClipboardItem::new(lines)); } + + pub fn inlay_hint_cache(&self) -> &InlayHintCache { + &self.inlay_hint_cache + } +} + +fn inlay_hint_settings( + location: Anchor, + snapshot: &MultiBufferSnapshot, + cx: &mut ViewContext<'_, '_, Editor>, +) -> InlayHintSettings { + let file = snapshot.file_at(location); + let language = snapshot.language_at(location); + let settings = all_language_settings(file, cx); + settings + .language(language.map(|l| l.name()).as_deref()) + .inlay_hints } fn consume_contiguous_rows( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6525e7fc22..d1e6f29bbe 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1392,7 +1392,12 @@ impl EditorElement { } else { let style = &self.style; let chunks = snapshot - .chunks(rows.clone(), true, Some(style.theme.suggestion)) + .chunks( + rows.clone(), + true, + Some(style.theme.hint), + Some(style.theme.suggestion), + ) .map(|chunk| { let mut highlight_style = chunk .syntax_highlight_id @@ -1921,7 +1926,7 @@ impl Element for EditorElement { let em_advance = style.text.em_advance(cx.font_cache()); let overscroll = vec2f(em_width, 0.); let snapshot = { - editor.set_visible_line_count(size.y() / line_height); + editor.set_visible_line_count(size.y() / line_height, cx); let editor_width = text_width - gutter_margin - overscroll.x() - em_width; let wrap_width = match editor.soft_wrap_mode(cx) { diff --git a/crates/editor/src/inlay_hint_cache.rs b/crates/editor/src/inlay_hint_cache.rs new file mode 100644 index 0000000000..af7bf3e4c5 --- /dev/null +++ b/crates/editor/src/inlay_hint_cache.rs @@ -0,0 +1,2021 @@ +use std::{ + cmp, + ops::{ControlFlow, Range}, + sync::Arc, +}; + +use crate::{ + display_map::Inlay, Anchor, Editor, ExcerptId, InlayId, MultiBuffer, MultiBufferSnapshot, +}; +use anyhow::Context; +use clock::Global; +use gpui::{ModelHandle, Task, ViewContext}; +use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot}; +use log::error; +use parking_lot::RwLock; +use project::InlayHint; + +use collections::{hash_map, HashMap, HashSet}; +use language::language_settings::InlayHintSettings; +use util::post_inc; + +pub struct InlayHintCache { + pub hints: HashMap>>, + pub allowed_hint_kinds: HashSet>, + pub version: usize, + pub enabled: bool, + update_tasks: HashMap, +} + +#[derive(Debug)] +pub struct CachedExcerptHints { + version: usize, + buffer_version: Global, + buffer_id: u64, + pub hints: Vec<(InlayId, InlayHint)>, +} + +#[derive(Debug, Clone, Copy)] +pub enum InvalidationStrategy { + RefreshRequested, + ExcerptEdited, + None, +} + +#[derive(Debug, Default)] +pub struct InlaySplice { + pub to_remove: Vec, + pub to_insert: Vec<(Anchor, InlayId, InlayHint)>, +} + +struct UpdateTask { + invalidate: InvalidationStrategy, + cache_version: usize, + task: RunningTask, + pending_refresh: Option, +} + +struct RunningTask { + _task: Task<()>, + is_running_rx: smol::channel::Receiver<()>, +} + +#[derive(Debug)] +struct ExcerptHintsUpdate { + excerpt_id: ExcerptId, + remove_from_visible: Vec, + remove_from_cache: HashSet, + add_to_cache: HashSet, +} + +#[derive(Debug, Clone, Copy)] +struct ExcerptQuery { + buffer_id: u64, + excerpt_id: ExcerptId, + dimensions: ExcerptDimensions, + cache_version: usize, + invalidate: InvalidationStrategy, +} + +#[derive(Debug, Clone, Copy)] +struct ExcerptDimensions { + excerpt_range_start: language::Anchor, + excerpt_range_end: language::Anchor, + excerpt_visible_range_start: language::Anchor, + excerpt_visible_range_end: language::Anchor, +} + +struct HintFetchRanges { + visible_range: Range, + other_ranges: Vec>, +} + +impl InvalidationStrategy { + fn should_invalidate(&self) -> bool { + matches!( + self, + InvalidationStrategy::RefreshRequested | InvalidationStrategy::ExcerptEdited + ) + } +} + +impl ExcerptQuery { + fn hints_fetch_ranges(&self, buffer: &BufferSnapshot) -> HintFetchRanges { + let visible_range = + self.dimensions.excerpt_visible_range_start..self.dimensions.excerpt_visible_range_end; + let mut other_ranges = Vec::new(); + if self + .dimensions + .excerpt_range_start + .cmp(&visible_range.start, buffer) + .is_lt() + { + let mut end = visible_range.start; + end.offset -= 1; + other_ranges.push(self.dimensions.excerpt_range_start..end); + } + if self + .dimensions + .excerpt_range_end + .cmp(&visible_range.end, buffer) + .is_gt() + { + let mut start = visible_range.end; + start.offset += 1; + other_ranges.push(start..self.dimensions.excerpt_range_end); + } + + HintFetchRanges { + visible_range, + other_ranges: other_ranges.into_iter().map(|range| range).collect(), + } + } +} + +impl InlayHintCache { + pub fn new(inlay_hint_settings: InlayHintSettings) -> Self { + Self { + allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(), + enabled: inlay_hint_settings.enabled, + hints: HashMap::default(), + update_tasks: HashMap::default(), + version: 0, + } + } + + pub fn update_settings( + &mut self, + multi_buffer: &ModelHandle, + new_hint_settings: InlayHintSettings, + visible_hints: Vec, + cx: &mut ViewContext, + ) -> ControlFlow> { + let new_allowed_hint_kinds = new_hint_settings.enabled_inlay_hint_kinds(); + match (self.enabled, new_hint_settings.enabled) { + (false, false) => { + self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Break(None) + } + (true, true) => { + if new_allowed_hint_kinds == self.allowed_hint_kinds { + ControlFlow::Break(None) + } else { + let new_splice = self.new_allowed_hint_kinds_splice( + multi_buffer, + &visible_hints, + &new_allowed_hint_kinds, + cx, + ); + if new_splice.is_some() { + self.version += 1; + self.update_tasks.clear(); + self.allowed_hint_kinds = new_allowed_hint_kinds; + } + ControlFlow::Break(new_splice) + } + } + (true, false) => { + self.enabled = new_hint_settings.enabled; + self.allowed_hint_kinds = new_allowed_hint_kinds; + if self.hints.is_empty() { + ControlFlow::Break(None) + } else { + self.clear(); + ControlFlow::Break(Some(InlaySplice { + to_remove: visible_hints.iter().map(|inlay| inlay.id).collect(), + to_insert: Vec::new(), + })) + } + } + (false, true) => { + self.enabled = new_hint_settings.enabled; + self.allowed_hint_kinds = new_allowed_hint_kinds; + ControlFlow::Continue(()) + } + } + } + + pub fn refresh_inlay_hints( + &mut self, + mut excerpts_to_query: HashMap, Range)>, + invalidate: InvalidationStrategy, + cx: &mut ViewContext, + ) { + if !self.enabled || excerpts_to_query.is_empty() { + return; + } + let update_tasks = &mut self.update_tasks; + if invalidate.should_invalidate() { + update_tasks + .retain(|task_excerpt_id, _| excerpts_to_query.contains_key(task_excerpt_id)); + } + let cache_version = self.version; + excerpts_to_query.retain(|visible_excerpt_id, _| { + match update_tasks.entry(*visible_excerpt_id) { + hash_map::Entry::Occupied(o) => match o.get().cache_version.cmp(&cache_version) { + cmp::Ordering::Less => true, + cmp::Ordering::Equal => invalidate.should_invalidate(), + cmp::Ordering::Greater => false, + }, + hash_map::Entry::Vacant(_) => true, + } + }); + + cx.spawn(|editor, mut cx| async move { + editor + .update(&mut cx, |editor, cx| { + spawn_new_update_tasks(editor, excerpts_to_query, invalidate, cache_version, cx) + }) + .ok(); + }) + .detach(); + } + + fn new_allowed_hint_kinds_splice( + &self, + multi_buffer: &ModelHandle, + visible_hints: &[Inlay], + new_kinds: &HashSet>, + cx: &mut ViewContext, + ) -> Option { + let old_kinds = &self.allowed_hint_kinds; + if new_kinds == old_kinds { + return None; + } + + let mut to_remove = Vec::new(); + let mut to_insert = Vec::new(); + let mut shown_hints_to_remove = visible_hints.iter().fold( + HashMap::>::default(), + |mut current_hints, inlay| { + current_hints + .entry(inlay.position.excerpt_id) + .or_default() + .push((inlay.position, inlay.id)); + current_hints + }, + ); + + let multi_buffer = multi_buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + + for (excerpt_id, excerpt_cached_hints) in &self.hints { + let shown_excerpt_hints_to_remove = + shown_hints_to_remove.entry(*excerpt_id).or_default(); + let excerpt_cached_hints = excerpt_cached_hints.read(); + let mut excerpt_cache = excerpt_cached_hints.hints.iter().fuse().peekable(); + shown_excerpt_hints_to_remove.retain(|(shown_anchor, shown_hint_id)| { + let Some(buffer) = shown_anchor + .buffer_id + .and_then(|buffer_id| multi_buffer.buffer(buffer_id)) else { return false }; + let buffer_snapshot = buffer.read(cx).snapshot(); + loop { + match excerpt_cache.peek() { + Some((cached_hint_id, cached_hint)) => { + if cached_hint_id == shown_hint_id { + excerpt_cache.next(); + return !new_kinds.contains(&cached_hint.kind); + } + + match cached_hint + .position + .cmp(&shown_anchor.text_anchor, &buffer_snapshot) + { + cmp::Ordering::Less | cmp::Ordering::Equal => { + if !old_kinds.contains(&cached_hint.kind) + && new_kinds.contains(&cached_hint.kind) + { + to_insert.push(( + multi_buffer_snapshot.anchor_in_excerpt( + *excerpt_id, + cached_hint.position, + ), + *cached_hint_id, + cached_hint.clone(), + )); + } + excerpt_cache.next(); + } + cmp::Ordering::Greater => return true, + } + } + None => return true, + } + } + }); + + for (cached_hint_id, maybe_missed_cached_hint) in excerpt_cache { + let cached_hint_kind = maybe_missed_cached_hint.kind; + if !old_kinds.contains(&cached_hint_kind) && new_kinds.contains(&cached_hint_kind) { + to_insert.push(( + multi_buffer_snapshot + .anchor_in_excerpt(*excerpt_id, maybe_missed_cached_hint.position), + *cached_hint_id, + maybe_missed_cached_hint.clone(), + )); + } + } + } + + to_remove.extend( + shown_hints_to_remove + .into_values() + .flatten() + .map(|(_, hint_id)| hint_id), + ); + if to_remove.is_empty() && to_insert.is_empty() { + None + } else { + Some(InlaySplice { + to_remove, + to_insert, + }) + } + } + + fn clear(&mut self) { + self.version += 1; + self.update_tasks.clear(); + self.hints.clear(); + } +} + +fn spawn_new_update_tasks( + editor: &mut Editor, + excerpts_to_query: HashMap, Range)>, + invalidate: InvalidationStrategy, + update_cache_version: usize, + cx: &mut ViewContext<'_, '_, Editor>, +) { + let visible_hints = Arc::new(editor.visible_inlay_hints(cx)); + for (excerpt_id, (buffer_handle, excerpt_visible_range)) in excerpts_to_query { + if !excerpt_visible_range.is_empty() { + let buffer = buffer_handle.read(cx); + let buffer_snapshot = buffer.snapshot(); + let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned(); + if let Some(cached_excerpt_hints) = &cached_excerpt_hints { + let new_task_buffer_version = buffer_snapshot.version(); + let cached_excerpt_hints = cached_excerpt_hints.read(); + let cached_buffer_version = &cached_excerpt_hints.buffer_version; + if cached_excerpt_hints.version > update_cache_version + || cached_buffer_version.changed_since(new_task_buffer_version) + { + return; + } + if !new_task_buffer_version.changed_since(&cached_buffer_version) + && !matches!(invalidate, InvalidationStrategy::RefreshRequested) + { + return; + } + }; + + let buffer_id = buffer.remote_id(); + let excerpt_visible_range_start = buffer.anchor_before(excerpt_visible_range.start); + let excerpt_visible_range_end = buffer.anchor_after(excerpt_visible_range.end); + + let (multi_buffer_snapshot, full_excerpt_range) = + editor.buffer.update(cx, |multi_buffer, cx| { + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + ( + multi_buffer_snapshot, + multi_buffer + .excerpts_for_buffer(&buffer_handle, cx) + .into_iter() + .find(|(id, _)| id == &excerpt_id) + .map(|(_, range)| range.context), + ) + }); + + if let Some(full_excerpt_range) = full_excerpt_range { + let query = ExcerptQuery { + buffer_id, + excerpt_id, + dimensions: ExcerptDimensions { + excerpt_range_start: full_excerpt_range.start, + excerpt_range_end: full_excerpt_range.end, + excerpt_visible_range_start, + excerpt_visible_range_end, + }, + cache_version: update_cache_version, + invalidate, + }; + + let new_update_task = |is_refresh_after_regular_task| { + new_update_task( + query, + multi_buffer_snapshot, + buffer_snapshot, + Arc::clone(&visible_hints), + cached_excerpt_hints, + is_refresh_after_regular_task, + cx, + ) + }; + match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) { + hash_map::Entry::Occupied(mut o) => { + let update_task = o.get_mut(); + match (update_task.invalidate, invalidate) { + (_, InvalidationStrategy::None) => {} + ( + InvalidationStrategy::ExcerptEdited, + InvalidationStrategy::RefreshRequested, + ) if !update_task.task.is_running_rx.is_closed() => { + update_task.pending_refresh = Some(query); + } + _ => { + o.insert(UpdateTask { + invalidate, + cache_version: query.cache_version, + task: new_update_task(false), + pending_refresh: None, + }); + } + } + } + hash_map::Entry::Vacant(v) => { + v.insert(UpdateTask { + invalidate, + cache_version: query.cache_version, + task: new_update_task(false), + pending_refresh: None, + }); + } + } + } + } + } +} + +fn new_update_task( + query: ExcerptQuery, + multi_buffer_snapshot: MultiBufferSnapshot, + buffer_snapshot: BufferSnapshot, + visible_hints: Arc>, + cached_excerpt_hints: Option>>, + is_refresh_after_regular_task: bool, + cx: &mut ViewContext<'_, '_, Editor>, +) -> RunningTask { + let hints_fetch_ranges = query.hints_fetch_ranges(&buffer_snapshot); + let (is_running_tx, is_running_rx) = smol::channel::bounded(1); + let _task = cx.spawn(|editor, mut cx| async move { + let _is_running_tx = is_running_tx; + let create_update_task = |range| { + fetch_and_update_hints( + editor.clone(), + multi_buffer_snapshot.clone(), + buffer_snapshot.clone(), + Arc::clone(&visible_hints), + cached_excerpt_hints.as_ref().map(Arc::clone), + query, + range, + cx.clone(), + ) + }; + + if is_refresh_after_regular_task { + let visible_range_has_updates = + match create_update_task(hints_fetch_ranges.visible_range).await { + Ok(updated) => updated, + Err(e) => { + error!("inlay hint visible range update task failed: {e:#}"); + return; + } + }; + + if visible_range_has_updates { + let other_update_results = futures::future::join_all( + hints_fetch_ranges + .other_ranges + .into_iter() + .map(create_update_task), + ) + .await; + + for result in other_update_results { + if let Err(e) = result { + error!("inlay hint update task failed: {e:#}"); + } + } + } + } else { + let task_update_results = futures::future::join_all( + std::iter::once(hints_fetch_ranges.visible_range) + .chain(hints_fetch_ranges.other_ranges.into_iter()) + .map(create_update_task), + ) + .await; + + for result in task_update_results { + if let Err(e) = result { + error!("inlay hint update task failed: {e:#}"); + } + } + } + + editor + .update(&mut cx, |editor, cx| { + let pending_refresh_query = editor + .inlay_hint_cache + .update_tasks + .get_mut(&query.excerpt_id) + .and_then(|task| task.pending_refresh.take()); + + if let Some(pending_refresh_query) = pending_refresh_query { + let refresh_multi_buffer = editor.buffer().read(cx); + let refresh_multi_buffer_snapshot = refresh_multi_buffer.snapshot(cx); + let refresh_visible_hints = Arc::new(editor.visible_inlay_hints(cx)); + let refresh_cached_excerpt_hints = editor + .inlay_hint_cache + .hints + .get(&pending_refresh_query.excerpt_id) + .map(Arc::clone); + if let Some(buffer) = + refresh_multi_buffer.buffer(pending_refresh_query.buffer_id) + { + drop(refresh_multi_buffer); + editor.inlay_hint_cache.update_tasks.insert( + pending_refresh_query.excerpt_id, + UpdateTask { + invalidate: InvalidationStrategy::RefreshRequested, + cache_version: editor.inlay_hint_cache.version, + task: new_update_task( + pending_refresh_query, + refresh_multi_buffer_snapshot, + buffer.read(cx).snapshot(), + refresh_visible_hints, + refresh_cached_excerpt_hints, + true, + cx, + ), + pending_refresh: None, + }, + ); + } + } + }) + .ok(); + }); + + RunningTask { + _task, + is_running_rx, + } +} + +async fn fetch_and_update_hints( + editor: gpui::WeakViewHandle, + multi_buffer_snapshot: MultiBufferSnapshot, + buffer_snapshot: BufferSnapshot, + visible_hints: Arc>, + cached_excerpt_hints: Option>>, + query: ExcerptQuery, + fetch_range: Range, + mut cx: gpui::AsyncAppContext, +) -> anyhow::Result { + let inlay_hints_fetch_task = editor + .update(&mut cx, |editor, cx| { + editor + .buffer() + .read(cx) + .buffer(query.buffer_id) + .and_then(|buffer| { + let project = editor.project.as_ref()?; + Some(project.update(cx, |project, cx| { + project.inlay_hints(buffer, fetch_range.clone(), cx) + })) + }) + }) + .ok() + .flatten(); + let mut update_happened = false; + let Some(inlay_hints_fetch_task) = inlay_hints_fetch_task else { return Ok(update_happened) }; + let new_hints = inlay_hints_fetch_task + .await + .context("inlay hint fetch task")?; + let background_task_buffer_snapshot = buffer_snapshot.clone(); + let backround_fetch_range = fetch_range.clone(); + let new_update = cx + .background() + .spawn(async move { + calculate_hint_updates( + query, + backround_fetch_range, + new_hints, + &background_task_buffer_snapshot, + cached_excerpt_hints, + &visible_hints, + ) + }) + .await; + + editor + .update(&mut cx, |editor, cx| { + if let Some(new_update) = new_update { + update_happened = !new_update.add_to_cache.is_empty() + || !new_update.remove_from_cache.is_empty() + || !new_update.remove_from_visible.is_empty(); + + let cached_excerpt_hints = editor + .inlay_hint_cache + .hints + .entry(new_update.excerpt_id) + .or_insert_with(|| { + Arc::new(RwLock::new(CachedExcerptHints { + version: query.cache_version, + buffer_version: buffer_snapshot.version().clone(), + buffer_id: query.buffer_id, + hints: Vec::new(), + })) + }); + let mut cached_excerpt_hints = cached_excerpt_hints.write(); + match query.cache_version.cmp(&cached_excerpt_hints.version) { + cmp::Ordering::Less => return, + cmp::Ordering::Greater | cmp::Ordering::Equal => { + cached_excerpt_hints.version = query.cache_version; + } + } + cached_excerpt_hints + .hints + .retain(|(hint_id, _)| !new_update.remove_from_cache.contains(hint_id)); + cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone(); + editor.inlay_hint_cache.version += 1; + + let mut splice = InlaySplice { + to_remove: new_update.remove_from_visible, + to_insert: Vec::new(), + }; + + for new_hint in new_update.add_to_cache { + let new_hint_position = multi_buffer_snapshot + .anchor_in_excerpt(query.excerpt_id, new_hint.position); + let new_inlay_id = InlayId::Hint(post_inc(&mut editor.next_inlay_id)); + if editor + .inlay_hint_cache + .allowed_hint_kinds + .contains(&new_hint.kind) + { + splice + .to_insert + .push((new_hint_position, new_inlay_id, new_hint.clone())); + } + + cached_excerpt_hints.hints.push((new_inlay_id, new_hint)); + } + + cached_excerpt_hints + .hints + .sort_by(|(_, hint_a), (_, hint_b)| { + hint_a.position.cmp(&hint_b.position, &buffer_snapshot) + }); + drop(cached_excerpt_hints); + + if query.invalidate.should_invalidate() { + let mut outdated_excerpt_caches = HashSet::default(); + for (excerpt_id, excerpt_hints) in editor.inlay_hint_cache().hints.iter() { + let excerpt_hints = excerpt_hints.read(); + if excerpt_hints.buffer_id == query.buffer_id + && excerpt_id != &query.excerpt_id + && buffer_snapshot + .version() + .changed_since(&excerpt_hints.buffer_version) + { + outdated_excerpt_caches.insert(*excerpt_id); + splice + .to_remove + .extend(excerpt_hints.hints.iter().map(|(id, _)| id)); + } + } + editor + .inlay_hint_cache + .hints + .retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id)); + } + + let InlaySplice { + to_remove, + to_insert, + } = splice; + if !to_remove.is_empty() || !to_insert.is_empty() { + editor.splice_inlay_hints(to_remove, to_insert, cx) + } + } + }) + .ok(); + + Ok(update_happened) +} + +fn calculate_hint_updates( + query: ExcerptQuery, + fetch_range: Range, + new_excerpt_hints: Vec, + buffer_snapshot: &BufferSnapshot, + cached_excerpt_hints: Option>>, + visible_hints: &[Inlay], +) -> Option { + let mut add_to_cache: HashSet = HashSet::default(); + let mut excerpt_hints_to_persist = HashMap::default(); + for new_hint in new_excerpt_hints { + if !contains_position(&fetch_range, new_hint.position, buffer_snapshot) { + continue; + } + let missing_from_cache = match &cached_excerpt_hints { + Some(cached_excerpt_hints) => { + let cached_excerpt_hints = cached_excerpt_hints.read(); + match cached_excerpt_hints.hints.binary_search_by(|probe| { + probe.1.position.cmp(&new_hint.position, buffer_snapshot) + }) { + Ok(ix) => { + let (cached_inlay_id, cached_hint) = &cached_excerpt_hints.hints[ix]; + if cached_hint == &new_hint { + excerpt_hints_to_persist.insert(*cached_inlay_id, cached_hint.kind); + false + } else { + true + } + } + Err(_) => true, + } + } + None => true, + }; + if missing_from_cache { + add_to_cache.insert(new_hint); + } + } + + let mut remove_from_visible = Vec::new(); + let mut remove_from_cache = HashSet::default(); + if query.invalidate.should_invalidate() { + remove_from_visible.extend( + visible_hints + .iter() + .filter(|hint| hint.position.excerpt_id == query.excerpt_id) + .filter(|hint| { + contains_position(&fetch_range, hint.position.text_anchor, buffer_snapshot) + }) + .filter(|hint| { + fetch_range + .start + .cmp(&hint.position.text_anchor, buffer_snapshot) + .is_le() + && fetch_range + .end + .cmp(&hint.position.text_anchor, buffer_snapshot) + .is_ge() + }) + .map(|inlay_hint| inlay_hint.id) + .filter(|hint_id| !excerpt_hints_to_persist.contains_key(hint_id)), + ); + + if let Some(cached_excerpt_hints) = &cached_excerpt_hints { + let cached_excerpt_hints = cached_excerpt_hints.read(); + remove_from_cache.extend( + cached_excerpt_hints + .hints + .iter() + .filter(|(cached_inlay_id, _)| { + !excerpt_hints_to_persist.contains_key(cached_inlay_id) + }) + .filter(|(_, cached_hint)| { + fetch_range + .start + .cmp(&cached_hint.position, buffer_snapshot) + .is_le() + && fetch_range + .end + .cmp(&cached_hint.position, buffer_snapshot) + .is_ge() + }) + .map(|(cached_inlay_id, _)| *cached_inlay_id), + ); + } + } + + if remove_from_visible.is_empty() && remove_from_cache.is_empty() && add_to_cache.is_empty() { + None + } else { + Some(ExcerptHintsUpdate { + excerpt_id: query.excerpt_id, + remove_from_visible, + remove_from_cache, + add_to_cache, + }) + } +} + +fn contains_position( + range: &Range, + position: language::Anchor, + buffer_snapshot: &BufferSnapshot, +) -> bool { + range.start.cmp(&position, buffer_snapshot).is_le() + && range.end.cmp(&position, buffer_snapshot).is_ge() +} + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + + use crate::{ + scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount}, + serde_json::json, + ExcerptRange, InlayHintSettings, + }; + use futures::StreamExt; + use gpui::{executor::Deterministic, TestAppContext, ViewHandle}; + use language::{ + language_settings::AllLanguageSettingsContent, FakeLspAdapter, Language, LanguageConfig, + }; + use lsp::FakeLanguageServer; + use parking_lot::Mutex; + use project::{FakeFs, Project}; + use settings::SettingsStore; + use text::Point; + use workspace::Workspace; + + use crate::editor_tests::update_test_settings; + + use super::*; + + #[gpui::test] + async fn test_basic_cache_update_with_duplicate_hints(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; + let lsp_request_count = Arc::new(AtomicU32::new(0)); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_count = Arc::clone(&lsp_request_count); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + let current_call_id = + Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); + let mut new_hints = Vec::with_capacity(2 * current_call_id as usize); + for _ in 0..2 { + let mut i = current_call_id; + loop { + new_hints.push(lsp::InlayHint { + position: lsp::Position::new(0, i), + label: lsp::InlayHintLabel::String(i.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }); + if i == 0 { + break; + } + i -= 1; + } + } + + Ok(Some(new_hints)) + } + }) + .next() + .await; + cx.foreground().run_until_parked(); + + let mut edits_made = 1; + editor.update(cx, |editor, cx| { + let expected_layers = vec!["0".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + + editor.update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input("some change", cx); + edits_made += 1; + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec!["0".to_string(), "1".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should get new hints after an edit" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + edits_made += 1; + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec!["0".to_string(), "1".to_string(), "2".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should get new hints after hint refresh/ request" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + } + + #[gpui::test] + async fn test_hint_setting_changes(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; + let lsp_request_count = Arc::new(AtomicU32::new(0)); + let another_lsp_request_count = Arc::clone(&lsp_request_count); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_count = Arc::clone(&another_lsp_request_count); + async move { + Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst); + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + Ok(Some(vec![ + lsp::InlayHint { + position: lsp::Position::new(0, 1), + label: lsp::InlayHintLabel::String("type hint".to_string()), + kind: Some(lsp::InlayHintKind::TYPE), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(0, 2), + label: lsp::InlayHintLabel::String("parameter hint".to_string()), + kind: Some(lsp::InlayHintKind::PARAMETER), + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + lsp::InlayHint { + position: lsp::Position::new(0, 3), + label: lsp::InlayHintLabel::String("other hint".to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }, + ])) + } + }) + .next() + .await; + cx.foreground().run_until_parked(); + + let mut edits_made = 1; + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 1, + "Should query new hints once" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + "Should get its first hints when opening the editor" + ); + assert_eq!( + vec!["other hint".to_string(), "type hint".to_string()], + visible_hint_labels(editor, cx) + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor update the cache version after every cache/view change" + ); + }); + + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should load new hints twice" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + "Cached hints should not change due to allowed hint kinds settings update" + ); + assert_eq!( + vec!["other hint".to_string(), "type hint".to_string()], + visible_hint_labels(editor, cx) + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, edits_made, + "Should not update cache version due to new loaded hints being the same" + ); + }); + + for (new_allowed_hint_kinds, expected_visible_hints) in [ + (HashSet::from_iter([None]), vec!["other hint".to_string()]), + ( + HashSet::from_iter([Some(InlayHintKind::Type)]), + vec!["type hint".to_string()], + ), + ( + HashSet::from_iter([Some(InlayHintKind::Parameter)]), + vec!["parameter hint".to_string()], + ), + ( + HashSet::from_iter([None, Some(InlayHintKind::Type)]), + vec!["other hint".to_string(), "type hint".to_string()], + ), + ( + HashSet::from_iter([None, Some(InlayHintKind::Parameter)]), + vec!["other hint".to_string(), "parameter hint".to_string()], + ), + ( + HashSet::from_iter([Some(InlayHintKind::Type), Some(InlayHintKind::Parameter)]), + vec!["parameter hint".to_string(), "type hint".to_string()], + ), + ( + HashSet::from_iter([ + None, + Some(InlayHintKind::Type), + Some(InlayHintKind::Parameter), + ]), + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + ), + ] { + edits_made += 1; + update_test_settings(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: new_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: new_allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: new_allowed_hint_kinds.contains(&None), + }) + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should not load new hints on allowed hint kinds change for hint kinds {new_allowed_hint_kinds:?}" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + "Should get its cached hints unchanged after the settings change for hint kinds {new_allowed_hint_kinds:?}" + ); + assert_eq!( + expected_visible_hints, + visible_hint_labels(editor, cx), + "Should get its visible hints filtered after the settings change for hint kinds {new_allowed_hint_kinds:?}" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, new_allowed_hint_kinds, + "Cache should use editor settings to get the allowed hint kinds for hint kinds {new_allowed_hint_kinds:?}" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor should update the cache version after every cache/view change for hint kinds {new_allowed_hint_kinds:?} due to visible hints change" + ); + }); + } + + edits_made += 1; + let another_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Type)]); + update_test_settings(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: false, + show_type_hints: another_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: another_allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: another_allowed_hint_kinds.contains(&None), + }) + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should not load new hints when hints got disabled" + ); + assert!( + cached_hint_labels(editor).is_empty(), + "Should clear the cache when hints got disabled" + ); + assert!( + visible_hint_labels(editor, cx).is_empty(), + "Should clear visible hints when hints got disabled" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds, + "Should update its allowed hint kinds even when hints got disabled" + ); + assert_eq!( + inlay_cache.version, edits_made, + "The editor should update the cache version after hints got disabled" + ); + }); + + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should not load new hints when they got disabled" + ); + assert!(cached_hint_labels(editor).is_empty()); + assert!(visible_hint_labels(editor, cx).is_empty()); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, another_allowed_hint_kinds); + assert_eq!( + inlay_cache.version, edits_made, + "The editor should not update the cache version after /refresh query without updates" + ); + }); + + let final_allowed_hint_kinds = HashSet::from_iter([Some(InlayHintKind::Parameter)]); + edits_made += 1; + update_test_settings(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: final_allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: final_allowed_hint_kinds + .contains(&Some(InlayHintKind::Parameter)), + show_other_hints: final_allowed_hint_kinds.contains(&None), + }) + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 3, + "Should query for new hints when they got reenabled" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + "Should get its cached hints fully repopulated after the hints got reenabled" + ); + assert_eq!( + vec!["parameter hint".to_string()], + visible_hint_labels(editor, cx), + "Should get its visible hints repopulated and filtered after the h" + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!( + inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds, + "Cache should update editor settings when hints got reenabled" + ); + assert_eq!( + inlay_cache.version, edits_made, + "Cache should update its version after hints got reenabled" + ); + }); + + fake_server + .request::(()) + .await + .expect("inlay refresh request failed"); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 4, + "Should query for new hints again" + ); + assert_eq!( + vec![ + "other hint".to_string(), + "parameter hint".to_string(), + "type hint".to_string(), + ], + cached_hint_labels(editor), + ); + assert_eq!( + vec!["parameter hint".to_string()], + visible_hint_labels(editor, cx), + ); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, final_allowed_hint_kinds); + assert_eq!(inlay_cache.version, edits_made); + }); + } + + #[gpui::test] + async fn test_hint_request_cancellation(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + let (file_with_hints, editor, fake_server) = prepare_test_objects(cx).await; + let fake_server = Arc::new(fake_server); + let lsp_request_count = Arc::new(AtomicU32::new(0)); + let another_lsp_request_count = Arc::clone(&lsp_request_count); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_count = Arc::clone(&another_lsp_request_count); + async move { + let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path(file_with_hints).unwrap(), + ); + Ok(Some(vec![lsp::InlayHint { + position: lsp::Position::new(0, i), + label: lsp::InlayHintLabel::String(i.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }])) + } + }) + .next() + .await; + + let mut expected_changes = Vec::new(); + for change_after_opening in [ + "initial change #1", + "initial change #2", + "initial change #3", + ] { + editor.update(cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(change_after_opening, cx); + }); + expected_changes.push(change_after_opening); + } + + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let current_text = editor.text(cx); + for change in &expected_changes { + assert!( + current_text.contains(change), + "Should apply all changes made" + ); + } + assert_eq!( + lsp_request_count.load(Ordering::Relaxed), + 2, + "Should query new hints twice: for editor init and for the last edit that interrupted all others" + ); + let expected_hints = vec!["2".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last edit landed only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 1, + "Only one update should be registered in the cache after all cancellations" + ); + }); + + let mut edits = Vec::new(); + for async_later_change in [ + "another change #1", + "another change #2", + "another change #3", + ] { + expected_changes.push(async_later_change); + let task_editor = editor.clone(); + let mut task_cx = cx.clone(); + edits.push(cx.foreground().spawn(async move { + task_editor.update(&mut task_cx, |editor, cx| { + editor.change_selections(None, cx, |s| s.select_ranges([13..13])); + editor.handle_input(async_later_change, cx); + }); + })); + } + let _ = futures::future::join_all(edits).await; + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let current_text = editor.text(cx); + for change in &expected_changes { + assert!( + current_text.contains(change), + "Should apply all changes made" + ); + } + assert_eq!( + lsp_request_count.load(Ordering::SeqCst), + 3, + "Should query new hints one more time, for the last edit only" + ); + let expected_hints = vec!["3".to_string()]; + assert_eq!( + expected_hints, + cached_hint_labels(editor), + "Should get hints from the last edit landed only" + ); + assert_eq!(expected_hints, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 2, + "Should update the cache version once more, for the new change" + ); + }); + } + + #[gpui::test] + async fn test_large_buffer_inlay_requests_split(cx: &mut gpui::TestAppContext) { + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + 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_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 fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": format!("fn main() {{\n{}\n}}", "let i = 5;\n".repeat(500)), + "other.rs": "// Test file", + }), + ) + .await; + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + cx.foreground().run_until_parked(); + cx.foreground().start_waiting(); + let fake_server = fake_servers.next().await.unwrap(); + let editor = workspace + .update(cx, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + let lsp_request_ranges = Arc::new(Mutex::new(Vec::new())); + let lsp_request_count = Arc::new(AtomicU32::new(0)); + let closure_lsp_request_ranges = Arc::clone(&lsp_request_ranges); + let closure_lsp_request_count = Arc::clone(&lsp_request_count); + fake_server + .handle_request::(move |params, _| { + let task_lsp_request_ranges = Arc::clone(&closure_lsp_request_ranges); + let task_lsp_request_count = Arc::clone(&closure_lsp_request_count); + async move { + assert_eq!( + params.text_document.uri, + lsp::Url::from_file_path("/a/main.rs").unwrap(), + ); + + task_lsp_request_ranges.lock().push(params.range); + let query_start = params.range.start; + let i = Arc::clone(&task_lsp_request_count).fetch_add(1, Ordering::SeqCst) + 1; + Ok(Some(vec![lsp::InlayHint { + position: query_start, + label: lsp::InlayHintLabel::String(i.to_string()), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }])) + } + }) + .next() + .await; + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let mut ranges = lsp_request_ranges.lock().drain(..).collect::>(); + ranges.sort_by_key(|range| range.start); + assert_eq!(ranges.len(), 2, "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints"); + assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document"); + assert_eq!(ranges[0].end.line, ranges[1].start.line, "Both requests should be on the same line"); + assert_eq!(ranges[0].end.character + 1, ranges[1].start.character, "Both request should be concequent"); + + assert_eq!(lsp_request_count.load(Ordering::SeqCst), 2, + "When scroll is at the edge of a big document, its visible part + the rest should be queried for hints"); + let expected_layers = vec!["1".to_string(), "2".to_string()]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "Should have hints from both LSP requests made for a big file" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!( + inlay_cache.version, 2, + "Both LSP queries should've bumped the cache version" + ); + }); + + editor.update(cx, |editor, cx| { + editor.scroll_screen(&ScrollAmount::Page(1.0), cx); + editor.scroll_screen(&ScrollAmount::Page(1.0), cx); + editor.change_selections(None, cx, |s| s.select_ranges([600..600])); + editor.handle_input("++++more text++++", cx); + }); + + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let mut ranges = lsp_request_ranges.lock().drain(..).collect::>(); + ranges.sort_by_key(|range| range.start); + assert_eq!(ranges.len(), 3, "When scroll is at the middle of a big document, its visible part + 2 other inbisible parts should be queried for hints"); + assert_eq!(ranges[0].start, lsp::Position::new(0, 0), "Should query from the beginning of the document"); + assert_eq!(ranges[0].end.line + 1, ranges[1].start.line, "Neighbour requests got on different lines due to the line end"); + assert_ne!(ranges[0].end.character, 0, "First query was in the end of the line, not in the beginning"); + assert_eq!(ranges[1].start.character, 0, "Second query got pushed into a new line and starts from the beginning"); + assert_eq!(ranges[1].end.line, ranges[2].start.line, "Neighbour requests should be on the same line"); + assert_eq!(ranges[1].end.character + 1, ranges[2].start.character, "Neighbour request should be concequent"); + + assert_eq!(lsp_request_count.load(Ordering::SeqCst), 5, + "When scroll not at the edge of a big document, visible part + 2 other parts should be queried for hints"); + let expected_layers = vec!["3".to_string(), "4".to_string(), "5".to_string()]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "Should have hints from the new LSP response after edit"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 5, "Should update the cache for every LSP response with hints added"); + }); + } + + #[gpui::test] + async fn test_multiple_excerpts_large_multibuffer( + deterministic: Arc, + cx: &mut gpui::TestAppContext, + ) { + let allowed_hint_kinds = HashSet::from_iter([None, Some(InlayHintKind::Type)]); + init_test(cx, |settings| { + settings.defaults.inlay_hints = Some(InlayHintSettings { + enabled: true, + show_type_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Type)), + show_parameter_hints: allowed_hint_kinds.contains(&Some(InlayHintKind::Parameter)), + show_other_hints: allowed_hint_kinds.contains(&None), + }) + }); + + 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_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); + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|i| format!("let i = {i};\n")).collect::>().join("")), + "other.rs": format!("fn main() {{\n{}\n}}", (0..501).map(|j| format!("let j = {j};\n")).collect::>().join("")), + }), + ) + .await; + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| { + project.languages().add(Arc::clone(&language)) + }); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + let buffer_1 = project + .update(cx, |project, cx| { + project.open_buffer((worktree_id, "main.rs"), cx) + }) + .await + .unwrap(); + let buffer_2 = project + .update(cx, |project, cx| { + project.open_buffer((worktree_id, "other.rs"), cx) + }) + .await + .unwrap(); + let multibuffer = cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + multibuffer.push_excerpts( + buffer_1.clone(), + [ + ExcerptRange { + context: Point::new(0, 0)..Point::new(2, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(4, 0)..Point::new(11, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(22, 0)..Point::new(33, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(44, 0)..Point::new(55, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(56, 0)..Point::new(66, 0), + primary: None, + }, + ExcerptRange { + context: Point::new(67, 0)..Point::new(77, 0), + primary: None, + }, + ], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ + ExcerptRange { + context: Point::new(0, 1)..Point::new(2, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(4, 1)..Point::new(11, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(22, 1)..Point::new(33, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(44, 1)..Point::new(55, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(56, 1)..Point::new(66, 1), + primary: None, + }, + ExcerptRange { + context: Point::new(67, 1)..Point::new(77, 1), + primary: None, + }, + ], + cx, + ); + multibuffer + }); + + deterministic.run_until_parked(); + cx.foreground().run_until_parked(); + let (_, editor) = + cx.add_window(|cx| Editor::for_multibuffer(multibuffer, Some(project.clone()), cx)); + let editor_edited = Arc::new(AtomicBool::new(false)); + let fake_server = fake_servers.next().await.unwrap(); + let closure_editor_edited = Arc::clone(&editor_edited); + fake_server + .handle_request::(move |params, _| { + let task_editor_edited = Arc::clone(&closure_editor_edited); + async move { + let hint_text = if params.text_document.uri + == lsp::Url::from_file_path("/a/main.rs").unwrap() + { + "main hint" + } else if params.text_document.uri + == lsp::Url::from_file_path("/a/other.rs").unwrap() + { + "other hint" + } else { + panic!("unexpected uri: {:?}", params.text_document.uri); + }; + + let positions = [ + lsp::Position::new(0, 2), + lsp::Position::new(4, 2), + lsp::Position::new(22, 2), + lsp::Position::new(44, 2), + lsp::Position::new(56, 2), + lsp::Position::new(67, 2), + ]; + let out_of_range_hint = lsp::InlayHint { + position: lsp::Position::new( + params.range.start.line + 99, + params.range.start.character + 99, + ), + label: lsp::InlayHintLabel::String( + "out of excerpt range, should be ignored".to_string(), + ), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + }; + + let edited = task_editor_edited.load(Ordering::Acquire); + Ok(Some( + std::iter::once(out_of_range_hint) + .chain(positions.into_iter().enumerate().map(|(i, position)| { + lsp::InlayHint { + position, + label: lsp::InlayHintLabel::String(format!( + "{hint_text}{} #{i}", + if edited { "(edited)" } else { "" }, + )), + kind: None, + text_edits: None, + tooltip: None, + padding_left: None, + padding_right: None, + data: None, + } + })) + .collect(), + )) + } + }) + .next() + .await; + cx.foreground().run_until_parked(); + + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + ]; + assert_eq!( + expected_layers, + cached_hint_labels(editor), + "When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints" + ); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 4, "Every visible excerpt hints should bump the verison"); + }); + + editor.update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(22, 0)..Point::new(22, 0)]) + }); + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(56, 0)..Point::new(56, 0)]) + }); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #4".to_string(), + "main hint #5".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 9); + }); + + editor.update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(100, 0)..Point::new(100, 0)]) + }); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #4".to_string(), + "main hint #5".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + "other hint #3".to_string(), + "other hint #4".to_string(), + "other hint #5".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "After multibuffer was scrolled to the end, all hints for all excerpts should be fetched"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 12); + }); + + editor.update(cx, |editor, cx| { + editor.change_selections(Some(Autoscroll::Next), cx, |s| { + s.select_ranges([Point::new(4, 0)..Point::new(4, 0)]) + }); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint #0".to_string(), + "main hint #1".to_string(), + "main hint #2".to_string(), + "main hint #3".to_string(), + "main hint #4".to_string(), + "main hint #5".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + "other hint #3".to_string(), + "other hint #4".to_string(), + "other hint #5".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "After multibuffer was scrolled to the end, further scrolls up should not bring more hints"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 12, "No updates should happen during scrolling already scolled buffer"); + }); + + editor_edited.store(true, Ordering::Release); + editor.update(cx, |editor, cx| { + editor.handle_input("++++more text++++", cx); + }); + cx.foreground().run_until_parked(); + editor.update(cx, |editor, cx| { + let expected_layers = vec![ + "main hint(edited) #0".to_string(), + "main hint(edited) #1".to_string(), + "main hint(edited) #2".to_string(), + "main hint(edited) #3".to_string(), + "other hint #0".to_string(), + "other hint #1".to_string(), + "other hint #2".to_string(), + "other hint #3".to_string(), + "other hint #4".to_string(), + "other hint #5".to_string(), + ]; + assert_eq!(expected_layers, cached_hint_labels(editor), + "After multibuffer was edited, hints for the edited buffer (1st) should be invalidated and requeried for all of its visible excerpts, \ +unedited (2nd) buffer should have the same hint"); + assert_eq!(expected_layers, visible_hint_labels(editor, cx)); + let inlay_cache = editor.inlay_hint_cache(); + assert_eq!(inlay_cache.allowed_hint_kinds, allowed_hint_kinds); + assert_eq!(inlay_cache.version, 16); + }); + } + + pub(crate) fn init_test(cx: &mut TestAppContext, f: impl Fn(&mut AllLanguageSettingsContent)) { + cx.foreground().forbid_parking(); + + cx.update(|cx| { + cx.set_global(SettingsStore::test(cx)); + theme::init((), cx); + client::init_settings(cx); + language::init(cx); + Project::init_settings(cx); + workspace::init_settings(cx); + crate::init(cx); + }); + + update_test_settings(cx, f); + } + + async fn prepare_test_objects( + cx: &mut TestAppContext, + ) -> (&'static str, ViewHandle, FakeLanguageServer) { + 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_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 fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/a", + json!({ + "main.rs": "fn main() { a } // and some long comment to ensure inlays are not trimmed out", + "other.rs": "// Test file", + }), + ) + .await; + + let project = Project::test(fs, ["/a".as_ref()], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let worktree_id = workspace.update(cx, |workspace, cx| { + workspace.project().read_with(cx, |project, cx| { + project.worktrees(cx).next().unwrap().read(cx).id() + }) + }); + + let _buffer = project + .update(cx, |project, cx| { + project.open_local_buffer("/a/main.rs", cx) + }) + .await + .unwrap(); + cx.foreground().run_until_parked(); + cx.foreground().start_waiting(); + let fake_server = fake_servers.next().await.unwrap(); + let editor = workspace + .update(cx, |workspace, cx| { + workspace.open_path((worktree_id, "main.rs"), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + ("/a/main.rs", editor, fake_server) + } + + fn cached_hint_labels(editor: &Editor) -> Vec { + let mut labels = Vec::new(); + for (_, excerpt_hints) in &editor.inlay_hint_cache().hints { + let excerpt_hints = excerpt_hints.read(); + for (_, inlay) in excerpt_hints.hints.iter() { + match &inlay.label { + project::InlayHintLabel::String(s) => labels.push(s.to_string()), + _ => unreachable!(), + } + } + } + + labels.sort(); + labels + } + + fn visible_hint_labels(editor: &Editor, cx: &ViewContext<'_, '_, Editor>) -> Vec { + let mut hints = editor + .visible_inlay_hints(cx) + .into_iter() + .map(|hint| hint.text.to_string()) + .collect::>(); + hints.sort(); + hints + } +} diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/editor/src/multi_buffer/anchor.rs index 9a5145c244..1be4dc2dfb 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/editor/src/multi_buffer/anchor.rs @@ -49,6 +49,10 @@ impl Anchor { } } + pub fn bias(&self) -> Bias { + self.text_anchor.bias + } + pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { if self.text_anchor.bias != Bias::Left { if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { @@ -81,6 +85,19 @@ impl Anchor { { snapshot.summary_for_anchor(self) } + + pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool { + if *self == Anchor::min() || *self == Anchor::max() { + true + } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + excerpt.contains(self) + && (self.text_anchor == excerpt.range.context.start + || self.text_anchor == excerpt.range.context.end + || self.text_anchor.is_valid(&excerpt.buffer)) + } else { + false + } + } } impl ToOffset for Anchor { diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 09b75bc680..d595337428 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -13,13 +13,14 @@ use gpui::{ }; use language::{Bias, Point}; use util::ResultExt; -use workspace::WorkspaceId; +use workspace::{item::Item, WorkspaceId}; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, persistence::DB, - Anchor, DisplayPoint, Editor, EditorMode, Event, MultiBufferSnapshot, ToPoint, + Anchor, DisplayPoint, Editor, EditorMode, Event, InlayRefreshReason, MultiBufferSnapshot, + ToPoint, }; use self::{ @@ -293,8 +294,19 @@ impl Editor { self.scroll_manager.visible_line_count } - pub(crate) fn set_visible_line_count(&mut self, lines: f32) { - self.scroll_manager.visible_line_count = Some(lines) + pub(crate) fn set_visible_line_count(&mut self, lines: f32, cx: &mut ViewContext) { + let opened_first_time = self.scroll_manager.visible_line_count.is_none(); + self.scroll_manager.visible_line_count = Some(lines); + if opened_first_time { + cx.spawn(|editor, mut cx| async move { + editor + .update(&mut cx, |editor, cx| { + editor.refresh_inlays(InlayRefreshReason::NewLinesShown, cx) + }) + .ok() + }) + .detach() + } } pub fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext) { @@ -320,6 +332,10 @@ impl Editor { workspace_id, cx, ); + + if !self.is_singleton(cx) { + self.refresh_inlays(InlayRefreshReason::NewLinesShown, cx); + } } pub fn scroll_position(&self, cx: &mut ViewContext) -> Vector2F { diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index e487b64c4e..592e6c9a53 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -388,6 +388,7 @@ struct FakeFsState { event_txs: Vec>>, events_paused: bool, buffered_events: Vec, + metadata_call_count: usize, read_dir_call_count: usize, } @@ -538,6 +539,7 @@ impl FakeFs { buffered_events: Vec::new(), events_paused: false, read_dir_call_count: 0, + metadata_call_count: 0, }), }) } @@ -774,10 +776,16 @@ impl FakeFs { result } + /// How many `read_dir` calls have been issued. pub fn read_dir_call_count(&self) -> usize { self.state.lock().read_dir_call_count } + /// How many `metadata` calls have been issued. + pub fn metadata_call_count(&self) -> usize { + self.state.lock().metadata_call_count + } + async fn simulate_random_delay(&self) { self.executor .upgrade() @@ -1098,7 +1106,8 @@ impl Fs for FakeFs { async fn metadata(&self, path: &Path) -> Result> { self.simulate_random_delay().await; let path = normalize_path(path); - let state = self.state.lock(); + let mut state = self.state.lock(); + state.metadata_call_count += 1; if let Some((mut entry, _)) = state.try_read_path(&path, false) { let is_symlink = entry.lock().is_symlink(); if is_symlink { diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index cffce6c3a6..23fbb33fe1 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -8,8 +8,8 @@ use crate::{ MouseButton, MouseMovedEvent, PromptLevel, WindowBounds, }, scene::{ - CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, - MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, + CursorRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, MouseEvent, + MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene, }, text_layout::TextLayoutCache, util::post_inc, @@ -524,6 +524,10 @@ impl<'a> WindowContext<'a> { region: Default::default(), platform_event: e.clone(), })); + mouse_events.push(MouseEvent::ClickOut(MouseClickOut { + region: Default::default(), + platform_event: e.clone(), + })); } Event::MouseMoved( @@ -712,7 +716,10 @@ impl<'a> WindowContext<'a> { } } - MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => { + MouseEvent::MoveOut(_) + | MouseEvent::UpOut(_) + | MouseEvent::DownOut(_) + | MouseEvent::ClickOut(_) => { for (mouse_region, _) in self.window.mouse_regions.iter().rev() { // NOT contains if !mouse_region diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 6f2762db66..1b8142d964 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -7,8 +7,8 @@ use crate::{ platform::CursorStyle, platform::MouseButton, scene::{ - CursorRegion, HandlerSet, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseHover, - MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, + CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, + MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, }, AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, SceneBuilder, SizeConstraint, View, ViewContext, @@ -136,6 +136,15 @@ impl MouseEventHandler { self } + pub fn on_click_out( + mut self, + button: MouseButton, + handler: impl Fn(MouseClickOut, &mut V, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_click_out(button, handler); + self + } + pub fn on_down_out( mut self, button: MouseButton, diff --git a/crates/gpui/src/scene/mouse_event.rs b/crates/gpui/src/scene/mouse_event.rs index cf0a08f33e..a492da771b 100644 --- a/crates/gpui/src/scene/mouse_event.rs +++ b/crates/gpui/src/scene/mouse_event.rs @@ -99,6 +99,20 @@ impl Deref for MouseClick { } } +#[derive(Debug, Default, Clone)] +pub struct MouseClickOut { + pub region: RectF, + pub platform_event: MouseButtonEvent, +} + +impl Deref for MouseClickOut { + type Target = MouseButtonEvent; + + fn deref(&self) -> &Self::Target { + &self.platform_event + } +} + #[derive(Debug, Default, Clone)] pub struct MouseDownOut { pub region: RectF, @@ -150,6 +164,7 @@ pub enum MouseEvent { Down(MouseDown), Up(MouseUp), Click(MouseClick), + ClickOut(MouseClickOut), DownOut(MouseDownOut), UpOut(MouseUpOut), ScrollWheel(MouseScrollWheel), @@ -165,6 +180,7 @@ impl MouseEvent { MouseEvent::Down(r) => r.region = region, MouseEvent::Up(r) => r.region = region, MouseEvent::Click(r) => r.region = region, + MouseEvent::ClickOut(r) => r.region = region, MouseEvent::DownOut(r) => r.region = region, MouseEvent::UpOut(r) => r.region = region, MouseEvent::ScrollWheel(r) => r.region = region, @@ -182,6 +198,7 @@ impl MouseEvent { MouseEvent::Down(_) => true, MouseEvent::Up(_) => true, MouseEvent::Click(_) => true, + MouseEvent::ClickOut(_) => true, MouseEvent::DownOut(_) => false, MouseEvent::UpOut(_) => false, MouseEvent::ScrollWheel(_) => true, @@ -222,6 +239,10 @@ impl MouseEvent { discriminant(&MouseEvent::Click(Default::default())) } + pub fn click_out_disc() -> Discriminant { + discriminant(&MouseEvent::ClickOut(Default::default())) + } + pub fn down_out_disc() -> Discriminant { discriminant(&MouseEvent::DownOut(Default::default())) } @@ -239,6 +260,7 @@ impl MouseEvent { MouseEvent::Down(e) => HandlerKey::new(Self::down_disc(), Some(e.button)), MouseEvent::Up(e) => HandlerKey::new(Self::up_disc(), Some(e.button)), MouseEvent::Click(e) => HandlerKey::new(Self::click_disc(), Some(e.button)), + MouseEvent::ClickOut(e) => HandlerKey::new(Self::click_out_disc(), Some(e.button)), MouseEvent::UpOut(e) => HandlerKey::new(Self::up_out_disc(), Some(e.button)), MouseEvent::DownOut(e) => HandlerKey::new(Self::down_out_disc(), Some(e.button)), MouseEvent::ScrollWheel(_) => HandlerKey::new(Self::scroll_wheel_disc(), None), diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 0efc794148..ca2cc04b9d 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -14,7 +14,7 @@ use super::{ MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover, MouseMove, MouseUp, MouseUpOut, }, - MouseMoveOut, MouseScrollWheel, + MouseClickOut, MouseMoveOut, MouseScrollWheel, }; #[derive(Clone)] @@ -89,6 +89,15 @@ impl MouseRegion { self } + pub fn on_click_out(mut self, button: MouseButton, handler: F) -> Self + where + V: View, + F: Fn(MouseClickOut, &mut V, &mut EventContext) + 'static, + { + self.handlers = self.handlers.on_click_out(button, handler); + self + } + pub fn on_down_out(mut self, button: MouseButton, handler: F) -> Self where V: View, @@ -246,6 +255,10 @@ impl HandlerSet { HandlerKey::new(MouseEvent::click_disc(), Some(button)), SmallVec::from_buf([Rc::new(|_, _, _, _| true)]), ); + set.insert( + HandlerKey::new(MouseEvent::click_out_disc(), Some(button)), + SmallVec::from_buf([Rc::new(|_, _, _, _| true)]), + ); set.insert( HandlerKey::new(MouseEvent::down_out_disc(), Some(button)), SmallVec::from_buf([Rc::new(|_, _, _, _| true)]), @@ -405,6 +418,28 @@ impl HandlerSet { self } + pub fn on_click_out(mut self, button: MouseButton, handler: F) -> Self + where + V: View, + F: Fn(MouseClickOut, &mut V, &mut EventContext) + 'static, + { + self.insert(MouseEvent::click_out_disc(), Some(button), + Rc::new(move |region_event, view, cx, view_id| { + if let MouseEvent::ClickOut(e) = region_event { + let view = view.downcast_mut().unwrap(); + let mut cx = ViewContext::mutable(cx, view_id); + let mut cx = EventContext::new(&mut cx); + handler(e, view, &mut cx); + cx.handled + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ClickOut, found {:?}", + region_event); + } + })); + self + } + pub fn on_down_out(mut self, button: MouseButton, handler: F) -> Self where V: View, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 4c6f709f38..b880cbc8d7 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,7 @@ use futures::{ use gpui::{executor::Background, AppContext, AsyncAppContext, Task}; use highlight_map::HighlightMap; use lazy_static::lazy_static; -use lsp::CodeActionKind; +use lsp::{CodeActionKind, LanguageServerBinary}; use parking_lot::{Mutex, RwLock}; use postage::watch; use regex::Regex; @@ -30,7 +30,6 @@ use std::{ any::Any, borrow::Cow, cell::RefCell, - ffi::OsString, fmt::Debug, hash::Hash, mem, @@ -86,12 +85,6 @@ pub trait ToLspPosition { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LanguageServerName(pub Arc); -#[derive(Debug, Clone, Deserialize)] -pub struct LanguageServerBinary { - pub path: PathBuf, - pub arguments: Vec, -} - /// Represents a Language Server, with certain cached sync properties. /// Uses [`LspAdapter`] under the hood, but calls all 'static' methods /// once at startup, and caches the results. @@ -167,6 +160,17 @@ impl CachedLspAdapter { .await } + pub fn can_be_reinstalled(&self) -> bool { + self.adapter.can_be_reinstalled() + } + + pub async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + self.adapter.installation_test_binary(container_dir).await + } + pub fn code_action_kinds(&self) -> Option> { self.adapter.code_action_kinds() } @@ -249,6 +253,15 @@ pub trait LspAdapter: 'static + Send + Sync { delegate: &dyn LspAdapterDelegate, ) -> Option; + fn can_be_reinstalled(&self) -> bool { + true + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option; + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn process_completion(&self, _: &mut lsp::CompletionItem) {} @@ -576,7 +589,8 @@ struct LanguageRegistryState { pub struct PendingLanguageServer { pub server_id: LanguageServerId, - pub task: Task>, + pub task: Task>>, + pub container_dir: Option>, } impl LanguageRegistry { @@ -848,7 +862,7 @@ impl LanguageRegistry { self.state.read().languages.iter().cloned().collect() } - pub fn start_language_server( + pub fn create_pending_language_server( self: &Arc, language: Arc, adapter: Arc, @@ -858,7 +872,7 @@ impl LanguageRegistry { ) -> Option { let server_id = self.state.write().next_language_server_id(); log::info!( - "starting language server name:{}, path:{root_path:?}, id:{server_id}", + "starting language server {:?}, path: {root_path:?}, id: {server_id}", adapter.name.0 ); @@ -888,66 +902,81 @@ impl LanguageRegistry { } }) .detach(); - Ok(server) + + Ok(Some(server)) }); - return Some(PendingLanguageServer { server_id, task }); + return Some(PendingLanguageServer { + server_id, + task, + container_dir: None, + }); } let download_dir = self .language_server_download_dir .clone() - .ok_or_else(|| anyhow!("language server download directory has not been assigned")) + .ok_or_else(|| anyhow!("language server download directory has not been assigned before starting server")) .log_err()?; let this = self.clone(); let language = language.clone(); - let download_dir = download_dir.clone(); + let container_dir: Arc = Arc::from(download_dir.join(adapter.name.0.as_ref())); let root_path = root_path.clone(); let adapter = adapter.clone(); let lsp_binary_statuses = self.lsp_binary_statuses_tx.clone(); let login_shell_env_loaded = self.login_shell_env_loaded.clone(); - let task = cx.spawn(|mut cx| async move { - login_shell_env_loaded.await; + let task = { + let container_dir = container_dir.clone(); + cx.spawn(|mut cx| async move { + login_shell_env_loaded.await; - let entry = this - .lsp_binary_paths - .lock() - .entry(adapter.name.clone()) - .or_insert_with(|| { - cx.spawn(|cx| { - get_binary( - adapter.clone(), - language.clone(), - delegate.clone(), - download_dir, - lsp_binary_statuses, - cx, - ) - .map_err(Arc::new) + let mut lock = this.lsp_binary_paths.lock(); + let entry = lock + .entry(adapter.name.clone()) + .or_insert_with(|| { + cx.spawn(|cx| { + get_binary( + adapter.clone(), + language.clone(), + delegate.clone(), + container_dir, + lsp_binary_statuses, + cx, + ) + .map_err(Arc::new) + }) + .shared() }) - .shared() - }) - .clone(); - let binary = entry.clone().map_err(|e| anyhow!(e)).await?; + .clone(); + drop(lock); - if let Some(task) = adapter.will_start_server(&delegate, &mut cx) { - task.await?; - } + let binary = match entry.clone().await.log_err() { + Some(binary) => binary, + None => return Ok(None), + }; - let server = lsp::LanguageServer::new( - server_id, - &binary.path, - &binary.arguments, - &root_path, - adapter.code_action_kinds(), - cx, - )?; + if let Some(task) = adapter.will_start_server(&delegate, &mut cx) { + if task.await.log_err().is_none() { + return Ok(None); + } + } - Ok(server) - }); + Ok(Some(lsp::LanguageServer::new( + server_id, + binary, + &root_path, + adapter.code_action_kinds(), + cx, + )?)) + }) + }; - Some(PendingLanguageServer { server_id, task }) + Some(PendingLanguageServer { + server_id, + task, + container_dir: Some(container_dir), + }) } pub fn language_server_binary_statuses( @@ -955,6 +984,30 @@ impl LanguageRegistry { ) -> async_broadcast::Receiver<(Arc, LanguageServerBinaryStatus)> { self.lsp_binary_statuses_rx.clone() } + + pub fn delete_server_container( + &self, + adapter: Arc, + cx: &mut AppContext, + ) -> Task<()> { + log::info!("deleting server container"); + + let mut lock = self.lsp_binary_paths.lock(); + lock.remove(&adapter.name); + + let download_dir = self + .language_server_download_dir + .clone() + .expect("language server download directory has not been assigned before deleting server container"); + + cx.spawn(|_| async move { + let container_dir = download_dir.join(adapter.name.0.as_ref()); + smol::fs::remove_dir_all(container_dir) + .await + .context("server container removal") + .log_err(); + }) + } } impl LanguageRegistryState { @@ -1005,11 +1058,10 @@ async fn get_binary( adapter: Arc, language: Arc, delegate: Arc, - download_dir: Arc, + container_dir: Arc, statuses: async_broadcast::Sender<(Arc, LanguageServerBinaryStatus)>, mut cx: AsyncAppContext, ) -> Result { - let container_dir = download_dir.join(adapter.name.0.as_ref()); if !container_dir.exists() { smol::fs::create_dir_all(&container_dir) .await @@ -1030,14 +1082,14 @@ async fn get_binary( .await; if let Err(error) = binary.as_ref() { - if let Some(cached) = adapter - .cached_server_binary(container_dir, delegate.as_ref()) + if let Some(binary) = adapter + .cached_server_binary(container_dir.to_path_buf(), delegate.as_ref()) .await { statuses .broadcast((language.clone(), LanguageServerBinaryStatus::Cached)) .await?; - return Ok(cached); + return Ok(binary); } else { statuses .broadcast(( @@ -1049,6 +1101,7 @@ async fn get_binary( .await?; } } + binary } @@ -1066,16 +1119,19 @@ async fn fetch_latest_binary( LanguageServerBinaryStatus::CheckingForUpdate, )) .await?; + let version_info = adapter.fetch_latest_server_version(delegate).await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloading)) .await?; + let binary = adapter .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate) .await?; lsp_binary_statuses_tx .broadcast((language.clone(), LanguageServerBinaryStatus::Downloaded)) .await?; + Ok(binary) } @@ -1617,6 +1673,10 @@ impl LspAdapter for Arc { unreachable!(); } + async fn installation_test_binary(&self, _: PathBuf) -> Option { + unreachable!(); + } + async fn process_diagnostics(&self, _: &mut lsp::PublishDiagnosticsParams) {} async fn disk_based_diagnostic_sources(&self) -> Vec { diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 832bb59222..820217567a 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,6 +1,6 @@ use crate::{File, Language}; use anyhow::Result; -use collections::HashMap; +use collections::{HashMap, HashSet}; use globset::GlobMatcher; use gpui::AppContext; use schemars::{ @@ -52,6 +52,7 @@ pub struct LanguageSettings { pub show_copilot_suggestions: bool, pub show_whitespaces: ShowWhitespaceSetting, pub extend_comment_on_newline: bool, + pub inlay_hints: InlayHintSettings, } #[derive(Clone, Debug, Default)] @@ -98,6 +99,8 @@ pub struct LanguageSettingsContent { pub show_whitespaces: Option, #[serde(default)] pub extend_comment_on_newline: Option, + #[serde(default)] + pub inlay_hints: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] @@ -150,6 +153,38 @@ pub enum Formatter { }, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct InlayHintSettings { + #[serde(default)] + pub enabled: bool, + #[serde(default = "default_true")] + pub show_type_hints: bool, + #[serde(default = "default_true")] + pub show_parameter_hints: bool, + #[serde(default = "default_true")] + pub show_other_hints: bool, +} + +fn default_true() -> bool { + true +} + +impl InlayHintSettings { + pub fn enabled_inlay_hint_kinds(&self) -> HashSet> { + let mut kinds = HashSet::default(); + if self.show_type_hints { + kinds.insert(Some(InlayHintKind::Type)); + } + if self.show_parameter_hints { + kinds.insert(Some(InlayHintKind::Parameter)); + } + if self.show_other_hints { + kinds.insert(None); + } + kinds + } +} + impl AllLanguageSettings { pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings { if let Some(name) = language_name { @@ -184,6 +219,29 @@ impl AllLanguageSettings { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum InlayHintKind { + Type, + Parameter, +} + +impl InlayHintKind { + pub fn from_name(name: &str) -> Option { + match name { + "type" => Some(InlayHintKind::Type), + "parameter" => Some(InlayHintKind::Parameter), + _ => None, + } + } + + pub fn name(&self) -> &'static str { + match self { + InlayHintKind::Type => "type", + InlayHintKind::Parameter => "parameter", + } + } +} + impl settings::Setting for AllLanguageSettings { const KEY: Option<&'static str> = None; @@ -347,6 +405,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent &mut settings.extend_comment_on_newline, src.extend_comment_on_newline, ); + merge(&mut settings.inlay_hints, src.inlay_hints); fn merge(target: &mut T, value: Option) { if let Some(value) = value { *target = value; diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 5074989277..a01f6e8a49 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -16,6 +16,7 @@ use smol::{ process::{self, Child}, }; use std::{ + ffi::OsString, fmt, future::Future, io::Write, @@ -36,6 +37,12 @@ type NotificationHandler = Box, &str, AsyncAppCon type ResponseHandler = Box)>; type IoHandler = Box; +#[derive(Debug, Clone, Deserialize)] +pub struct LanguageServerBinary { + pub path: PathBuf, + pub arguments: Vec, +} + pub struct LanguageServer { server_id: LanguageServerId, next_id: AtomicUsize, @@ -51,7 +58,7 @@ pub struct LanguageServer { io_tasks: Mutex>, Task>)>>, output_done_rx: Mutex>, root_path: PathBuf, - _server: Option, + _server: Option>, } #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -119,10 +126,9 @@ struct Error { } impl LanguageServer { - pub fn new>( + pub fn new( server_id: LanguageServerId, - binary_path: &Path, - arguments: &[T], + binary: LanguageServerBinary, root_path: &Path, code_action_kinds: Option>, cx: AsyncAppContext, @@ -133,9 +139,9 @@ impl LanguageServer { root_path.parent().unwrap_or_else(|| Path::new("/")) }; - let mut server = process::Command::new(binary_path) + let mut server = process::Command::new(&binary.path) .current_dir(working_dir) - .args(arguments) + .args(binary.arguments) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::inherit()) @@ -167,9 +173,10 @@ impl LanguageServer { }, ); - if let Some(name) = binary_path.file_name() { + if let Some(name) = binary.path.file_name() { server.name = name.to_string_lossy().to_string(); } + Ok(server) } @@ -231,7 +238,7 @@ impl LanguageServer { io_tasks: Mutex::new(Some((input_task, output_task))), output_done_rx: Mutex::new(Some(output_done_rx)), root_path: root_path.to_path_buf(), - _server: server, + _server: server.map(|server| Mutex::new(server)), } } @@ -381,6 +388,9 @@ impl LanguageServer { resolve_support: None, ..WorkspaceSymbolClientCapabilities::default() }), + inlay_hint: Some(InlayHintWorkspaceClientCapabilities { + refresh_support: Some(true), + }), ..Default::default() }), text_document: Some(TextDocumentClientCapabilities { @@ -422,6 +432,10 @@ impl LanguageServer { content_format: Some(vec![MarkupKind::Markdown]), ..Default::default() }), + inlay_hint: Some(InlayHintClientCapabilities { + resolve_support: None, + dynamic_registration: Some(false), + }), ..Default::default() }), experimental: Some(json!({ @@ -600,6 +614,7 @@ impl LanguageServer { }) .detach(); } + Err(error) => { log::error!( "error deserializing {} request: {:?}, message: {:?}", @@ -701,7 +716,7 @@ impl LanguageServer { .context("failed to deserialize response"), Err(error) => Err(anyhow!("{}", error.message)), }; - let _ = tx.send(response); + _ = tx.send(response); }) .detach(); }), diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index fce0fdfe50..53635f2725 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -20,3 +20,4 @@ serde.workspace = true serde_derive.workspace = true serde_json.workspace = true smol.workspace = true +log.workspace = true diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index e2a8d0d003..27a763e7f8 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -1,21 +1,24 @@ use anyhow::{anyhow, bail, Context, Result}; use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; +use futures::lock::Mutex; use futures::{future::Shared, FutureExt}; use gpui::{executor::Background, Task}; -use parking_lot::Mutex; use serde::Deserialize; use smol::{fs, io::BufReader, process::Command}; +use std::process::Output; use std::{ env::consts, path::{Path, PathBuf}, - sync::Arc, + sync::{Arc, OnceLock}, }; -use util::http::HttpClient; +use util::{http::HttpClient, ResultExt}; const VERSION: &str = "v18.15.0"; -#[derive(Deserialize)] +static RUNTIME_INSTANCE: OnceLock> = OnceLock::new(); + +#[derive(Debug, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct NpmInfo { #[serde(default)] @@ -23,7 +26,7 @@ pub struct NpmInfo { versions: Vec, } -#[derive(Deserialize, Default)] +#[derive(Debug, Deserialize, Default)] pub struct NpmInfoDistTags { latest: Option, } @@ -35,12 +38,16 @@ pub struct NodeRuntime { } impl NodeRuntime { - pub fn new(http: Arc, background: Arc) -> Arc { - Arc::new(NodeRuntime { - http, - background, - installation_path: Mutex::new(None), - }) + pub fn instance(http: Arc, background: Arc) -> Arc { + RUNTIME_INSTANCE + .get_or_init(|| { + Arc::new(NodeRuntime { + http, + background, + installation_path: Mutex::new(None), + }) + }) + .clone() } pub async fn binary_path(&self) -> Result { @@ -50,55 +57,74 @@ impl NodeRuntime { pub async fn run_npm_subcommand( &self, - directory: &Path, + directory: Option<&Path>, subcommand: &str, args: &[&str], - ) -> Result<()> { + ) -> Result { + let attempt = |installation_path: PathBuf| async move { + let node_binary = installation_path.join("bin/node"); + let npm_file = installation_path.join("bin/npm"); + + if smol::fs::metadata(&node_binary).await.is_err() { + return Err(anyhow!("missing node binary file")); + } + + if smol::fs::metadata(&npm_file).await.is_err() { + return Err(anyhow!("missing npm file")); + } + + let mut command = Command::new(node_binary); + command.arg(npm_file).arg(subcommand).args(args); + + if let Some(directory) = directory { + command.current_dir(directory); + } + + command.output().await.map_err(|e| anyhow!("{e}")) + }; + let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .arg(subcommand) - .args(args) - .current_dir(directory) - .output() - .await?; - - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); + let mut output = attempt(installation_path).await; + if output.is_err() { + let installation_path = self.reinstall().await?; + output = attempt(installation_path).await; + if output.is_err() { + return Err(anyhow!( + "failed to launch npm subcommand {subcommand} subcommand" + )); + } } - Ok(()) + if let Ok(output) = &output { + if !output.status.success() { + return Err(anyhow!( + "failed to execute npm {subcommand} subcommand:\nstdout: {:?}\nstderr: {:?}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )); + } + } + + output.map_err(|e| anyhow!("{e}")) } pub async fn npm_package_latest_version(&self, name: &str) -> Result { - let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); - - let output = Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .args(["info", name, "--json"]) - .output() - .await - .context("failed to run npm info")?; - - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } + let output = self + .run_npm_subcommand( + None, + "info", + &[ + name, + "--json", + "-fetch-retry-mintimeout", + "2000", + "-fetch-retry-maxtimeout", + "5000", + "-fetch-timeout", + "5000", + ], + ) + .await?; let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?; info.dist_tags @@ -112,41 +138,54 @@ impl NodeRuntime { directory: &Path, packages: impl IntoIterator, ) -> Result<()> { - let installation_path = self.install_if_needed().await?; - let node_binary = installation_path.join("bin/node"); - let npm_file = installation_path.join("bin/npm"); + let packages: Vec<_> = packages + .into_iter() + .map(|(name, version)| format!("{name}@{version}")) + .collect(); - let output = Command::new(node_binary) - .arg(npm_file) - .args(["-fetch-retry-mintimeout", "2000"]) - .args(["-fetch-retry-maxtimeout", "5000"]) - .args(["-fetch-timeout", "5000"]) - .arg("install") - .arg("--prefix") - .arg(directory) - .args( - packages - .into_iter() - .map(|(name, version)| format!("{name}@{version}")), - ) - .output() - .await - .context("failed to run npm install")?; + let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect(); + arguments.extend_from_slice(&[ + "-fetch-retry-mintimeout", + "2000", + "-fetch-retry-maxtimeout", + "5000", + "-fetch-timeout", + "5000", + ]); - if !output.status.success() { - return Err(anyhow!( - "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )); - } + self.run_npm_subcommand(Some(directory), "install", &arguments) + .await?; Ok(()) } + async fn reinstall(&self) -> Result { + log::info!("beginnning to reinstall Node runtime"); + let mut installation_path = self.installation_path.lock().await; + + if let Some(task) = installation_path.as_ref().cloned() { + if let Ok(installation_path) = task.await { + smol::fs::remove_dir_all(&installation_path) + .await + .context("node dir removal") + .log_err(); + } + } + + let http = self.http.clone(); + let task = self + .background + .spawn(async move { Self::install(http).await.map_err(Arc::new) }) + .shared(); + + *installation_path = Some(task.clone()); + task.await.map_err(|e| anyhow!("{}", e)) + } + async fn install_if_needed(&self) -> Result { let task = self .installation_path .lock() + .await .get_or_insert_with(|| { let http = self.http.clone(); self.background @@ -155,13 +194,11 @@ impl NodeRuntime { }) .clone(); - match task.await { - Ok(path) => Ok(path), - Err(error) => Err(anyhow!("{}", error)), - } + task.await.map_err(|e| anyhow!("{}", e)) } async fn install(http: Arc) -> Result { + log::info!("installing Node runtime"); let arch = match consts::ARCH { "x86_64" => "x64", "aarch64" => "arm64", diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 8435de71e2..a3c6302e29 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1,14 +1,15 @@ use crate::{ - DocumentHighlight, Hover, HoverBlock, HoverBlockKind, Location, LocationLink, Project, - ProjectTransaction, + DocumentHighlight, Hover, HoverBlock, HoverBlockKind, InlayHint, InlayHintLabel, + InlayHintLabelPart, InlayHintLabelPartTooltip, InlayHintTooltip, Location, LocationLink, + MarkupContent, Project, ProjectTransaction, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use client::proto::{self, PeerId}; use fs::LineEnding; use gpui::{AppContext, AsyncAppContext, ModelHandle}; use language::{ - language_settings::language_settings, + language_settings::{language_settings, InlayHintKind}, point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CharKind, CodeAction, @@ -126,6 +127,10 @@ pub(crate) struct OnTypeFormatting { pub push_to_history: bool, } +pub(crate) struct InlayHints { + pub range: Range, +} + pub(crate) struct FormattingOptions { tab_size: u32, } @@ -1780,3 +1785,327 @@ impl LspCommand for OnTypeFormatting { message.buffer_id } } + +#[async_trait(?Send)] +impl LspCommand for InlayHints { + type Response = Vec; + type LspRequest = lsp::InlayHintRequest; + type ProtoRequest = proto::InlayHints; + + fn check_capabilities(&self, server_capabilities: &lsp::ServerCapabilities) -> bool { + let Some(inlay_hint_provider) = &server_capabilities.inlay_hint_provider else { return false }; + match inlay_hint_provider { + lsp::OneOf::Left(enabled) => *enabled, + lsp::OneOf::Right(inlay_hint_capabilities) => match inlay_hint_capabilities { + lsp::InlayHintServerCapabilities::Options(_) => true, + lsp::InlayHintServerCapabilities::RegistrationOptions(_) => false, + }, + } + } + + fn to_lsp( + &self, + path: &Path, + buffer: &Buffer, + _: &Arc, + _: &AppContext, + ) -> lsp::InlayHintParams { + lsp::InlayHintParams { + text_document: lsp::TextDocumentIdentifier { + uri: lsp::Url::from_file_path(path).unwrap(), + }, + range: range_to_lsp(self.range.to_point_utf16(buffer)), + work_done_progress_params: Default::default(), + } + } + + async fn response_from_lsp( + self, + message: Option>, + _: ModelHandle, + buffer: ModelHandle, + _: LanguageServerId, + cx: AsyncAppContext, + ) -> Result> { + cx.read(|cx| { + let origin_buffer = buffer.read(cx); + Ok(message + .unwrap_or_default() + .into_iter() + .map(|lsp_hint| { + let kind = lsp_hint.kind.and_then(|kind| match kind { + lsp::InlayHintKind::TYPE => Some(InlayHintKind::Type), + lsp::InlayHintKind::PARAMETER => Some(InlayHintKind::Parameter), + _ => None, + }); + let position = origin_buffer + .clip_point_utf16(point_from_lsp(lsp_hint.position), Bias::Left); + InlayHint { + buffer_id: origin_buffer.remote_id(), + position: if kind == Some(InlayHintKind::Parameter) { + origin_buffer.anchor_before(position) + } else { + origin_buffer.anchor_after(position) + }, + padding_left: lsp_hint.padding_left.unwrap_or(false), + padding_right: lsp_hint.padding_right.unwrap_or(false), + label: match lsp_hint.label { + lsp::InlayHintLabel::String(s) => InlayHintLabel::String(s), + lsp::InlayHintLabel::LabelParts(lsp_parts) => { + InlayHintLabel::LabelParts( + lsp_parts + .into_iter() + .map(|label_part| InlayHintLabelPart { + value: label_part.value, + tooltip: label_part.tooltip.map( + |tooltip| { + match tooltip { + lsp::InlayHintLabelPartTooltip::String(s) => { + InlayHintLabelPartTooltip::String(s) + } + lsp::InlayHintLabelPartTooltip::MarkupContent( + markup_content, + ) => InlayHintLabelPartTooltip::MarkupContent( + MarkupContent { + kind: format!("{:?}", markup_content.kind), + value: markup_content.value, + }, + ), + } + }, + ), + location: label_part.location.map(|lsp_location| { + let target_start = origin_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.start), + Bias::Left, + ); + let target_end = origin_buffer.clip_point_utf16( + point_from_lsp(lsp_location.range.end), + Bias::Left, + ); + Location { + buffer: buffer.clone(), + range: origin_buffer.anchor_after(target_start) + ..origin_buffer.anchor_before(target_end), + } + }), + }) + .collect(), + ) + } + }, + kind, + tooltip: lsp_hint.tooltip.map(|tooltip| match tooltip { + lsp::InlayHintTooltip::String(s) => InlayHintTooltip::String(s), + lsp::InlayHintTooltip::MarkupContent(markup_content) => { + InlayHintTooltip::MarkupContent(MarkupContent { + kind: format!("{:?}", markup_content.kind), + value: markup_content.value, + }) + } + }), + } + }) + .collect()) + }) + } + + fn to_proto(&self, project_id: u64, buffer: &Buffer) -> proto::InlayHints { + proto::InlayHints { + project_id, + buffer_id: buffer.remote_id(), + start: Some(language::proto::serialize_anchor(&self.range.start)), + end: Some(language::proto::serialize_anchor(&self.range.end)), + version: serialize_version(&buffer.version()), + } + } + + async fn from_proto( + message: proto::InlayHints, + _: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result { + let start = message + .start + .and_then(language::proto::deserialize_anchor) + .context("invalid start")?; + let end = message + .end + .and_then(language::proto::deserialize_anchor) + .context("invalid end")?; + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(deserialize_version(&message.version)) + }) + .await?; + + Ok(Self { range: start..end }) + } + + fn response_to_proto( + response: Vec, + _: &mut Project, + _: PeerId, + buffer_version: &clock::Global, + cx: &mut AppContext, + ) -> proto::InlayHintsResponse { + proto::InlayHintsResponse { + hints: response + .into_iter() + .map(|response_hint| proto::InlayHint { + position: Some(language::proto::serialize_anchor(&response_hint.position)), + padding_left: response_hint.padding_left, + padding_right: response_hint.padding_right, + label: Some(proto::InlayHintLabel { + label: Some(match response_hint.label { + InlayHintLabel::String(s) => proto::inlay_hint_label::Label::Value(s), + InlayHintLabel::LabelParts(label_parts) => { + proto::inlay_hint_label::Label::LabelParts(proto::InlayHintLabelParts { + parts: label_parts.into_iter().map(|label_part| proto::InlayHintLabelPart { + value: label_part.value, + tooltip: label_part.tooltip.map(|tooltip| { + let proto_tooltip = match tooltip { + InlayHintLabelPartTooltip::String(s) => proto::inlay_hint_label_part_tooltip::Content::Value(s), + InlayHintLabelPartTooltip::MarkupContent(markup_content) => proto::inlay_hint_label_part_tooltip::Content::MarkupContent(proto::MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }), + }; + proto::InlayHintLabelPartTooltip {content: Some(proto_tooltip)} + }), + location: label_part.location.map(|location| proto::Location { + start: Some(serialize_anchor(&location.range.start)), + end: Some(serialize_anchor(&location.range.end)), + buffer_id: location.buffer.read(cx).remote_id(), + }), + }).collect() + }) + } + }), + }), + kind: response_hint.kind.map(|kind| kind.name().to_string()), + tooltip: response_hint.tooltip.map(|response_tooltip| { + let proto_tooltip = match response_tooltip { + InlayHintTooltip::String(s) => { + proto::inlay_hint_tooltip::Content::Value(s) + } + InlayHintTooltip::MarkupContent(markup_content) => { + proto::inlay_hint_tooltip::Content::MarkupContent( + proto::MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }, + ) + } + }; + proto::InlayHintTooltip { + content: Some(proto_tooltip), + } + }), + }) + .collect(), + version: serialize_version(buffer_version), + } + } + + async fn response_from_proto( + self, + message: proto::InlayHintsResponse, + project: ModelHandle, + buffer: ModelHandle, + mut cx: AsyncAppContext, + ) -> Result> { + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(deserialize_version(&message.version)) + }) + .await?; + + let mut hints = Vec::new(); + for message_hint in message.hints { + let buffer_id = message_hint + .position + .as_ref() + .and_then(|location| location.buffer_id) + .context("missing buffer id")?; + let hint = InlayHint { + buffer_id, + position: message_hint + .position + .and_then(language::proto::deserialize_anchor) + .context("invalid position")?, + label: match message_hint + .label + .and_then(|label| label.label) + .context("missing label")? + { + proto::inlay_hint_label::Label::Value(s) => InlayHintLabel::String(s), + proto::inlay_hint_label::Label::LabelParts(parts) => { + let mut label_parts = Vec::new(); + for part in parts.parts { + label_parts.push(InlayHintLabelPart { + value: part.value, + tooltip: part.tooltip.map(|tooltip| match tooltip.content { + Some(proto::inlay_hint_label_part_tooltip::Content::Value(s)) => InlayHintLabelPartTooltip::String(s), + Some(proto::inlay_hint_label_part_tooltip::Content::MarkupContent(markup_content)) => InlayHintLabelPartTooltip::MarkupContent(MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }), + None => InlayHintLabelPartTooltip::String(String::new()), + }), + location: match part.location { + Some(location) => { + let target_buffer = project + .update(&mut cx, |this, cx| { + this.wait_for_remote_buffer(location.buffer_id, cx) + }) + .await?; + Some(Location { + range: location + .start + .and_then(language::proto::deserialize_anchor) + .context("invalid start")? + ..location + .end + .and_then(language::proto::deserialize_anchor) + .context("invalid end")?, + buffer: target_buffer, + })}, + None => None, + }, + }); + } + + InlayHintLabel::LabelParts(label_parts) + } + }, + padding_left: message_hint.padding_left, + padding_right: message_hint.padding_right, + kind: message_hint + .kind + .as_deref() + .and_then(InlayHintKind::from_name), + tooltip: message_hint.tooltip.and_then(|tooltip| { + Some(match tooltip.content? { + proto::inlay_hint_tooltip::Content::Value(s) => InlayHintTooltip::String(s), + proto::inlay_hint_tooltip::Content::MarkupContent(markup_content) => { + InlayHintTooltip::MarkupContent(MarkupContent { + kind: markup_content.kind, + value: markup_content.value, + }) + } + }) + }), + }; + + hints.push(hint); + } + + Ok(hints) + } + + fn buffer_id_from_proto(message: &proto::InlayHints) -> u64 { + message.buffer_id + } +} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 91c92edb6a..bbb2064da2 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -29,14 +29,15 @@ use gpui::{ AnyModelHandle, AppContext, AsyncAppContext, BorrowAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle, }; +use itertools::Itertools; use language::{ - language_settings::{language_settings, FormatOnSave, Formatter}, + language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, serialize_anchor, serialize_version, }, - range_from_lsp, range_to_lsp, Anchor, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel, + range_from_lsp, range_to_lsp, Bias, Buffer, CachedLspAdapter, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticEntry, DiagnosticSet, Diff, Event as BufferEvent, File as _, Language, LanguageRegistry, LanguageServerName, LocalFile, LspAdapterDelegate, OffsetRangeExt, Operation, Patch, PendingLanguageServer, PointUtf16, TextBufferSnapshot, ToOffset, @@ -45,7 +46,7 @@ use language::{ use log::error; use lsp::{ DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, - DocumentHighlightKind, LanguageServer, LanguageServerId, + DocumentHighlightKind, LanguageServer, LanguageServerBinary, LanguageServerId, OneOf, }; use lsp_command::*; use postage::watch; @@ -65,6 +66,7 @@ use std::{ num::NonZeroU32, ops::Range, path::{self, Component, Path, PathBuf}, + process::Stdio, rc::Rc, str, sync::{ @@ -74,6 +76,7 @@ use std::{ time::{Duration, Instant}, }; use terminals::Terminals; +use text::Anchor; use util::{ debug_panic, defer, http::HttpClient, merge_json_value_into, paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, @@ -223,6 +226,7 @@ enum OpenBuffer { Operations(Vec), } +#[derive(Clone)] enum WorktreeHandle { Strong(ModelHandle), Weak(WeakModelHandle), @@ -275,10 +279,12 @@ pub enum Event { new_peer_id: proto::PeerId, }, CollaboratorLeft(proto::PeerId), + RefreshInlays, } pub enum LanguageServerState { Starting(Task>>), + Running { language: Arc, adapter: Arc, @@ -316,12 +322,63 @@ pub struct DiagnosticSummary { pub warning_count: usize, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Location { pub buffer: ModelHandle, pub range: Range, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct InlayHint { + pub buffer_id: u64, + pub position: language::Anchor, + pub label: InlayHintLabel, + pub kind: Option, + pub padding_left: bool, + pub padding_right: bool, + pub tooltip: Option, +} + +impl InlayHint { + pub fn text(&self) -> String { + match &self.label { + InlayHintLabel::String(s) => s.to_owned(), + InlayHintLabel::LabelParts(parts) => parts.iter().map(|part| &part.value).join(""), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum InlayHintLabel { + String(String), + LabelParts(Vec), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct InlayHintLabelPart { + pub value: String, + pub tooltip: Option, + pub location: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum InlayHintTooltip { + String(String), + MarkupContent(MarkupContent), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum InlayHintLabelPartTooltip { + String(String), + MarkupContent(MarkupContent), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MarkupContent { + pub kind: String, + pub value: String, +} + #[derive(Debug, Clone)] pub struct LocationLink { pub origin: Option, @@ -482,6 +539,8 @@ impl Project { client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion); client.add_model_request_handler(Self::handle_apply_code_action); client.add_model_request_handler(Self::handle_on_type_formatting); + client.add_model_request_handler(Self::handle_inlay_hints); + client.add_model_request_handler(Self::handle_refresh_inlay_hints); client.add_model_request_handler(Self::handle_reload_buffers); client.add_model_request_handler(Self::handle_synchronize_buffers); client.add_model_request_handler(Self::handle_format_buffers); @@ -2424,348 +2483,524 @@ impl Project { language: Arc, cx: &mut ModelContext, ) { - if !language_settings( - Some(&language), - worktree - .update(cx, |tree, cx| tree.root_file(cx)) - .map(|f| f as _) - .as_ref(), - cx, - ) - .enable_language_server - { + let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx)); + let settings = language_settings(Some(&language), root_file.map(|f| f as _).as_ref(), cx); + if !settings.enable_language_server { return; } let worktree_id = worktree.read(cx).id(); for adapter in language.lsp_adapters() { - let key = (worktree_id, adapter.name.clone()); - if self.language_server_ids.contains_key(&key) { - continue; - } - - let pending_server = match self.languages.start_language_server( - language.clone(), - adapter.clone(), + self.start_language_server( + worktree_id, worktree_path.clone(), - ProjectLspAdapterDelegate::new(self, cx), - cx, - ) { - Some(pending_server) => pending_server, - None => continue, - }; - - let lsp = settings::get::(cx) - .lsp - .get(&adapter.name.0); - let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); - - let mut initialization_options = adapter.initialization_options.clone(); - match (&mut initialization_options, override_options) { - (Some(initialization_options), Some(override_options)) => { - merge_json_value_into(override_options, initialization_options); - } - (None, override_options) => initialization_options = override_options, - _ => {} - } - - let server_id = pending_server.server_id; - let state = self.setup_pending_language_server( - initialization_options, - pending_server, adapter.clone(), language.clone(), - key.clone(), cx, ); - self.language_servers.insert(server_id, state); - self.language_server_ids.insert(key.clone(), server_id); } } - fn setup_pending_language_server( + fn start_language_server( &mut self, + worktree_id: WorktreeId, + worktree_path: Arc, + adapter: Arc, + language: Arc, + cx: &mut ModelContext, + ) { + let key = (worktree_id, adapter.name.clone()); + if self.language_server_ids.contains_key(&key) { + return; + } + + let pending_server = match self.languages.create_pending_language_server( + language.clone(), + adapter.clone(), + worktree_path, + ProjectLspAdapterDelegate::new(self, cx), + cx, + ) { + Some(pending_server) => pending_server, + None => return, + }; + + let project_settings = settings::get::(cx); + let lsp = project_settings.lsp.get(&adapter.name.0); + let override_options = lsp.map(|s| s.initialization_options.clone()).flatten(); + + let mut initialization_options = adapter.initialization_options.clone(); + match (&mut initialization_options, override_options) { + (Some(initialization_options), Some(override_options)) => { + merge_json_value_into(override_options, initialization_options); + } + (None, override_options) => initialization_options = override_options, + _ => {} + } + + let server_id = pending_server.server_id; + let container_dir = pending_server.container_dir.clone(); + let state = LanguageServerState::Starting({ + let adapter = adapter.clone(); + let server_name = adapter.name.0.clone(); + let languages = self.languages.clone(); + let language = language.clone(); + let key = key.clone(); + + cx.spawn_weak(|this, mut cx| async move { + let result = Self::setup_and_insert_language_server( + this, + initialization_options, + pending_server, + adapter.clone(), + languages, + language.clone(), + server_id, + key, + &mut cx, + ) + .await; + + match result { + Ok(server) => server, + + Err(err) => { + log::error!("failed to start language server {:?}: {}", server_name, err); + + if let Some(this) = this.upgrade(&cx) { + if let Some(container_dir) = container_dir { + let installation_test_binary = adapter + .installation_test_binary(container_dir.to_path_buf()) + .await; + + this.update(&mut cx, |_, cx| { + Self::check_errored_server( + language, + adapter, + server_id, + installation_test_binary, + cx, + ) + }); + } + } + + None + } + } + }) + }); + + self.language_servers.insert(server_id, state); + self.language_server_ids.insert(key, server_id); + } + + fn reinstall_language_server( + &mut self, + language: Arc, + adapter: Arc, + server_id: LanguageServerId, + cx: &mut ModelContext, + ) -> Option> { + log::info!("beginning to reinstall server"); + + let existing_server = match self.language_servers.remove(&server_id) { + Some(LanguageServerState::Running { server, .. }) => Some(server), + _ => None, + }; + + for worktree in &self.worktrees { + if let Some(worktree) = worktree.upgrade(cx) { + let key = (worktree.read(cx).id(), adapter.name.clone()); + self.language_server_ids.remove(&key); + } + } + + Some(cx.spawn(move |this, mut cx| async move { + if let Some(task) = existing_server.and_then(|server| server.shutdown()) { + log::info!("shutting down existing server"); + task.await; + } + + // TODO: This is race-safe with regards to preventing new instances from + // starting while deleting, but existing instances in other projects are going + // to be very confused and messed up + this.update(&mut cx, |this, cx| { + this.languages.delete_server_container(adapter.clone(), cx) + }) + .await; + + this.update(&mut cx, |this, mut cx| { + let worktrees = this.worktrees.clone(); + for worktree in worktrees { + let worktree = match worktree.upgrade(cx) { + Some(worktree) => worktree.read(cx), + None => continue, + }; + let worktree_id = worktree.id(); + let root_path = worktree.abs_path(); + + this.start_language_server( + worktree_id, + root_path, + adapter.clone(), + language.clone(), + &mut cx, + ); + } + }) + })) + } + + async fn setup_and_insert_language_server( + this: WeakModelHandle, initialization_options: Option, pending_server: PendingLanguageServer, adapter: Arc, + languages: Arc, language: Arc, + server_id: LanguageServerId, key: (WorktreeId, LanguageServerName), - cx: &mut ModelContext, - ) -> LanguageServerState { - let server_id = pending_server.server_id; - let languages = self.languages.clone(); + cx: &mut AsyncAppContext, + ) -> Result>> { + let setup = Self::setup_pending_language_server( + this, + initialization_options, + pending_server, + adapter.clone(), + languages, + server_id, + cx, + ); - LanguageServerState::Starting(cx.spawn_weak(|this, mut cx| async move { - let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; - let language_server = pending_server.task.await.log_err()?; + let language_server = match setup.await? { + Some(language_server) => language_server, + None => return Ok(None), + }; - language_server - .on_notification::({ - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |_, cx| { - cx.emit(Event::LanguageServerLog(server_id, params.message)) - }); - } - } - }) - .detach(); + let this = match this.upgrade(cx) { + Some(this) => this, + None => return Err(anyhow!("failed to upgrade project handle")), + }; - language_server - .on_notification::({ - let adapter = adapter.clone(); - move |mut params, cx| { - let adapter = adapter.clone(); - cx.spawn(|mut cx| async move { - adapter.process_diagnostics(&mut params).await; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.update_diagnostics( - server_id, - params, - &adapter.disk_based_diagnostic_sources, - cx, - ) - .log_err(); - }); - } - }) - .detach(); - } - }) - .detach(); + this.update(cx, |this, cx| { + this.insert_newly_running_language_server( + language, + adapter, + language_server.clone(), + server_id, + key, + cx, + ) + })?; - language_server - .on_request::({ - let languages = languages.clone(); - move |params, mut cx| { - let languages = languages.clone(); - async move { - let workspace_config = - cx.update(|cx| languages.workspace_configuration(cx)).await; - Ok(params - .items - .into_iter() - .map(|item| { - if let Some(section) = &item.section { - workspace_config - .get(section) - .cloned() - .unwrap_or(serde_json::Value::Null) - } else { - workspace_config.clone() - } - }) - .collect()) - } - } - }) - .detach(); + Ok(Some(language_server)) + } - // Even though we don't have handling for these requests, respond to them to - // avoid stalling any language server like `gopls` which waits for a response - // to these requests when initializing. - language_server - .on_request::( - move |params, mut cx| async move { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, _| { - if let Some(status) = - this.language_server_statuses.get_mut(&server_id) - { - if let lsp::NumberOrString::String(token) = params.token { - status.progress_tokens.insert(token); - } - } - }); - } - Ok(()) - }, - ) - .detach(); - language_server - .on_request::( - move |params, mut cx| async move { - let this = this - .upgrade(&cx) - .ok_or_else(|| anyhow!("project dropped"))?; - for reg in params.registrations { - if reg.method == "workspace/didChangeWatchedFiles" { - if let Some(options) = reg.register_options { - let options = serde_json::from_value(options)?; - this.update(&mut cx, |this, cx| { - this.on_lsp_did_change_watched_files( - server_id, options, cx, - ); - }); - } - } - } - Ok(()) - }, - ) - .detach(); + async fn setup_pending_language_server( + this: WeakModelHandle, + initialization_options: Option, + pending_server: PendingLanguageServer, + adapter: Arc, + languages: Arc, + server_id: LanguageServerId, + cx: &mut AsyncAppContext, + ) -> Result>> { + let workspace_config = cx.update(|cx| languages.workspace_configuration(cx)).await; + let language_server = match pending_server.task.await? { + Some(server) => server.initialize(initialization_options).await?, + None => { + return Ok(None); + } + }; - language_server - .on_request::({ - let adapter = adapter.clone(); - move |params, cx| { - Self::on_lsp_workspace_edit(this, params, server_id, adapter.clone(), cx) - } - }) - .detach(); - - let disk_based_diagnostics_progress_token = - adapter.disk_based_diagnostics_progress_token.clone(); - - language_server - .on_notification::({ - move |params, mut cx| { - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.on_lsp_progress( - params, - server_id, - disk_based_diagnostics_progress_token.clone(), - cx, - ); - }); - } - } - }) - .detach(); - - let language_server = language_server - .initialize(initialization_options) - .await - .log_err()?; - language_server - .notify::( - lsp::DidChangeConfigurationParams { - settings: workspace_config, - }, - ) - .ok(); - - let this = this.upgrade(&cx)?; - this.update(&mut cx, |this, cx| { - // If the language server for this key doesn't match the server id, don't store the - // server. Which will cause it to be dropped, killing the process - if this - .language_server_ids - .get(&key) - .map(|id| id != &server_id) - .unwrap_or(false) - { - return None; - } - - // Update language_servers collection with Running variant of LanguageServerState - // indicating that the server is up and running and ready - this.language_servers.insert( - server_id, - LanguageServerState::Running { - adapter: adapter.clone(), - language: language.clone(), - watched_paths: Default::default(), - server: language_server.clone(), - simulate_disk_based_diagnostics_completion: None, - }, - ); - this.language_server_statuses.insert( - server_id, - LanguageServerStatus { - name: language_server.name().to_string(), - pending_work: Default::default(), - has_pending_diagnostic_updates: false, - progress_tokens: Default::default(), - }, - ); - - cx.emit(Event::LanguageServerAdded(server_id)); - - if let Some(project_id) = this.remote_id() { - this.client - .send(proto::StartLanguageServer { - project_id, - server: Some(proto::LanguageServer { - id: server_id.0 as u64, - name: language_server.name().to_string(), - }), - }) - .log_err(); - } - - // Tell the language server about every open buffer in the worktree that matches the language. - for buffer in this.opened_buffers.values() { - if let Some(buffer_handle) = buffer.upgrade(cx) { - let buffer = buffer_handle.read(cx); - let file = match File::from_dyn(buffer.file()) { - Some(file) => file, - None => continue, - }; - let language = match buffer.language() { - Some(language) => language, - None => continue, - }; - - if file.worktree.read(cx).id() != key.0 - || !language.lsp_adapters().iter().any(|a| a.name == key.1) - { - continue; - } - - let file = file.as_local()?; - let versions = this - .buffer_snapshots - .entry(buffer.remote_id()) - .or_default() - .entry(server_id) - .or_insert_with(|| { - vec![LspBufferSnapshot { - version: 0, - snapshot: buffer.text_snapshot(), - }] - }); - - let snapshot = versions.last().unwrap(); - let version = snapshot.version; - let initial_snapshot = &snapshot.snapshot; - let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); - language_server - .notify::( - lsp::DidOpenTextDocumentParams { - text_document: lsp::TextDocumentItem::new( - uri, - adapter - .language_ids - .get(language.name().as_ref()) - .cloned() - .unwrap_or_default(), - version, - initial_snapshot.text(), - ), - }, - ) - .log_err()?; - buffer_handle.update(cx, |buffer, cx| { - buffer.set_completion_triggers( - language_server - .capabilities() - .completion_provider - .as_ref() - .and_then(|provider| provider.trigger_characters.clone()) - .unwrap_or_default(), - cx, - ) + language_server + .on_notification::({ + move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |_, cx| { + cx.emit(Event::LanguageServerLog(server_id, params.message)) }); } } - - cx.notify(); - Some(language_server) }) - })) + .detach(); + + language_server + .on_notification::({ + let adapter = adapter.clone(); + move |mut params, cx| { + let this = this; + let adapter = adapter.clone(); + cx.spawn(|mut cx| async move { + adapter.process_diagnostics(&mut params).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.update_diagnostics( + server_id, + params, + &adapter.disk_based_diagnostic_sources, + cx, + ) + .log_err(); + }); + } + }) + .detach(); + } + }) + .detach(); + + language_server + .on_request::({ + let languages = languages.clone(); + move |params, mut cx| { + let languages = languages.clone(); + async move { + let workspace_config = + cx.update(|cx| languages.workspace_configuration(cx)).await; + Ok(params + .items + .into_iter() + .map(|item| { + if let Some(section) = &item.section { + workspace_config + .get(section) + .cloned() + .unwrap_or(serde_json::Value::Null) + } else { + workspace_config.clone() + } + }) + .collect()) + } + } + }) + .detach(); + + // Even though we don't have handling for these requests, respond to them to + // avoid stalling any language server like `gopls` which waits for a response + // to these requests when initializing. + language_server + .on_request::( + move |params, mut cx| async move { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, _| { + if let Some(status) = this.language_server_statuses.get_mut(&server_id) + { + if let lsp::NumberOrString::String(token) = params.token { + status.progress_tokens.insert(token); + } + } + }); + } + Ok(()) + }, + ) + .detach(); + language_server + .on_request::({ + move |params, mut cx| async move { + let this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + for reg in params.registrations { + if reg.method == "workspace/didChangeWatchedFiles" { + if let Some(options) = reg.register_options { + let options = serde_json::from_value(options)?; + this.update(&mut cx, |this, cx| { + this.on_lsp_did_change_watched_files(server_id, options, cx); + }); + } + } + } + Ok(()) + } + }) + .detach(); + + language_server + .on_request::({ + let adapter = adapter.clone(); + move |params, cx| { + Self::on_lsp_workspace_edit(this, params, server_id, adapter.clone(), cx) + } + }) + .detach(); + + language_server + .on_request::({ + move |(), mut cx| async move { + let this = this + .upgrade(&cx) + .ok_or_else(|| anyhow!("project dropped"))?; + this.update(&mut cx, |project, cx| { + cx.emit(Event::RefreshInlays); + project.remote_id().map(|project_id| { + project.client.send(proto::RefreshInlayHints { project_id }) + }) + }) + .transpose()?; + Ok(()) + } + }) + .detach(); + + let disk_based_diagnostics_progress_token = + adapter.disk_based_diagnostics_progress_token.clone(); + + language_server + .on_notification::(move |params, mut cx| { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.on_lsp_progress( + params, + server_id, + disk_based_diagnostics_progress_token.clone(), + cx, + ); + }); + } + }) + .detach(); + + language_server + .notify::( + lsp::DidChangeConfigurationParams { + settings: workspace_config, + }, + ) + .ok(); + + Ok(Some(language_server)) + } + + fn insert_newly_running_language_server( + &mut self, + language: Arc, + adapter: Arc, + language_server: Arc, + server_id: LanguageServerId, + key: (WorktreeId, LanguageServerName), + cx: &mut ModelContext, + ) -> Result<()> { + // If the language server for this key doesn't match the server id, don't store the + // server. Which will cause it to be dropped, killing the process + if self + .language_server_ids + .get(&key) + .map(|id| id != &server_id) + .unwrap_or(false) + { + return Ok(()); + } + + // Update language_servers collection with Running variant of LanguageServerState + // indicating that the server is up and running and ready + self.language_servers.insert( + server_id, + LanguageServerState::Running { + adapter: adapter.clone(), + language: language.clone(), + watched_paths: Default::default(), + server: language_server.clone(), + simulate_disk_based_diagnostics_completion: None, + }, + ); + + self.language_server_statuses.insert( + server_id, + LanguageServerStatus { + name: language_server.name().to_string(), + pending_work: Default::default(), + has_pending_diagnostic_updates: false, + progress_tokens: Default::default(), + }, + ); + + cx.emit(Event::LanguageServerAdded(server_id)); + + if let Some(project_id) = self.remote_id() { + self.client.send(proto::StartLanguageServer { + project_id, + server: Some(proto::LanguageServer { + id: server_id.0 as u64, + name: language_server.name().to_string(), + }), + })?; + } + + // Tell the language server about every open buffer in the worktree that matches the language. + for buffer in self.opened_buffers.values() { + if let Some(buffer_handle) = buffer.upgrade(cx) { + let buffer = buffer_handle.read(cx); + let file = match File::from_dyn(buffer.file()) { + Some(file) => file, + None => continue, + }; + let language = match buffer.language() { + Some(language) => language, + None => continue, + }; + + if file.worktree.read(cx).id() != key.0 + || !language.lsp_adapters().iter().any(|a| a.name == key.1) + { + continue; + } + + let file = match file.as_local() { + Some(file) => file, + None => continue, + }; + + let versions = self + .buffer_snapshots + .entry(buffer.remote_id()) + .or_default() + .entry(server_id) + .or_insert_with(|| { + vec![LspBufferSnapshot { + version: 0, + snapshot: buffer.text_snapshot(), + }] + }); + + let snapshot = versions.last().unwrap(); + let version = snapshot.version; + let initial_snapshot = &snapshot.snapshot; + let uri = lsp::Url::from_file_path(file.abs_path(cx)).unwrap(); + language_server.notify::( + lsp::DidOpenTextDocumentParams { + text_document: lsp::TextDocumentItem::new( + uri, + adapter + .language_ids + .get(language.name().as_ref()) + .cloned() + .unwrap_or_default(), + version, + initial_snapshot.text(), + ), + }, + )?; + + buffer_handle.update(cx, |buffer, cx| { + buffer.set_completion_triggers( + language_server + .capabilities() + .completion_provider + .as_ref() + .and_then(|provider| provider.trigger_characters.clone()) + .unwrap_or_default(), + cx, + ) + }); + } + } + + cx.notify(); + Ok(()) } // Returns a list of all of the worktrees which no longer have a language server and the root path @@ -2814,9 +3049,7 @@ impl Project { let mut root_path = None; let server = match server_state { - Some(LanguageServerState::Starting(started_language_server)) => { - started_language_server.await - } + Some(LanguageServerState::Starting(task)) => task.await, Some(LanguageServerState::Running { server, .. }) => Some(server), None => None, }; @@ -2927,6 +3160,72 @@ impl Project { .detach(); } + fn check_errored_server( + language: Arc, + adapter: Arc, + server_id: LanguageServerId, + installation_test_binary: Option, + cx: &mut ModelContext, + ) { + if !adapter.can_be_reinstalled() { + log::info!( + "Validation check requested for {:?} but it cannot be reinstalled", + adapter.name.0 + ); + return; + } + + cx.spawn(|this, mut cx| async move { + log::info!("About to spawn test binary"); + + // A lack of test binary counts as a failure + let process = installation_test_binary.and_then(|binary| { + smol::process::Command::new(&binary.path) + .current_dir(&binary.path) + .args(binary.arguments) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .kill_on_drop(true) + .spawn() + .ok() + }); + + const PROCESS_TIMEOUT: Duration = Duration::from_secs(5); + let mut timeout = cx.background().timer(PROCESS_TIMEOUT).fuse(); + + let mut errored = false; + if let Some(mut process) = process { + futures::select! { + status = process.status().fuse() => match status { + Ok(status) => errored = !status.success(), + Err(_) => errored = true, + }, + + _ = timeout => { + log::info!("test binary time-ed out, this counts as a success"); + _ = process.kill(); + } + } + } else { + log::warn!("test binary failed to launch"); + errored = true; + } + + if errored { + log::warn!("test binary check failed"); + let task = this.update(&mut cx, move |this, mut cx| { + this.reinstall_language_server(language, adapter, server_id, &mut cx) + }); + + if let Some(task) = task { + task.await; + } + } + }) + .detach(); + } + fn on_lsp_progress( &mut self, progress: lsp::ProgressParams, @@ -3719,14 +4018,15 @@ impl Project { tab_size: NonZeroU32, cx: &mut AsyncAppContext, ) -> Result, String)>> { - let text_document = - lsp::TextDocumentIdentifier::new(lsp::Url::from_file_path(abs_path).unwrap()); + let uri = lsp::Url::from_file_path(abs_path) + .map_err(|_| anyhow!("failed to convert abs path to uri"))?; + let text_document = lsp::TextDocumentIdentifier::new(uri); let capabilities = &language_server.capabilities(); - let lsp_edits = if capabilities - .document_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { + + let formatting_provider = capabilities.document_formatting_provider.as_ref(); + let range_formatting_provider = capabilities.document_range_formatting_provider.as_ref(); + + let lsp_edits = if matches!(formatting_provider, Some(p) if *p != OneOf::Left(false)) { language_server .request::(lsp::DocumentFormattingParams { text_document, @@ -3734,14 +4034,10 @@ impl Project { work_done_progress_params: Default::default(), }) .await? - } else if capabilities - .document_range_formatting_provider - .as_ref() - .map_or(false, |provider| *provider != lsp::OneOf::Left(false)) - { + } else if matches!(range_formatting_provider, Some(p) if *p != OneOf::Left(false)) { let buffer_start = lsp::Position::new(0, 0); - let buffer_end = - buffer.read_with(cx, |buffer, _| point_to_lsp(buffer.max_point_utf16())); + let buffer_end = buffer.read_with(cx, |b, _| point_to_lsp(b.max_point_utf16())); + language_server .request::(lsp::DocumentRangeFormattingParams { text_document, @@ -3760,7 +4056,7 @@ impl Project { }) .await } else { - Ok(Default::default()) + Ok(Vec::new()) } } @@ -3868,70 +4164,72 @@ impl Project { let mut requests = Vec::new(); for ((worktree_id, _), server_id) in self.language_server_ids.iter() { let worktree_id = *worktree_id; - if let Some(worktree) = self - .worktree_for_id(worktree_id, cx) - .and_then(|worktree| worktree.read(cx).as_local()) - { - if let Some(LanguageServerState::Running { + let worktree_handle = self.worktree_for_id(worktree_id, cx); + let worktree = match worktree_handle.and_then(|tree| tree.read(cx).as_local()) { + Some(worktree) => worktree, + None => continue, + }; + let worktree_abs_path = worktree.abs_path().clone(); + + let (adapter, language, server) = match self.language_servers.get(server_id) { + Some(LanguageServerState::Running { adapter, language, server, .. - }) = self.language_servers.get(server_id) - { - let adapter = adapter.clone(); - let language = language.clone(); - let worktree_abs_path = worktree.abs_path().clone(); - requests.push( - server - .request::( - lsp::WorkspaceSymbolParams { - query: query.to_string(), - ..Default::default() - }, - ) - .log_err() - .map(move |response| { - let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { - lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { - flat_responses.into_iter().map(|lsp_symbol| { - (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) - }).collect::>() - } - lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { - nested_responses.into_iter().filter_map(|lsp_symbol| { - let location = match lsp_symbol.location { - lsp::OneOf::Left(location) => location, - lsp::OneOf::Right(_) => { - error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); - return None - } - }; - Some((lsp_symbol.name, lsp_symbol.kind, location)) - }).collect::>() - } - }).unwrap_or_default(); + }) => (adapter.clone(), language.clone(), server), - ( - adapter, - language, - worktree_id, - worktree_abs_path, - lsp_symbols, - ) - }), - ); - } - } + _ => continue, + }; + + requests.push( + server + .request::( + lsp::WorkspaceSymbolParams { + query: query.to_string(), + ..Default::default() + }, + ) + .log_err() + .map(move |response| { + let lsp_symbols = response.flatten().map(|symbol_response| match symbol_response { + lsp::WorkspaceSymbolResponse::Flat(flat_responses) => { + flat_responses.into_iter().map(|lsp_symbol| { + (lsp_symbol.name, lsp_symbol.kind, lsp_symbol.location) + }).collect::>() + } + lsp::WorkspaceSymbolResponse::Nested(nested_responses) => { + nested_responses.into_iter().filter_map(|lsp_symbol| { + let location = match lsp_symbol.location { + OneOf::Left(location) => location, + OneOf::Right(_) => { + error!("Unexpected: client capabilities forbid symbol resolutions in workspace.symbol.resolveSupport"); + return None + } + }; + Some((lsp_symbol.name, lsp_symbol.kind, location)) + }).collect::>() + } + }).unwrap_or_default(); + + ( + adapter, + language, + worktree_id, + worktree_abs_path, + lsp_symbols, + ) + }), + ); } cx.spawn_weak(|this, cx| async move { let responses = futures::future::join_all(requests).await; - let this = if let Some(this) = this.upgrade(&cx) { - this - } else { - return Ok(Default::default()); + let this = match this.upgrade(&cx) { + Some(this) => this, + None => return Ok(Vec::new()), }; + let symbols = this.read_with(&cx, |this, cx| { let mut symbols = Vec::new(); for ( @@ -3988,8 +4286,10 @@ impl Project { }, )); } + symbols }); + Ok(futures::future::join_all(symbols).await) }) } else if let Some(project_id) = self.remote_id() { @@ -4270,13 +4570,20 @@ impl Project { this.last_workspace_edits_by_language_server .remove(&lang_server.server_id()); }); - lang_server + + let result = lang_server .request::(lsp::ExecuteCommandParams { command: command.command, arguments: command.arguments.unwrap_or_default(), ..Default::default() }) - .await?; + .await; + + if let Err(err) = result { + // TODO: LSP ERROR + return Err(err); + } + return Ok(this.update(&mut cx, |this, _| { this.last_workspace_edits_by_language_server .remove(&lang_server.server_id()) @@ -4436,7 +4743,7 @@ impl Project { uri, version: None, }, - edits: edits.into_iter().map(lsp::OneOf::Left).collect(), + edits: edits.into_iter().map(OneOf::Left).collect(), }) })); } @@ -4506,8 +4813,8 @@ impl Project { let edits = this .update(cx, |this, cx| { let edits = op.edits.into_iter().map(|edit| match edit { - lsp::OneOf::Left(edit) => edit, - lsp::OneOf::Right(edit) => edit.text_edit, + OneOf::Left(edit) => edit, + OneOf::Right(edit) => edit.text_edit, }); this.edits_from_lsp( &buffer_to_edit, @@ -4605,6 +4912,61 @@ impl Project { ) } + pub fn inlay_hints( + &self, + buffer_handle: ModelHandle, + range: Range, + cx: &mut ModelContext, + ) -> Task>> { + let buffer = buffer_handle.read(cx); + let range = buffer.anchor_before(range.start)..buffer.anchor_before(range.end); + let range_start = range.start; + let range_end = range.end; + let buffer_id = buffer.remote_id(); + let buffer_version = buffer.version().clone(); + let lsp_request = InlayHints { range }; + + if self.is_local() { + let lsp_request_task = self.request_lsp(buffer_handle.clone(), lsp_request, cx); + cx.spawn(|_, mut cx| async move { + buffer_handle + .update(&mut cx, |buffer, _| { + buffer.wait_for_edits(vec![range_start.timestamp, range_end.timestamp]) + }) + .await + .context("waiting for inlay hint request range edits")?; + lsp_request_task.await.context("inlay hints LSP request") + }) + } else if let Some(project_id) = self.remote_id() { + let client = self.client.clone(); + let request = proto::InlayHints { + project_id, + buffer_id, + start: Some(serialize_anchor(&range_start)), + end: Some(serialize_anchor(&range_end)), + version: serialize_version(&buffer_version), + }; + cx.spawn(|project, cx| async move { + let response = client + .request(request) + .await + .context("inlay hints proto request")?; + let hints_request_result = LspCommand::response_from_proto( + lsp_request, + response, + project, + buffer_handle.clone(), + cx, + ) + .await; + + hints_request_result.context("inlay hints proto response conversion") + }) + } else { + Task::ready(Err(anyhow!("project does not have a remote id"))) + } + } + #[allow(clippy::type_complexity)] pub fn search( &self, @@ -4844,10 +5206,20 @@ impl Project { return Ok(Default::default()); } - let response = language_server - .request::(lsp_params) - .await - .context("lsp request failed")?; + let result = language_server.request::(lsp_params).await; + let response = match result { + Ok(response) => response, + + Err(err) => { + log::warn!( + "Generic lsp request to {} failed: {}", + language_server.name(), + err + ); + return Err(err); + } + }; + request .response_from_lsp( response, @@ -5167,41 +5539,39 @@ impl Project { let abs_path = worktree_handle.read(cx).abs_path(); for server_id in &language_server_ids { - if let Some(server) = self.language_servers.get(server_id) { - if let LanguageServerState::Running { - server, - watched_paths, - .. - } = server - { - if let Some(watched_paths) = watched_paths.get(&worktree_id) { - let params = lsp::DidChangeWatchedFilesParams { - changes: changes - .iter() - .filter_map(|(path, _, change)| { - if !watched_paths.is_match(&path) { - return None; - } - let typ = match change { - PathChange::Loaded => return None, - PathChange::Added => lsp::FileChangeType::CREATED, - PathChange::Removed => lsp::FileChangeType::DELETED, - PathChange::Updated => lsp::FileChangeType::CHANGED, - PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, - }; - Some(lsp::FileEvent { - uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), - typ, - }) + if let Some(LanguageServerState::Running { + server, + watched_paths, + .. + }) = self.language_servers.get(server_id) + { + if let Some(watched_paths) = watched_paths.get(&worktree_id) { + let params = lsp::DidChangeWatchedFilesParams { + changes: changes + .iter() + .filter_map(|(path, _, change)| { + if !watched_paths.is_match(&path) { + return None; + } + let typ = match change { + PathChange::Loaded => return None, + PathChange::Added => lsp::FileChangeType::CREATED, + PathChange::Removed => lsp::FileChangeType::DELETED, + PathChange::Updated => lsp::FileChangeType::CHANGED, + PathChange::AddedOrUpdated => lsp::FileChangeType::CHANGED, + }; + Some(lsp::FileEvent { + uri: lsp::Url::from_file_path(abs_path.join(path)).unwrap(), + typ, }) - .collect(), - }; + }) + .collect(), + }; - if !params.changes.is_empty() { - server - .notify::(params) - .log_err(); - } + if !params.changes.is_empty() { + server + .notify::(params) + .log_err(); } } } @@ -6339,6 +6709,68 @@ impl Project { Ok(proto::OnTypeFormattingResponse { transaction }) } + async fn handle_inlay_hints( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + let sender_id = envelope.original_sender_id()?; + let buffer = this.update(&mut cx, |this, cx| { + this.opened_buffers + .get(&envelope.payload.buffer_id) + .and_then(|buffer| buffer.upgrade(cx)) + .ok_or_else(|| anyhow!("unknown buffer id {}", envelope.payload.buffer_id)) + })?; + let buffer_version = deserialize_version(&envelope.payload.version); + + buffer + .update(&mut cx, |buffer, _| { + buffer.wait_for_version(buffer_version.clone()) + }) + .await + .with_context(|| { + format!( + "waiting for version {:?} for buffer {}", + buffer_version, + buffer.id() + ) + })?; + + let start = envelope + .payload + .start + .and_then(deserialize_anchor) + .context("missing range start")?; + let end = envelope + .payload + .end + .and_then(deserialize_anchor) + .context("missing range end")?; + let buffer_hints = this + .update(&mut cx, |project, cx| { + project.inlay_hints(buffer, start..end, cx) + }) + .await + .context("inlay hints fetch")?; + + Ok(this.update(&mut cx, |project, cx| { + InlayHints::response_to_proto(buffer_hints, project, sender_id, &buffer_version, cx) + })) + } + + async fn handle_refresh_inlay_hints( + this: ModelHandle, + _: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result { + this.update(&mut cx, |_, cx| { + cx.emit(Event::RefreshInlays); + }); + Ok(proto::Ack {}) + } + async fn handle_lsp_command( this: ModelHandle, envelope: TypedEnvelope, @@ -7074,16 +7506,11 @@ impl Project { ) -> impl Iterator, &Arc)> { self.language_server_ids_for_buffer(buffer, cx) .into_iter() - .filter_map(|server_id| { - let server = self.language_servers.get(&server_id)?; - if let LanguageServerState::Running { + .filter_map(|server_id| match self.language_servers.get(&server_id)? { + LanguageServerState::Running { adapter, server, .. - } = server - { - Some((adapter, server)) - } else { - None - } + } => Some((adapter, server)), + _ => None, }) } @@ -7253,11 +7680,10 @@ impl Entity for Project { .language_servers .drain() .map(|(_, server_state)| async { + use LanguageServerState::*; match server_state { - LanguageServerState::Running { server, .. } => server.shutdown()?.await, - LanguageServerState::Starting(starting_server) => { - starting_server.await?.shutdown()?.await - } + Running { server, .. } => server.shutdown()?.await, + Starting(task) => task.await?.shutdown()?.await, } }) .collect::>(); diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 478fad74a9..16e706a77e 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -596,6 +596,8 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon ); }); + let prev_read_dir_count = fs.read_dir_call_count(); + // Keep track of the FS events reported to the language server. let fake_server = fake_servers.next().await.unwrap(); let file_changes = Arc::new(Mutex::new(Vec::new())); @@ -607,6 +609,12 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon register_options: serde_json::to_value( lsp::DidChangeWatchedFilesRegistrationOptions { watchers: vec![ + lsp::FileSystemWatcher { + glob_pattern: lsp::GlobPattern::String( + "/the-root/Cargo.toml".to_string(), + ), + kind: None, + }, lsp::FileSystemWatcher { glob_pattern: lsp::GlobPattern::String( "/the-root/src/*.{rs,c}".to_string(), @@ -638,6 +646,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon cx.foreground().run_until_parked(); assert_eq!(mem::take(&mut *file_changes.lock()), &[]); + assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 4); // Now the language server has asked us to watch an ignored directory path, // so we recursively load it. diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index be3bcd05fa..20e693770f 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3071,17 +3071,20 @@ impl BackgroundScanner { path_prefix = self.path_prefixes_to_scan_rx.recv().fuse() => { let Ok(path_prefix) = path_prefix else { break }; + log::trace!("adding path prefix {:?}", path_prefix); - self.forcibly_load_paths(&[path_prefix.clone()]).await; + let did_scan = self.forcibly_load_paths(&[path_prefix.clone()]).await; + if did_scan { + let abs_path = + { + let mut state = self.state.lock(); + state.path_prefixes_to_scan.insert(path_prefix.clone()); + state.snapshot.abs_path.join(&path_prefix) + }; - let abs_path = - { - let mut state = self.state.lock(); - state.path_prefixes_to_scan.insert(path_prefix.clone()); - state.snapshot.abs_path.join(path_prefix) - }; - if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() { - self.process_events(vec![abs_path]).await; + if let Some(abs_path) = self.fs.canonicalize(&abs_path).await.log_err() { + self.process_events(vec![abs_path]).await; + } } } @@ -3097,10 +3100,13 @@ impl BackgroundScanner { } } - async fn process_scan_request(&self, request: ScanRequest, scanning: bool) -> bool { + async fn process_scan_request(&self, mut request: ScanRequest, scanning: bool) -> bool { log::debug!("rescanning paths {:?}", request.relative_paths); - let root_path = self.forcibly_load_paths(&request.relative_paths).await; + request.relative_paths.sort_unstable(); + self.forcibly_load_paths(&request.relative_paths).await; + + let root_path = self.state.lock().snapshot.abs_path.clone(); let root_canonical_path = match self.fs.canonicalize(&root_path).await { Ok(path) => path, Err(err) => { @@ -3108,10 +3114,9 @@ impl BackgroundScanner { return false; } }; - let abs_paths = request .relative_paths - .into_iter() + .iter() .map(|path| { if path.file_name().is_some() { root_canonical_path.join(path) @@ -3120,12 +3125,19 @@ impl BackgroundScanner { } }) .collect::>(); - self.reload_entries_for_paths(root_path, root_canonical_path, abs_paths, None) - .await; + + self.reload_entries_for_paths( + root_path, + root_canonical_path, + &request.relative_paths, + abs_paths, + None, + ) + .await; self.send_status_update(scanning, Some(request.done)) } - async fn process_events(&mut self, abs_paths: Vec) { + async fn process_events(&mut self, mut abs_paths: Vec) { log::debug!("received fs events {:?}", abs_paths); let root_path = self.state.lock().snapshot.abs_path.clone(); @@ -3137,25 +3149,61 @@ impl BackgroundScanner { } }; - let (scan_job_tx, scan_job_rx) = channel::unbounded(); - let paths = self - .reload_entries_for_paths( + let mut relative_paths = Vec::with_capacity(abs_paths.len()); + let mut unloaded_relative_paths = Vec::new(); + abs_paths.sort_unstable(); + abs_paths.dedup_by(|a, b| a.starts_with(&b)); + abs_paths.retain(|abs_path| { + let snapshot = &self.state.lock().snapshot; + { + let relative_path: Arc = + if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { + path.into() + } else { + log::error!( + "ignoring event {abs_path:?} outside of root path {root_canonical_path:?}", + ); + return false; + }; + + let parent_dir_is_loaded = relative_path.parent().map_or(true, |parent| { + snapshot + .entry_for_path(parent) + .map_or(false, |entry| entry.kind == EntryKind::Dir) + }); + if !parent_dir_is_loaded { + log::debug!("ignoring event {relative_path:?} within unloaded directory"); + unloaded_relative_paths.push(relative_path); + return false; + } + + relative_paths.push(relative_path); + true + } + }); + + if !relative_paths.is_empty() { + let (scan_job_tx, scan_job_rx) = channel::unbounded(); + self.reload_entries_for_paths( root_path, root_canonical_path, + &relative_paths, abs_paths, Some(scan_job_tx.clone()), ) .await; - drop(scan_job_tx); - self.scan_dirs(false, scan_job_rx).await; + drop(scan_job_tx); + self.scan_dirs(false, scan_job_rx).await; - let (scan_job_tx, scan_job_rx) = channel::unbounded(); - self.update_ignore_statuses(scan_job_tx).await; - self.scan_dirs(false, scan_job_rx).await; + let (scan_job_tx, scan_job_rx) = channel::unbounded(); + self.update_ignore_statuses(scan_job_tx).await; + self.scan_dirs(false, scan_job_rx).await; + } { let mut state = self.state.lock(); - state.reload_repositories(&paths, self.fs.as_ref()); + relative_paths.extend(unloaded_relative_paths); + state.reload_repositories(&relative_paths, self.fs.as_ref()); state.snapshot.completed_scan_id = state.snapshot.scan_id; for (_, entry_id) in mem::take(&mut state.removed_entry_ids) { state.scanned_dirs.remove(&entry_id); @@ -3165,12 +3213,11 @@ impl BackgroundScanner { self.send_status_update(false, None); } - async fn forcibly_load_paths(&self, paths: &[Arc]) -> Arc { - let root_path; + async fn forcibly_load_paths(&self, paths: &[Arc]) -> bool { let (scan_job_tx, mut scan_job_rx) = channel::unbounded(); { let mut state = self.state.lock(); - root_path = state.snapshot.abs_path.clone(); + let root_path = state.snapshot.abs_path.clone(); for path in paths { for ancestor in path.ancestors() { if let Some(entry) = state.snapshot.entry_for_path(ancestor) { @@ -3201,8 +3248,8 @@ impl BackgroundScanner { while let Some(job) = scan_job_rx.next().await { self.scan_dir(&job).await.log_err(); } - self.state.lock().paths_to_scan.clear(); - root_path + + mem::take(&mut self.state.lock().paths_to_scan).len() > 0 } async fn scan_dirs( @@ -3475,7 +3522,7 @@ impl BackgroundScanner { .expect("channel is unbounded"); } } else { - log::debug!("defer scanning directory {:?} {:?}", entry.path, entry.kind); + log::debug!("defer scanning directory {:?}", entry.path); entry.kind = EntryKind::UnloadedDir; } } @@ -3490,26 +3537,10 @@ impl BackgroundScanner { &self, root_abs_path: Arc, root_canonical_path: PathBuf, - mut abs_paths: Vec, + relative_paths: &[Arc], + abs_paths: Vec, scan_queue_tx: Option>, - ) -> Vec> { - let mut event_paths = Vec::>::with_capacity(abs_paths.len()); - abs_paths.sort_unstable(); - abs_paths.dedup_by(|a, b| a.starts_with(&b)); - abs_paths.retain(|abs_path| { - if let Ok(path) = abs_path.strip_prefix(&root_canonical_path) { - event_paths.push(path.into()); - true - } else { - log::error!( - "unexpected event {:?} for root path {:?}", - abs_path, - root_canonical_path - ); - false - } - }); - + ) { let metadata = futures::future::join_all( abs_paths .iter() @@ -3538,30 +3569,15 @@ impl BackgroundScanner { // Remove any entries for paths that no longer exist or are being recursively // refreshed. Do this before adding any new entries, so that renames can be // detected regardless of the order of the paths. - for (path, metadata) in event_paths.iter().zip(metadata.iter()) { + for (path, metadata) in relative_paths.iter().zip(metadata.iter()) { if matches!(metadata, Ok(None)) || doing_recursive_update { log::trace!("remove path {:?}", path); state.remove_path(path); } } - for (path, metadata) in event_paths.iter().zip(metadata.iter()) { - if let (Some(parent), true) = (path.parent(), doing_recursive_update) { - if state - .snapshot - .entry_for_path(parent) - .map_or(true, |entry| entry.kind != EntryKind::Dir) - { - log::debug!( - "ignoring event {path:?} within unloaded directory {:?}", - parent - ); - continue; - } - } - + for (path, metadata) in relative_paths.iter().zip(metadata.iter()) { let abs_path: Arc = root_abs_path.join(&path).into(); - match metadata { Ok(Some((metadata, canonical_path))) => { let ignore_stack = state @@ -3624,12 +3640,10 @@ impl BackgroundScanner { util::extend_sorted( &mut state.changed_paths, - event_paths.iter().cloned(), + relative_paths.iter().cloned(), usize::MAX, Ord::cmp, ); - - event_paths } fn remove_repo_path(&self, path: &Path, snapshot: &mut LocalSnapshot) -> Option<()> { @@ -3760,25 +3774,22 @@ impl BackgroundScanner { // Scan any directories that were previously ignored and weren't // previously scanned. - if was_ignored - && !entry.is_ignored - && !entry.is_external - && entry.kind == EntryKind::UnloadedDir - { - job.scan_queue - .try_send(ScanJob { - abs_path: abs_path.clone(), - path: entry.path.clone(), - ignore_stack: child_ignore_stack.clone(), - scan_queue: job.scan_queue.clone(), - ancestor_inodes: self - .state - .lock() - .snapshot - .ancestor_inodes_for_path(&entry.path), - is_external: false, - }) - .unwrap(); + if was_ignored && !entry.is_ignored && entry.kind.is_unloaded() { + let state = self.state.lock(); + if state.should_scan_directory(&entry) { + job.scan_queue + .try_send(ScanJob { + abs_path: abs_path.clone(), + path: entry.path.clone(), + ignore_stack: child_ignore_stack.clone(), + scan_queue: job.scan_queue.clone(), + ancestor_inodes: state + .snapshot + .ancestor_inodes_for_path(&entry.path), + is_external: false, + }) + .unwrap(); + } } job.ignore_queue diff --git a/crates/project/src/worktree_tests.rs b/crates/project/src/worktree_tests.rs index 553c5e2cca..f908d702eb 100644 --- a/crates/project/src/worktree_tests.rs +++ b/crates/project/src/worktree_tests.rs @@ -454,6 +454,10 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { "b1.js": "b1", "b2.js": "b2", }, + "c": { + "c1.js": "c1", + "c2.js": "c2", + } }, }, "two": { @@ -521,6 +525,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { (Path::new("one/node_modules/b"), true), (Path::new("one/node_modules/b/b1.js"), true), (Path::new("one/node_modules/b/b2.js"), true), + (Path::new("one/node_modules/c"), true), (Path::new("two"), false), (Path::new("two/x.js"), false), (Path::new("two/y.js"), false), @@ -564,6 +569,7 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { (Path::new("one/node_modules/b"), true), (Path::new("one/node_modules/b/b1.js"), true), (Path::new("one/node_modules/b/b2.js"), true), + (Path::new("one/node_modules/c"), true), (Path::new("two"), false), (Path::new("two/x.js"), false), (Path::new("two/y.js"), false), @@ -578,6 +584,17 @@ async fn test_open_gitignored_files(cx: &mut TestAppContext) { // Only the newly-expanded directory is scanned. assert_eq!(fs.read_dir_call_count() - prev_read_dir_count, 1); }); + + // No work happens when files and directories change within an unloaded directory. + let prev_fs_call_count = fs.read_dir_call_count() + fs.metadata_call_count(); + fs.create_dir("/root/one/node_modules/c/lib".as_ref()) + .await + .unwrap(); + cx.foreground().run_until_parked(); + assert_eq!( + fs.read_dir_call_count() + fs.metadata_call_count() - prev_fs_call_count, + 0 + ); } #[gpui::test] diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 0b76dba319..2bfb090bb2 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -384,6 +384,12 @@ impl<'a> From<&'a str> for Rope { } } +impl From for Rope { + fn from(text: String) -> Self { + Rope::from(text.as_str()) + } +} + impl fmt::Display for Rope { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for chunk in self.chunks() { diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 2bce1ce1e3..a0b98372b1 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -136,6 +136,10 @@ message Envelope { OnTypeFormattingResponse on_type_formatting_response = 112; UpdateWorktreeSettings update_worktree_settings = 113; + + InlayHints inlay_hints = 116; + InlayHintsResponse inlay_hints_response = 117; + RefreshInlayHints refresh_inlay_hints = 118; } } @@ -705,6 +709,68 @@ message OnTypeFormattingResponse { Transaction transaction = 1; } +message InlayHints { + uint64 project_id = 1; + uint64 buffer_id = 2; + Anchor start = 3; + Anchor end = 4; + repeated VectorClockEntry version = 5; +} + +message InlayHintsResponse { + repeated InlayHint hints = 1; + repeated VectorClockEntry version = 2; +} + +message InlayHint { + Anchor position = 1; + InlayHintLabel label = 2; + optional string kind = 3; + bool padding_left = 4; + bool padding_right = 5; + InlayHintTooltip tooltip = 6; +} + +message InlayHintLabel { + oneof label { + string value = 1; + InlayHintLabelParts label_parts = 2; + } +} + +message InlayHintLabelParts { + repeated InlayHintLabelPart parts = 1; +} + +message InlayHintLabelPart { + string value = 1; + InlayHintLabelPartTooltip tooltip = 2; + Location location = 3; +} + +message InlayHintTooltip { + oneof content { + string value = 1; + MarkupContent markup_content = 2; + } +} + +message InlayHintLabelPartTooltip { + oneof content { + string value = 1; + MarkupContent markup_content = 2; + } +} + +message RefreshInlayHints { + uint64 project_id = 1; +} + +message MarkupContent { + string kind = 1; + string value = 2; +} + message PerformRenameResponse { ProjectTransaction transaction = 2; } diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 4532e798e7..605b05a562 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -198,6 +198,9 @@ messages!( (PerformRenameResponse, Background), (OnTypeFormatting, Background), (OnTypeFormattingResponse, Background), + (InlayHints, Background), + (InlayHintsResponse, Background), + (RefreshInlayHints, Foreground), (Ping, Foreground), (PrepareRename, Background), (PrepareRenameResponse, Background), @@ -286,6 +289,8 @@ request_messages!( (PerformRename, PerformRenameResponse), (PrepareRename, PrepareRenameResponse), (OnTypeFormatting, OnTypeFormattingResponse), + (InlayHints, InlayHintsResponse), + (RefreshInlayHints, Ack), (ReloadBuffers, ReloadBuffersResponse), (RequestContact, Ack), (RemoveContact, Ack), @@ -332,6 +337,8 @@ entity_messages!( OpenBufferForSymbol, PerformRename, OnTypeFormatting, + InlayHints, + RefreshInlayHints, PrepareRename, ReloadBuffers, RemoveProjectCollaborator, diff --git a/crates/sum_tree/src/cursor.rs b/crates/sum_tree/src/cursor.rs index b78484e6de..59165283f6 100644 --- a/crates/sum_tree/src/cursor.rs +++ b/crates/sum_tree/src/cursor.rs @@ -97,6 +97,42 @@ where } } + pub fn next_item(&self) -> Option<&'a T> { + self.assert_did_seek(); + if let Some(entry) = self.stack.last() { + if entry.index == entry.tree.0.items().len() - 1 { + if let Some(next_leaf) = self.next_leaf() { + Some(next_leaf.0.items().first().unwrap()) + } else { + None + } + } else { + match *entry.tree.0 { + Node::Leaf { ref items, .. } => Some(&items[entry.index + 1]), + _ => unreachable!(), + } + } + } else if self.at_end { + None + } else { + self.tree.first() + } + } + + fn next_leaf(&self) -> Option<&'a SumTree> { + for entry in self.stack.iter().rev().skip(1) { + if entry.index < entry.tree.0.child_trees().len() - 1 { + match *entry.tree.0 { + Node::Internal { + ref child_trees, .. + } => return Some(child_trees[entry.index + 1].leftmost_leaf()), + Node::Leaf { .. } => unreachable!(), + }; + } + } + None + } + pub fn prev_item(&self) -> Option<&'a T> { self.assert_did_seek(); if let Some(entry) = self.stack.last() { diff --git a/crates/sum_tree/src/sum_tree.rs b/crates/sum_tree/src/sum_tree.rs index fa467886f0..8d219ca021 100644 --- a/crates/sum_tree/src/sum_tree.rs +++ b/crates/sum_tree/src/sum_tree.rs @@ -95,31 +95,18 @@ impl fmt::Debug for End { } } -#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Hash, Default)] pub enum Bias { + #[default] Left, Right, } -impl Default for Bias { - fn default() -> Self { - Bias::Left - } -} - -impl PartialOrd for Bias { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for Bias { - fn cmp(&self, other: &Self) -> Ordering { - match (self, other) { - (Self::Left, Self::Left) => Ordering::Equal, - (Self::Left, Self::Right) => Ordering::Less, - (Self::Right, Self::Right) => Ordering::Equal, - (Self::Right, Self::Left) => Ordering::Greater, +impl Bias { + pub fn invert(self) -> Self { + match self { + Self::Left => Self::Right, + Self::Right => Self::Left, } } } @@ -838,6 +825,14 @@ mod tests { assert_eq!(cursor.item(), None); } + if before_start { + assert_eq!(cursor.next_item(), reference_items.get(0)); + } else if pos + 1 < reference_items.len() { + assert_eq!(cursor.next_item().unwrap(), &reference_items[pos + 1]); + } else { + assert_eq!(cursor.next_item(), None); + } + if i < 5 { cursor.next(&()); if pos < reference_items.len() { @@ -883,14 +878,17 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); cursor.prev(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); // Single-element tree @@ -903,22 +901,26 @@ mod tests { ); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 1); cursor.prev(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 0); let mut cursor = tree.cursor::(); assert_eq!(cursor.slice(&Count(1), Bias::Right, &()).items(&()), [1]); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 1); cursor.seek(&Count(0), Bias::Right, &()); @@ -930,6 +932,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 1); // Multiple-element tree @@ -940,67 +943,80 @@ mod tests { assert_eq!(cursor.slice(&Count(2), Bias::Right, &()).items(&()), [1, 2]); assert_eq!(cursor.item(), Some(&3)); assert_eq!(cursor.prev_item(), Some(&2)); + assert_eq!(cursor.next_item(), Some(&4)); assert_eq!(cursor.start().sum, 3); cursor.next(&()); assert_eq!(cursor.item(), Some(&4)); assert_eq!(cursor.prev_item(), Some(&3)); + assert_eq!(cursor.next_item(), Some(&5)); assert_eq!(cursor.start().sum, 6); cursor.next(&()); assert_eq!(cursor.item(), Some(&5)); assert_eq!(cursor.prev_item(), Some(&4)); + assert_eq!(cursor.next_item(), Some(&6)); assert_eq!(cursor.start().sum, 10); cursor.next(&()); assert_eq!(cursor.item(), Some(&6)); assert_eq!(cursor.prev_item(), Some(&5)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 15); cursor.next(&()); cursor.next(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 21); cursor.prev(&()); assert_eq!(cursor.item(), Some(&6)); assert_eq!(cursor.prev_item(), Some(&5)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 15); cursor.prev(&()); assert_eq!(cursor.item(), Some(&5)); assert_eq!(cursor.prev_item(), Some(&4)); + assert_eq!(cursor.next_item(), Some(&6)); assert_eq!(cursor.start().sum, 10); cursor.prev(&()); assert_eq!(cursor.item(), Some(&4)); assert_eq!(cursor.prev_item(), Some(&3)); + assert_eq!(cursor.next_item(), Some(&5)); assert_eq!(cursor.start().sum, 6); cursor.prev(&()); assert_eq!(cursor.item(), Some(&3)); assert_eq!(cursor.prev_item(), Some(&2)); + assert_eq!(cursor.next_item(), Some(&4)); assert_eq!(cursor.start().sum, 3); cursor.prev(&()); assert_eq!(cursor.item(), Some(&2)); assert_eq!(cursor.prev_item(), Some(&1)); + assert_eq!(cursor.next_item(), Some(&3)); assert_eq!(cursor.start().sum, 1); cursor.prev(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), Some(&2)); assert_eq!(cursor.start().sum, 0); cursor.prev(&()); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), Some(&1)); assert_eq!(cursor.start().sum, 0); cursor.next(&()); assert_eq!(cursor.item(), Some(&1)); assert_eq!(cursor.prev_item(), None); + assert_eq!(cursor.next_item(), Some(&2)); assert_eq!(cursor.start().sum, 0); let mut cursor = tree.cursor::(); @@ -1012,6 +1028,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 21); cursor.seek(&Count(3), Bias::Right, &()); @@ -1023,6 +1040,7 @@ mod tests { ); assert_eq!(cursor.item(), None); assert_eq!(cursor.prev_item(), Some(&6)); + assert_eq!(cursor.next_item(), None); assert_eq!(cursor.start().sum, 21); // Seeking can bias left or right diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 2f2ff2cdc3..b92059f5d6 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -395,16 +395,17 @@ impl TerminalElement { // Terminal Emulator controlled behavior: region = region // Start selections - .on_down( - MouseButton::Left, - TerminalElement::generic_button_handler( - connection, - origin, - move |terminal, origin, e, _cx| { - terminal.mouse_down(&e, origin); - }, - ), - ) + .on_down(MouseButton::Left, move |event, v: &mut TerminalView, cx| { + cx.focus_parent(); + v.context_menu.update(cx, |menu, _cx| menu.delay_cancel()); + if let Some(conn_handle) = connection.upgrade(cx) { + conn_handle.update(cx, |terminal, cx| { + terminal.mouse_down(&event, origin); + + cx.notify(); + }) + } + }) // Update drag selections .on_drag(MouseButton::Left, move |event, _: &mut TerminalView, cx| { if cx.is_self_focused() { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 6de6527a26..11f8f7abde 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -87,6 +87,7 @@ impl TerminalPanel { } }) }, + |_, _| {}, None, )) .with_child(Pane::render_tab_bar_button( @@ -100,6 +101,7 @@ impl TerminalPanel { Some(("Toggle Zoom".into(), Some(Box::new(workspace::ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + |_, _| {}, None, )) .into_any() diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 0a62459a3a..e54dcdfd1e 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -689,6 +689,7 @@ pub struct Editor { pub line_number_active: Color, pub guest_selections: Vec, pub syntax: Arc, + pub hint: HighlightStyle, pub suggestion: HighlightStyle, pub diagnostic_path_header: DiagnosticPathHeader, pub diagnostic_header: DiagnosticHeader, diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 71ffacebf3..c8beb86aef 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -118,14 +118,15 @@ pub fn merge_non_null_json_value_into(source: serde_json::Value, target: &mut se } } -pub trait ResultExt { +pub trait ResultExt { type Ok; fn log_err(self) -> Option; fn warn_on_err(self) -> Option; + fn inspect_error(self, func: impl FnOnce(&E)) -> Self; } -impl ResultExt for Result +impl ResultExt for Result where E: std::fmt::Debug, { @@ -152,6 +153,15 @@ where } } } + + /// https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err + fn inspect_error(self, func: impl FnOnce(&E)) -> Self { + if let Err(err) = &self { + func(err); + } + + self + } } pub trait TryFutureExt { diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 9776fede2c..6a20fab9a2 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -273,6 +273,11 @@ impl Pane { Some(("New...".into(), None)), cx, |pane, cx| pane.deploy_new_menu(cx), + |pane, cx| { + pane.tab_bar_context_menu + .handle + .update(cx, |menu, _| menu.delay_cancel()) + }, pane.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::New), )) @@ -283,6 +288,11 @@ impl Pane { Some(("Split Pane".into(), None)), cx, |pane, cx| pane.deploy_split_menu(cx), + |pane, cx| { + pane.tab_bar_context_menu + .handle + .update(cx, |menu, _| menu.delay_cancel()) + }, pane.tab_bar_context_menu .handle_if_kind(TabBarContextMenuKind::Split), )) @@ -304,6 +314,7 @@ impl Pane { Some((tooltip_label, Some(Box::new(ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), + move |_, _| {}, None, ) }) @@ -988,7 +999,7 @@ impl Pane { fn deploy_split_menu(&mut self, cx: &mut ViewContext) { self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::TopRight, vec![ @@ -1006,7 +1017,7 @@ impl Pane { fn deploy_new_menu(&mut self, cx: &mut ViewContext) { self.tab_bar_context_menu.handle.update(cx, |menu, cx| { - menu.show( + menu.toggle( Default::default(), AnchorCorner::TopRight, vec![ @@ -1416,13 +1427,17 @@ impl Pane { .into_any() } - pub fn render_tab_bar_button)>( + pub fn render_tab_bar_button< + F1: 'static + Fn(&mut Pane, &mut EventContext), + F2: 'static + Fn(&mut Pane, &mut EventContext), + >( index: usize, icon: &'static str, is_active: bool, tooltip: Option<(String, Option>)>, cx: &mut ViewContext, - on_click: F, + on_click: F1, + on_down: F2, context_menu: Option>, ) -> AnyElement { enum TabBarButton {} @@ -1440,6 +1455,7 @@ impl Pane { .with_height(style.button_width) }) .with_cursor_style(CursorStyle::PointingHand) + .on_down(MouseButton::Left, move |_, pane, cx| on_down(pane, cx)) .on_click(MouseButton::Left, move |_, pane, cx| on_click(pane, cx)) .into_any(); if let Some((tooltip, action)) = tooltip { diff --git a/crates/zed/src/languages/c.rs b/crates/zed/src/languages/c.rs index 241b11b47c..47aa2b739c 100644 --- a/crates/zed/src/languages/c.rs +++ b/crates/zed/src/languages/c.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use futures::StreamExt; pub use language::*; +use lsp::LanguageServerBinary; use smol::fs::{self, File}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::{ @@ -86,31 +87,19 @@ impl super::LspAdapter for CLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_clangd_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_clangd_dir = Some(entry.path()); - } - } - let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let clangd_bin = clangd_dir.join("bin/clangd"); - if clangd_bin.exists() { - Ok(LanguageServerBinary { - path: clangd_bin, - arguments: vec![], - }) - } else { - Err(anyhow!( - "missing clangd binary in directory {:?}", - clangd_dir - )) - } - })() - .await - .log_err() + get_cached_server_binary(container_dir).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary + }) } async fn label_for_completion( @@ -250,6 +239,34 @@ impl super::LspAdapter for CLspAdapter { } } +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last_clangd_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_clangd_dir = Some(entry.path()); + } + } + let clangd_dir = last_clangd_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let clangd_bin = clangd_dir.join("bin/clangd"); + if clangd_bin.exists() { + Ok(LanguageServerBinary { + path: clangd_bin, + arguments: vec![], + }) + } else { + Err(anyhow!( + "missing clangd binary in directory {:?}", + clangd_dir + )) + } + })() + .await + .log_err() +} + #[cfg(test)] mod tests { use gpui::TestAppContext; diff --git a/crates/zed/src/languages/elixir.rs b/crates/zed/src/languages/elixir.rs index d07faae0cd..c32927e15c 100644 --- a/crates/zed/src/languages/elixir.rs +++ b/crates/zed/src/languages/elixir.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use futures::StreamExt; use gpui::{AsyncAppContext, Task}; pub use language::*; -use lsp::{CompletionItemKind, SymbolKind}; +use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; use smol::fs::{self, File}; use std::{ any::Any, @@ -140,20 +140,14 @@ impl LspAdapter for ElixirLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } - last.map(|path| LanguageServerBinary { - path, - arguments: vec![], - }) - .ok_or_else(|| anyhow!("no cached binary")) - })() - .await - .log_err() + get_cached_server_binary(container_dir).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir).await } async fn label_for_completion( @@ -239,3 +233,20 @@ impl LspAdapter for ElixirLspAdapter { }) } } + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + last.map(|path| LanguageServerBinary { + path, + arguments: vec![], + }) + .ok_or_else(|| anyhow!("no cached binary")) + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/go.rs b/crates/zed/src/languages/go.rs index 34364d0b7f..d7982f7bdb 100644 --- a/crates/zed/src/languages/go.rs +++ b/crates/zed/src/languages/go.rs @@ -4,6 +4,7 @@ use futures::StreamExt; use gpui::{AsyncAppContext, Task}; pub use language::*; use lazy_static::lazy_static; +use lsp::LanguageServerBinary; use regex::Regex; use smol::{fs, process}; use std::{ @@ -148,32 +149,19 @@ impl super::LspAdapter for GoLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_binary_path = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name.starts_with("gopls_")) - { - last_binary_path = Some(entry.path()); - } - } + get_cached_server_binary(container_dir).await + } - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - arguments: server_binary_arguments(), - }) - } else { - Err(anyhow!("no cached binary")) - } - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary + }) } async fn label_for_completion( @@ -336,6 +324,35 @@ impl super::LspAdapter for GoLspAdapter { } } +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last_binary_path = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name.starts_with("gopls_")) + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) + } else { + Err(anyhow!("no cached binary")) + } + })() + .await + .log_err() +} + fn adjust_runs( delta: usize, mut runs: Vec<(Range, HighlightId)>, diff --git a/crates/zed/src/languages/html.rs b/crates/zed/src/languages/html.rs index d038ef8071..ecc839fca6 100644 --- a/crates/zed/src/languages/html.rs +++ b/crates/zed/src/languages/html.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use smol::fs; @@ -13,6 +14,9 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = + "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -22,9 +26,6 @@ pub struct HtmlLspAdapter { } impl HtmlLspAdapter { - const SERVER_PATH: &'static str = - "node_modules/vscode-langservers-extracted/bin/vscode-html-language-server"; - pub fn new(node: Arc) -> Self { HtmlLspAdapter { node } } @@ -54,7 +55,7 @@ impl LspAdapter for HtmlLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -76,31 +77,14 @@ impl LspAdapter for HtmlLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + get_cached_server_binary(container_dir, &self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await } async fn initialization_options(&self) -> Option { @@ -109,3 +93,34 @@ impl LspAdapter for HtmlLspAdapter { })) } } + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index ced733fa1b..b7e4ab4ba7 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -3,9 +3,8 @@ use async_trait::async_trait; use collections::HashMap; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; -use language::{ - LanguageRegistry, LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate, -}; +use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::json; use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore}; @@ -84,32 +83,14 @@ impl LspAdapter for JsonLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } + get_cached_server_binary(container_dir, &self.node).await + } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await } async fn initialization_options(&self) -> Option { @@ -162,6 +143,38 @@ impl LspAdapter for JsonLspAdapter { } } +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} + fn schema_file_match(path: &Path) -> &Path { path.strip_prefix(path.parent().unwrap().parent().unwrap()) .unwrap() diff --git a/crates/zed/src/languages/language_plugin.rs b/crates/zed/src/languages/language_plugin.rs index 5209102454..b071936392 100644 --- a/crates/zed/src/languages/language_plugin.rs +++ b/crates/zed/src/languages/language_plugin.rs @@ -3,7 +3,8 @@ use async_trait::async_trait; use collections::HashMap; use futures::lock::Mutex; use gpui::executor::Background; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use plugin_runtime::{Plugin, PluginBinary, PluginBuilder, WasiFn}; use std::{any::Any, path::PathBuf, sync::Arc}; use util::ResultExt; @@ -129,6 +130,14 @@ impl LspAdapter for PluginLspAdapter { .await } + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + async fn initialization_options(&self) -> Option { let string: String = self .runtime diff --git a/crates/zed/src/languages/lua.rs b/crates/zed/src/languages/lua.rs index 7f63a1fae2..7c5c7179d0 100644 --- a/crates/zed/src/languages/lua.rs +++ b/crates/zed/src/languages/lua.rs @@ -3,7 +3,8 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; -use language::{LanguageServerBinary, LanguageServerName, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use smol::fs; use std::{any::Any, env::consts, ffi::OsString, path::PathBuf}; use util::{ @@ -91,31 +92,47 @@ impl super::LspAdapter for LuaLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - async_iife!({ - let mut last_binary_path = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_file() - && entry - .file_name() - .to_str() - .map_or(false, |name| name == "lua-language-server") - { - last_binary_path = Some(entry.path()); - } - } + get_cached_server_binary(container_dir).await + } - if let Some(path) = last_binary_path { - Ok(LanguageServerBinary { - path, - arguments: server_binary_arguments(), - }) - } else { - Err(anyhow!("no cached binary")) - } - }) - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--version".into()]; + binary + }) } } + +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + async_iife!({ + let mut last_binary_path = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_file() + && entry + .file_name() + .to_str() + .map_or(false, |name| name == "lua-language-server") + { + last_binary_path = Some(entry.path()); + } + } + + if let Some(path) = last_binary_path { + Ok(LanguageServerBinary { + path, + arguments: server_binary_arguments(), + }) + } else { + Err(anyhow!("no cached binary")) + } + }) + .await + .log_err() +} diff --git a/crates/zed/src/languages/python.rs b/crates/zed/src/languages/python.rs index b56a0d6175..41ad28ba86 100644 --- a/crates/zed/src/languages/python.rs +++ b/crates/zed/src/languages/python.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use futures::StreamExt; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use smol::fs; use std::{ @@ -12,6 +13,8 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -21,8 +24,6 @@ pub struct PythonLspAdapter { } impl PythonLspAdapter { - const SERVER_PATH: &'static str = "node_modules/pyright/langserver.index.js"; - pub fn new(node: Arc) -> Self { PythonLspAdapter { node } } @@ -48,7 +49,7 @@ impl LspAdapter for PythonLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -67,31 +68,14 @@ impl LspAdapter for PythonLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + get_cached_server_binary(container_dir, &self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await } async fn process_completion(&self, item: &mut lsp::CompletionItem) { @@ -170,6 +154,37 @@ impl LspAdapter for PythonLspAdapter { } } +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} + #[cfg(test)] mod tests { use gpui::{ModelContext, TestAppContext}; diff --git a/crates/zed/src/languages/ruby.rs b/crates/zed/src/languages/ruby.rs index 18756e3b77..358441352a 100644 --- a/crates/zed/src/languages/ruby.rs +++ b/crates/zed/src/languages/ruby.rs @@ -1,6 +1,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate}; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::LanguageServerBinary; use std::{any::Any, path::PathBuf, sync::Arc}; pub struct RubyLanguageServer; @@ -38,6 +39,14 @@ impl LspAdapter for RubyLanguageServer { }) } + fn can_be_reinstalled(&self) -> bool { + false + } + + async fn installation_test_binary(&self, _: PathBuf) -> Option { + None + } + async fn label_for_completion( &self, item: &lsp::CompletionItem, diff --git a/crates/zed/src/languages/rust.rs b/crates/zed/src/languages/rust.rs index e60846b70a..97549b0058 100644 --- a/crates/zed/src/languages/rust.rs +++ b/crates/zed/src/languages/rust.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use futures::{io::BufReader, StreamExt}; pub use language::*; use lazy_static::lazy_static; +use lsp::LanguageServerBinary; use regex::Regex; use smol::fs::{self, File}; use std::{any::Any, borrow::Cow, env::consts, path::PathBuf, str, sync::Arc}; @@ -78,20 +79,19 @@ impl LspAdapter for RustLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - last = Some(entry?.path()); - } + get_cached_server_binary(container_dir).await + } - anyhow::Ok(LanguageServerBinary { - path: last.ok_or_else(|| anyhow!("no cached binary"))?, - arguments: Default::default(), + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir) + .await + .map(|mut binary| { + binary.arguments = vec!["--help".into()]; + binary }) - })() - .await - .log_err() } async fn disk_based_diagnostic_sources(&self) -> Vec { @@ -258,6 +258,22 @@ impl LspAdapter for RustLspAdapter { }) } } +async fn get_cached_server_binary(container_dir: PathBuf) -> Option { + (|| async move { + let mut last = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + last = Some(entry?.path()); + } + + anyhow::Ok(LanguageServerBinary { + path: last.ok_or_else(|| anyhow!("no cached binary"))?, + arguments: Default::default(), + }) + })() + .await + .log_err() +} #[cfg(test)] mod tests { diff --git a/crates/zed/src/languages/typescript.rs b/crates/zed/src/languages/typescript.rs index 662e73ea33..0a47d365b5 100644 --- a/crates/zed/src/languages/typescript.rs +++ b/crates/zed/src/languages/typescript.rs @@ -4,8 +4,8 @@ use async_tar::Archive; use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt}; use gpui::AppContext; -use language::{LanguageServerBinary, LanguageServerName, LspAdapter, LspAdapterDelegate}; -use lsp::CodeActionKind; +use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; +use lsp::{CodeActionKind, LanguageServerBinary}; use node_runtime::NodeRuntime; use serde_json::{json, Value}; use smol::{fs, io::BufReader, stream::StreamExt}; @@ -104,28 +104,14 @@ impl LspAdapter for TypeScriptLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let old_server_path = container_dir.join(Self::OLD_SERVER_PATH); - let new_server_path = container_dir.join(Self::NEW_SERVER_PATH); - if new_server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: typescript_server_binary_arguments(&new_server_path), - }) - } else if old_server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: typescript_server_binary_arguments(&old_server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - container_dir - )) - } - })() - .await - .log_err() + get_cached_ts_server_binary(container_dir, &self.node).await + } + + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_ts_server_binary(container_dir, &self.node).await } fn code_action_kinds(&self) -> Option> { @@ -173,6 +159,34 @@ impl LspAdapter for TypeScriptLspAdapter { } } +async fn get_cached_ts_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let old_server_path = container_dir.join(TypeScriptLspAdapter::OLD_SERVER_PATH); + let new_server_path = container_dir.join(TypeScriptLspAdapter::NEW_SERVER_PATH); + if new_server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: typescript_server_binary_arguments(&new_server_path), + }) + } else if old_server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: typescript_server_binary_arguments(&old_server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + container_dir + )) + } + })() + .await + .log_err() +} + pub struct EsLintLspAdapter { node: Arc, } @@ -249,11 +263,11 @@ impl LspAdapter for EsLintLspAdapter { fs::rename(first.path(), &repo_root).await?; self.node - .run_npm_subcommand(&repo_root, "install", &[]) + .run_npm_subcommand(Some(&repo_root), "install", &[]) .await?; self.node - .run_npm_subcommand(&repo_root, "run-script", &["compile"]) + .run_npm_subcommand(Some(&repo_root), "run-script", &["compile"]) .await?; } @@ -268,21 +282,14 @@ impl LspAdapter for EsLintLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - // This is unfortunate but we don't know what the version is to build a path directly - let mut dir = fs::read_dir(&container_dir).await?; - let first = dir.next().await.ok_or(anyhow!("missing first file"))??; - if !first.file_type().await?.is_dir() { - return Err(anyhow!("First entry is not a directory")); - } + get_cached_eslint_server_binary(container_dir, &self.node).await + } - Ok(LanguageServerBinary { - path: first.path().join(Self::SERVER_PATH), - arguments: Default::default(), - }) - })() - .await - .log_err() + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_eslint_server_binary(container_dir, &self.node).await } async fn label_for_completion( @@ -298,6 +305,28 @@ impl LspAdapter for EsLintLspAdapter { } } +async fn get_cached_eslint_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + // This is unfortunate but we don't know what the version is to build a path directly + let mut dir = fs::read_dir(&container_dir).await?; + let first = dir.next().await.ok_or(anyhow!("missing first file"))??; + if !first.file_type().await?.is_dir() { + return Err(anyhow!("First entry is not a directory")); + } + let server_path = first.path().join(EsLintLspAdapter::SERVER_PATH); + + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: eslint_server_binary_arguments(&server_path), + }) + })() + .await + .log_err() +} + #[cfg(test)] mod tests { use gpui::TestAppContext; diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index 99c226bba7..b57c6f5699 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -3,9 +3,9 @@ use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; use language::{ - language_settings::all_language_settings, LanguageServerBinary, LanguageServerName, LspAdapter, - LspAdapterDelegate, + language_settings::all_language_settings, LanguageServerName, LspAdapter, LspAdapterDelegate, }; +use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; use serde_json::Value; use smol::fs; @@ -18,6 +18,8 @@ use std::{ }; use util::ResultExt; +const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; + fn server_binary_arguments(server_path: &Path) -> Vec { vec![server_path.into(), "--stdio".into()] } @@ -27,8 +29,6 @@ pub struct YamlLspAdapter { } impl YamlLspAdapter { - const SERVER_PATH: &'static str = "node_modules/yaml-language-server/bin/yaml-language-server"; - pub fn new(node: Arc) -> Self { YamlLspAdapter { node } } @@ -58,7 +58,7 @@ impl LspAdapter for YamlLspAdapter { _: &dyn LspAdapterDelegate, ) -> Result { let version = version.downcast::().unwrap(); - let server_path = container_dir.join(Self::SERVER_PATH); + let server_path = container_dir.join(SERVER_PATH); if fs::metadata(&server_path).await.is_err() { self.node @@ -77,33 +77,15 @@ impl LspAdapter for YamlLspAdapter { container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Option { - (|| async move { - let mut last_version_dir = None; - let mut entries = fs::read_dir(&container_dir).await?; - while let Some(entry) = entries.next().await { - let entry = entry?; - if entry.file_type().await?.is_dir() { - last_version_dir = Some(entry.path()); - } - } - let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; - let server_path = last_version_dir.join(Self::SERVER_PATH); - if server_path.exists() { - Ok(LanguageServerBinary { - path: self.node.binary_path().await?, - arguments: server_binary_arguments(&server_path), - }) - } else { - Err(anyhow!( - "missing executable in directory {:?}", - last_version_dir - )) - } - })() - .await - .log_err() + get_cached_server_binary(container_dir, &self.node).await } + async fn installation_test_binary( + &self, + container_dir: PathBuf, + ) -> Option { + get_cached_server_binary(container_dir, &self.node).await + } fn workspace_configuration(&self, cx: &mut AppContext) -> Option> { let tab_size = all_language_settings(None, cx) .language(Some("YAML")) @@ -121,3 +103,34 @@ impl LspAdapter for YamlLspAdapter { ) } } + +async fn get_cached_server_binary( + container_dir: PathBuf, + node: &NodeRuntime, +) -> Option { + (|| async move { + let mut last_version_dir = None; + let mut entries = fs::read_dir(&container_dir).await?; + while let Some(entry) = entries.next().await { + let entry = entry?; + if entry.file_type().await?.is_dir() { + last_version_dir = Some(entry.path()); + } + } + let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?; + let server_path = last_version_dir.join(SERVER_PATH); + if server_path.exists() { + Ok(LanguageServerBinary { + path: node.binary_path().await?, + arguments: server_binary_arguments(&server_path), + }) + } else { + Err(anyhow!( + "missing executable in directory {:?}", + last_version_dir + )) + } + })() + .await + .log_err() +} diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 5837c0ba2c..a140650ec6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -131,7 +131,7 @@ fn main() { languages.set_executor(cx.background().clone()); languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone()); let languages = Arc::new(languages); - let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned()); + let node_runtime = NodeRuntime::instance(http.clone(), cx.background().to_owned()); languages::init(languages.clone(), node_runtime.clone()); let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx)); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9b204374d1..a7c6956a34 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -2144,7 +2144,7 @@ mod tests { languages.set_executor(cx.background().clone()); let languages = Arc::new(languages); let http = FakeHttpClient::with_404_response(); - let node_runtime = NodeRuntime::new(http, cx.background().to_owned()); + let node_runtime = NodeRuntime::instance(http, cx.background().to_owned()); languages::init(languages.clone(), node_runtime); for name in languages.language_names() { languages.language_for_name(&name); diff --git a/styles/.eslintrc.js b/styles/.eslintrc.js new file mode 100644 index 0000000000..485ff73d10 --- /dev/null +++ b/styles/.eslintrc.js @@ -0,0 +1,33 @@ +module.exports = { + env: { + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:import/typescript", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + }, + plugins: ["@typescript-eslint", "import"], + globals: { + module: true, + }, + settings: { + "import/parsers": { + "@typescript-eslint/parser": [".ts"], + }, + "import/resolver": { + typescript: true, + node: true, + }, + "import/extensions": [".ts"], + }, + rules: { + "linebreak-style": ["error", "unix"], + semi: ["error", "never"], + }, +} diff --git a/styles/.prettierrc b/styles/.prettierrc new file mode 100644 index 0000000000..b83ccdda6a --- /dev/null +++ b/styles/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "printWidth": 80, + "htmlWhitespaceSensitivity": "strict", + "tabWidth": 4 +} diff --git a/styles/package-lock.json b/styles/package-lock.json index 236e571148..3f73a0b4e5 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -12,27 +12,36 @@ "@tokens-studio/types": "^0.2.3", "@types/chroma-js": "^2.4.0", "@types/node": "^18.14.1", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", + "@vitest/coverage-v8": "^0.32.0", "ayu": "^8.0.1", - "bezier-easing": "^2.1.0", - "case-anything": "^2.1.10", "chroma-js": "^2.4.2", "deepmerge": "^4.3.0", + "eslint": "^8.43.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", "json-schema-to-typescript": "^13.0.2", "toml": "^3.0.0", "ts-deepmerge": "^6.0.3", "ts-node": "^10.9.1", + "typescript": "^5.1.5", "utility-types": "^3.10.0", "vitest": "^0.32.0" - }, - "devDependencies": { - "@vitest/coverage-v8": "^0.32.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "engines": { + "node": ">=0.10.0" } }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -61,8 +70,7 @@ "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==" }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", @@ -90,11 +98,92 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.2", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, "engines": { "node": ">=8" } @@ -103,7 +192,6 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -125,7 +213,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -149,6 +236,62 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz", + "integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==", + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.2.12", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pkgr/utils/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, "node_modules/@tokens-studio/types": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@tokens-studio/types/-/types-0.2.3.tgz", @@ -204,14 +347,18 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==" }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==" }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==" + }, "node_modules/@types/lodash": { "version": "4.14.195", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", @@ -232,11 +379,215 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==" }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.60.1.tgz", + "integrity": "sha512-KSWsVvsJsLJv3c4e73y/Bzt7OpqMCADUO846bHcuWYSYM19bldbAeDv7dYyV0jwkbMfJ2XdlzwjhXtuD7OY6bw==", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/type-utils": "5.60.1", + "@typescript-eslint/utils": "5.60.1", + "debug": "^4.3.4", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz", + "integrity": "sha512-pHWlc3alg2oSMGwsU/Is8hbm3XFbcrb6P5wIxcQW9NsYBfnrubl/GhVVD/Jm/t8HXhA2WncoIRfBtnCgRGV96Q==", + "dependencies": { + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.60.1.tgz", + "integrity": "sha512-Dn/LnN7fEoRD+KspEOV0xDMynEmR3iSHdgNsarlXNLGGtcUok8L4N71dxUgt3YvlO8si7E+BJ5Fe3wb5yUw7DQ==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.60.1.tgz", + "integrity": "sha512-vN6UztYqIu05nu7JqwQGzQKUJctzs3/Hg7E2Yx8rz9J+4LgtIDFWjjl1gm3pycH0P3mHAcEUBd23LVgfrsTR8A==", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.60.1", + "@typescript-eslint/utils": "5.60.1", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.60.1.tgz", + "integrity": "sha512-zDcDx5fccU8BA0IDZc71bAtYIcG9PowaOwaD8rjYbqwK7dpe/UMQl3inJ4UtUK42nOCT41jTSCwg76E62JpMcg==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.1.tgz", + "integrity": "sha512-hkX70J9+2M2ZT6fhti5Q2FoU9zb+GeZK2SLP1WZlvUDqdMbEKhexZODD1WodNRyO8eS+4nScvT0dts8IdaBzfw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/visitor-keys": "5.60.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.60.1.tgz", + "integrity": "sha512-tiJ7FFdFQOWssFa3gqb94Ilexyw0JVxj6vBzaSpfN/8IhoKkDuSAenUKvsSHw2A/TMpJb26izIszTXaqygkvpQ==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.60.1", + "@typescript-eslint/types": "5.60.1", + "@typescript-eslint/typescript-estree": "5.60.1", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.1.tgz", + "integrity": "sha512-xEYIxKcultP6E/RMKqube11pGjXH1DCo60mQoWhVYyKfLkwbIVVjYxmOenNMxILx0TjCujPTjjnTIVzm09TXIw==", + "dependencies": { + "@typescript-eslint/types": "5.60.1", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitest/coverage-v8": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-0.32.0.tgz", "integrity": "sha512-VXXlWq9X/NbsoP/l/CHLBjutsFFww1UY1qEhzGjn/DY7Tqe+z0Nu8XKc8im/XUAmjiWsh2XV7sy/F0IKAl4eaw==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", "@bcoe/v8-coverage": "^0.2.3", @@ -332,6 +683,14 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -340,6 +699,21 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -374,6 +748,78 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -382,6 +828,17 @@ "node": "*" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ayu": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ayu/-/ayu-8.0.1.tgz", @@ -397,16 +854,30 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "node_modules/bezier-easing": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", - "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==" + "node_modules/big-integer": { + "version": "1.6.51", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", + "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", + "engines": { + "node": ">=0.6" + } }, "node_modules/blueimp-md5": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==" }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -416,6 +887,31 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -424,20 +920,29 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" }, - "node_modules/case-anything": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz", - "integrity": "sha512-JczJwVrCP0jPKh05McyVsuOg6AYosrB9XWZKbQzXeDAm2ClE/PJE/BcrrQrVyGYH7Jg8V/LDupmyL4kFlVsVFQ==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" + "node": ">=6" } }, "node_modules/chai": { @@ -457,6 +962,35 @@ "node": ">=4" } }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -485,6 +1019,22 @@ "node": ">=0.10" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -511,14 +1061,26 @@ "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -566,6 +1128,11 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, "node_modules/deepmerge": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz", @@ -574,6 +1141,64 @@ "node": ">=0.10.0" } }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -582,6 +1207,124 @@ "node": ">=0.3.1" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.0", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es5-ext": { "version": "0.10.62", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", @@ -662,6 +1405,295 @@ "@esbuild/win32-x64": "0.17.19" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz", + "integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==", + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "get-tsconfig": "^4.5.0", + "globby": "^13.1.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.5" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/globby": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.0.tgz", + "integrity": "sha512-jWsQfayf13NvqKUIL3Ta+CIqMnvlaIDFveWE/dpOZ9+3AMEJozsxDvKA02zync9UuvOM8rOXzsD5GqKP4OnWPQ==", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-typescript/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -679,6 +1711,28 @@ "es5-ext": "~0.10.14" } }, + "node_modules/execa": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz", + "integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -692,11 +1746,122 @@ "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "node_modules/fast-diff": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -715,6 +1880,36 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", @@ -723,6 +1918,20 @@ "node": "*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-stdin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", @@ -734,6 +1943,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.6.2.tgz", + "integrity": "sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -753,6 +1999,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/glob-promise": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz", @@ -771,20 +2028,196 @@ "glob": "^7.1.6" } }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } }, "node_modules/inflight": { "version": "1.0.6", @@ -800,6 +2233,108 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -819,16 +2354,197 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true, "engines": { "node": ">=8" } @@ -837,7 +2553,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", @@ -851,7 +2566,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -865,7 +2579,6 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", - "dev": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -920,11 +2633,44 @@ "node": ">=12.0.0" } }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/local-pkg": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", @@ -936,11 +2682,30 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "node_modules/loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -983,7 +2748,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, "dependencies": { "semver": "^6.0.0" }, @@ -998,7 +2762,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -1034,6 +2797,42 @@ "timers-ext": "^0.1.7" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1107,6 +2906,16 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==" + }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -1117,6 +2926,31 @@ "resolved": "https://registry.npmjs.org/nonenumerable/-/nonenumerable-1.1.1.tgz", "integrity": "sha512-ptUD9w9D8WqW6fuJJkZNCImkf+0vdbgUTbRK3i7jsy3olqtH96hYE6Q/S3Tx9NWbcB/ocAjYshXCAUP0lZ9B4Q==" }, + "node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1125,6 +2959,55 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1133,6 +3016,53 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -1147,6 +3077,64 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1155,6 +3143,27 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", @@ -1173,6 +3182,17 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -1210,6 +3230,14 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -1237,11 +3265,109 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "3.25.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.25.1.tgz", @@ -1257,6 +3383,137 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/run-applescript/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-applescript/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/semver": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.2.tgz", @@ -1271,16 +3528,60 @@ "node": ">=10" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1303,6 +3604,89 @@ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.3.3.tgz", "integrity": "sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==" }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-literal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.1.tgz", @@ -1318,7 +3702,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -1326,11 +3709,49 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -1340,6 +3761,11 @@ "node": ">=8" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -1397,6 +3823,28 @@ "node": ">=14.0.0" } }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toml": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", @@ -1452,11 +3900,52 @@ } } }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -1465,17 +3954,40 @@ "node": ">=4" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.5.tgz", + "integrity": "sha512-FOH+WN/DQjUvN6WgW+c4Ml3yi0PH+a/8q+kNIfRehv1wLhWONedw85iu+vQ39Wp49IzTJEsZ2lyLXpBF7mkF1g==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/ufo": { @@ -1483,6 +3995,36 @@ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.1.2.tgz", "integrity": "sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==" }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/utility-types": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", @@ -1500,7 +4042,6 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -1514,7 +4055,6 @@ "version": "0.3.18", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -1674,6 +4214,54 @@ "node": ">=6" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/why-is-node-running": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", diff --git a/styles/package.json b/styles/package.json index b14fb5f527..d82bbb7e81 100644 --- a/styles/package.json +++ b/styles/package.json @@ -1,24 +1,22 @@ { "name": "styles", "version": "1.0.0", - "description": "", - "main": "index.js", + "description": "Typescript app that builds Zed's themes", + "main": "./src/build_themes.ts", "scripts": { - "build": "ts-node ./src/buildThemes.ts", - "build-licenses": "ts-node ./src/buildLicenses.ts", - "build-tokens": "ts-node ./src/buildTokens.ts", - "build-types": "ts-node ./src/buildTypes.ts", + "build": "ts-node ./src/build_themes.ts", + "build-licenses": "ts-node ./src/build_licenses.ts", + "build-tokens": "ts-node ./src/build_tokens.ts", + "build-types": "ts-node ./src/build_types.ts", "test": "vitest" }, - "author": "", + "author": "Zed Industries (https://github.com/zed-industries/)", "license": "ISC", "dependencies": { "@tokens-studio/types": "^0.2.3", "@types/chroma-js": "^2.4.0", "@types/node": "^18.14.1", "ayu": "^8.0.1", - "bezier-easing": "^2.1.0", - "case-anything": "^2.1.10", "chroma-js": "^2.4.2", "deepmerge": "^4.3.0", "json-schema-to-typescript": "^13.0.2", @@ -26,15 +24,13 @@ "ts-deepmerge": "^6.0.3", "ts-node": "^10.9.1", "utility-types": "^3.10.0", - "vitest": "^0.32.0" - }, - "prettier": { - "semi": false, - "printWidth": 80, - "htmlWhitespaceSensitivity": "strict", - "tabWidth": 4 - }, - "devDependencies": { - "@vitest/coverage-v8": "^0.32.0" + "vitest": "^0.32.0", + "@typescript-eslint/eslint-plugin": "^5.60.1", + "@typescript-eslint/parser": "^5.60.1", + "@vitest/coverage-v8": "^0.32.0", + "eslint": "^8.43.0", + "eslint-import-resolver-typescript": "^3.5.5", + "eslint-plugin-import": "^2.27.5", + "typescript": "^5.1.5" } } diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts deleted file mode 100644 index 13a6951a82..0000000000 --- a/styles/src/buildLicenses.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as fs from "fs" -import toml from "toml" -import { themes } from "./themes" -import { ThemeConfig } from "./common" - -const ACCEPTED_LICENSES_FILE = `${__dirname}/../../script/licenses/zed-licenses.toml` - -// Use the cargo-about configuration file as the source of truth for supported licenses. -function parseAcceptedToml(file: string): string[] { - let buffer = fs.readFileSync(file).toString() - - let obj = toml.parse(buffer) - - if (!Array.isArray(obj.accepted)) { - throw Error("Accepted license source is malformed") - } - - return obj.accepted -} - -function checkLicenses(themes: ThemeConfig[]) { - for (const theme of themes) { - if (!theme.licenseFile) { - throw Error(`Theme ${theme.name} should have a LICENSE file`) - } - } -} - -function generateLicenseFile(themes: ThemeConfig[]) { - checkLicenses(themes) - for (const theme of themes) { - const licenseText = fs.readFileSync(theme.licenseFile).toString() - writeLicense(theme.name, licenseText, theme.licenseUrl) - } -} - -function writeLicense( - themeName: string, - licenseText: string, - licenseUrl?: string -) { - process.stdout.write( - licenseUrl - ? `## [${themeName}](${licenseUrl})\n\n${licenseText}\n********************************************************************************\n\n` - : `## ${themeName}\n\n${licenseText}\n********************************************************************************\n\n` - ) -} - -const acceptedLicenses = parseAcceptedToml(ACCEPTED_LICENSES_FILE) -generateLicenseFile(themes) diff --git a/styles/src/buildThemes.ts b/styles/src/buildThemes.ts deleted file mode 100644 index 8d807d62f3..0000000000 --- a/styles/src/buildThemes.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as fs from "fs" -import { tmpdir } from "os" -import * as path from "path" -import app from "./styleTree/app" -import { ColorScheme, createColorScheme } from "./theme/colorScheme" -import snakeCase from "./utils/snakeCase" -import { themes } from "./themes" - -const assetsDirectory = `${__dirname}/../../assets` -const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")) - -// Clear existing themes -function clearThemes(themeDirectory: string) { - if (!fs.existsSync(themeDirectory)) { - fs.mkdirSync(themeDirectory, { recursive: true }) - } else { - for (const file of fs.readdirSync(themeDirectory)) { - if (file.endsWith(".json")) { - fs.unlinkSync(path.join(themeDirectory, file)) - } - } - } -} - -function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) { - clearThemes(outputDirectory) - for (let colorScheme of colorSchemes) { - let styleTree = snakeCase(app(colorScheme)) - let styleTreeJSON = JSON.stringify(styleTree, null, 2) - let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`) - let outPath = path.join(outputDirectory, `${colorScheme.name}.json`) - fs.writeFileSync(tempPath, styleTreeJSON) - fs.renameSync(tempPath, outPath) - console.log(`- ${outPath} created`) - } -} - -const colorSchemes: ColorScheme[] = themes.map((theme) => - createColorScheme(theme) -) - -// Write new themes to theme directory -writeThemes(colorSchemes, `${assetsDirectory}/themes`) diff --git a/styles/src/buildTokens.ts b/styles/src/buildTokens.ts deleted file mode 100644 index 6c6acd2f22..0000000000 --- a/styles/src/buildTokens.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as fs from "fs" -import * as path from "path" -import { ColorScheme, createColorScheme } from "./common" -import { themes } from "./themes" -import { slugify } from "./utils/slugify" -import { colorSchemeTokens } from "./theme/tokens/colorScheme" - -const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens") -const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json") -const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json") - -function clearTokens(tokensDirectory: string) { - if (!fs.existsSync(tokensDirectory)) { - fs.mkdirSync(tokensDirectory, { recursive: true }) - } else { - for (const file of fs.readdirSync(tokensDirectory)) { - if (file.endsWith(".json")) { - fs.unlinkSync(path.join(tokensDirectory, file)) - } - } - } -} - -type TokenSet = { - id: string - name: string - selectedTokenSets: { [key: string]: "enabled" } -} - -function buildTokenSetOrder(colorSchemes: ColorScheme[]): { - tokenSetOrder: string[] -} { - const tokenSetOrder: string[] = colorSchemes.map((scheme) => - scheme.name.toLowerCase().replace(/\s+/g, "_") - ) - return { tokenSetOrder } -} - -function buildThemesIndex(colorSchemes: ColorScheme[]): TokenSet[] { - const themesIndex: TokenSet[] = colorSchemes.map((scheme, index) => { - const id = `${scheme.isLight ? "light" : "dark"}_${scheme.name - .toLowerCase() - .replace(/\s+/g, "_")}_${index}` - const selectedTokenSets: { [key: string]: "enabled" } = {} - const tokenSet = scheme.name.toLowerCase().replace(/\s+/g, "_") - selectedTokenSets[tokenSet] = "enabled" - - return { - id, - name: `${scheme.name} - ${scheme.isLight ? "Light" : "Dark"}`, - selectedTokenSets, - } - }) - - return themesIndex -} - -function writeTokens(colorSchemes: ColorScheme[], tokensDirectory: string) { - clearTokens(tokensDirectory) - - for (const colorScheme of colorSchemes) { - const fileName = slugify(colorScheme.name) + ".json" - const tokens = colorSchemeTokens(colorScheme) - const tokensJSON = JSON.stringify(tokens, null, 2) - const outPath = path.join(tokensDirectory, fileName) - fs.writeFileSync(outPath, tokensJSON, { mode: 0o644 }) - console.log(`- ${outPath} created`) - } - - const themeIndexData = buildThemesIndex(colorSchemes) - - const themesJSON = JSON.stringify(themeIndexData, null, 2) - fs.writeFileSync(TOKENS_FILE, themesJSON, { mode: 0o644 }) - console.log(`- ${TOKENS_FILE} created`) - - const tokenSetOrderData = buildTokenSetOrder(colorSchemes) - - const metadataJSON = JSON.stringify(tokenSetOrderData, null, 2) - fs.writeFileSync(METADATA_FILE, metadataJSON, { mode: 0o644 }) - console.log(`- ${METADATA_FILE} created`) -} - -const colorSchemes: ColorScheme[] = themes.map((theme) => - createColorScheme(theme) -) - -writeTokens(colorSchemes, TOKENS_DIRECTORY) diff --git a/styles/src/buildTypes.ts b/styles/src/buildTypes.ts deleted file mode 100644 index 8c1981cf97..0000000000 --- a/styles/src/buildTypes.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as fs from "fs/promises" -import * as fsSync from "fs" -import * as path from "path" -import { compile } from "json-schema-to-typescript" - -const BANNER = `/* -* This file is autogenerated -*/\n\n` -const dirname = __dirname - -async function main() { - let schemasPath = path.join(dirname, "../../", "crates/theme/schemas") - let schemaFiles = (await fs.readdir(schemasPath)).filter((x) => - x.endsWith(".json") - ) - - let compiledTypes = new Set() - - for (let filename of schemaFiles) { - let filePath = path.join(schemasPath, filename) - const fileContents = await fs.readFile(filePath) - let schema = JSON.parse(fileContents.toString()) - let compiled = await compile(schema, schema.title, { - bannerComment: "", - }) - let eachType = compiled.split("export") - for (let type of eachType) { - if (!type) { - continue - } - compiledTypes.add("export " + type.trim()) - } - } - - let output = BANNER + Array.from(compiledTypes).join("\n\n") - let outputPath = path.join(dirname, "../../styles/src/types/zed.ts") - - try { - let existing = await fs.readFile(outputPath) - if (existing.toString() == output) { - // Skip writing if it hasn't changed - console.log("Schemas are up to date") - return - } - } catch (e) { - // It's fine if there's no output from a previous run. - // @ts-ignore - if (e.code !== "ENOENT") { - throw e - } - } - - const typesDic = path.dirname(outputPath) - if (!fsSync.existsSync(typesDic)) { - await fs.mkdir(typesDic) - } - await fs.writeFile(outputPath, output) - console.log(`Wrote Typescript types to ${outputPath}`) -} - -main().catch((e) => { - console.error(e) - process.exit(1) -}) diff --git a/styles/src/build_licenses.ts b/styles/src/build_licenses.ts new file mode 100644 index 0000000000..76c18dfee1 --- /dev/null +++ b/styles/src/build_licenses.ts @@ -0,0 +1,50 @@ +import * as fs from "fs" +import toml from "toml" +import { themes } from "./themes" +import { ThemeConfig } from "./common" + +const ACCEPTED_LICENSES_FILE = `${__dirname}/../../script/licenses/zed-licenses.toml` + +// Use the cargo-about configuration file as the source of truth for supported licenses. +function parse_accepted_toml(file: string): string[] { + const buffer = fs.readFileSync(file).toString() + + const obj = toml.parse(buffer) + + if (!Array.isArray(obj.accepted)) { + throw Error("Accepted license source is malformed") + } + + return obj.accepted +} + +function check_licenses(themes: ThemeConfig[]) { + for (const theme of themes) { + if (!theme.license_file) { + throw Error(`Theme ${theme.name} should have a LICENSE file`) + } + } +} + +function generate_license_file(themes: ThemeConfig[]) { + check_licenses(themes) + for (const theme of themes) { + const license_text = fs.readFileSync(theme.license_file).toString() + write_license(theme.name, license_text, theme.license_url) + } +} + +function write_license( + theme_name: string, + license_text: string, + license_url?: string +) { + process.stdout.write( + license_url + ? `## [${theme_name}](${license_url})\n\n${license_text}\n********************************************************************************\n\n` + : `## ${theme_name}\n\n${license_text}\n********************************************************************************\n\n` + ) +} + +const accepted_licenses = parse_accepted_toml(ACCEPTED_LICENSES_FILE) +generate_license_file(themes) diff --git a/styles/src/build_themes.ts b/styles/src/build_themes.ts new file mode 100644 index 0000000000..5a091719df --- /dev/null +++ b/styles/src/build_themes.ts @@ -0,0 +1,43 @@ +import * as fs from "fs" +import { tmpdir } from "os" +import * as path from "path" +import app from "./style_tree/app" +import { ColorScheme, create_color_scheme } from "./theme/color_scheme" +import { themes } from "./themes" + +const assets_directory = `${__dirname}/../../assets` +const temp_directory = fs.mkdtempSync(path.join(tmpdir(), "build-themes")) + +function clear_themes(theme_directory: string) { + if (!fs.existsSync(theme_directory)) { + fs.mkdirSync(theme_directory, { recursive: true }) + } else { + for (const file of fs.readdirSync(theme_directory)) { + if (file.endsWith(".json")) { + fs.unlinkSync(path.join(theme_directory, file)) + } + } + } +} + +function write_themes(themes: ColorScheme[], output_directory: string) { + clear_themes(output_directory) + for (const color_scheme of themes) { + const style_tree = app(color_scheme) + const style_tree_json = JSON.stringify(style_tree, null, 2) + const temp_path = path.join(temp_directory, `${color_scheme.name}.json`) + const out_path = path.join( + output_directory, + `${color_scheme.name}.json` + ) + fs.writeFileSync(temp_path, style_tree_json) + fs.renameSync(temp_path, out_path) + console.log(`- ${out_path} created`) + } +} + +const all_themes: ColorScheme[] = themes.map((theme) => + create_color_scheme(theme) +) + +write_themes(all_themes, `${assets_directory}/themes`) diff --git a/styles/src/build_tokens.ts b/styles/src/build_tokens.ts new file mode 100644 index 0000000000..e33c3712e6 --- /dev/null +++ b/styles/src/build_tokens.ts @@ -0,0 +1,87 @@ +import * as fs from "fs" +import * as path from "path" +import { ColorScheme, create_color_scheme } from "./common" +import { themes } from "./themes" +import { slugify } from "./utils/slugify" +import { theme_tokens } from "./theme/tokens/color_scheme" + +const TOKENS_DIRECTORY = path.join(__dirname, "..", "target", "tokens") +const TOKENS_FILE = path.join(TOKENS_DIRECTORY, "$themes.json") +const METADATA_FILE = path.join(TOKENS_DIRECTORY, "$metadata.json") + +function clear_tokens(tokens_directory: string) { + if (!fs.existsSync(tokens_directory)) { + fs.mkdirSync(tokens_directory, { recursive: true }) + } else { + for (const file of fs.readdirSync(tokens_directory)) { + if (file.endsWith(".json")) { + fs.unlinkSync(path.join(tokens_directory, file)) + } + } + } +} + +type TokenSet = { + id: string + name: string + selected_token_sets: { [key: string]: "enabled" } +} + +function build_token_set_order(theme: ColorScheme[]): { + token_set_order: string[] +} { + const token_set_order: string[] = theme.map((scheme) => + scheme.name.toLowerCase().replace(/\s+/g, "_") + ) + return { token_set_order } +} + +function build_themes_index(theme: ColorScheme[]): TokenSet[] { + const themes_index: TokenSet[] = theme.map((scheme, index) => { + const id = `${scheme.is_light ? "light" : "dark"}_${scheme.name + .toLowerCase() + .replace(/\s+/g, "_")}_${index}` + const selected_token_sets: { [key: string]: "enabled" } = {} + const token_set = scheme.name.toLowerCase().replace(/\s+/g, "_") + selected_token_sets[token_set] = "enabled" + + return { + id, + name: `${scheme.name} - ${scheme.is_light ? "Light" : "Dark"}`, + selected_token_sets, + } + }) + + return themes_index +} + +function write_tokens(themes: ColorScheme[], tokens_directory: string) { + clear_tokens(tokens_directory) + + for (const theme of themes) { + const file_name = slugify(theme.name) + ".json" + const tokens = theme_tokens(theme) + const tokens_json = JSON.stringify(tokens, null, 2) + const out_path = path.join(tokens_directory, file_name) + fs.writeFileSync(out_path, tokens_json, { mode: 0o644 }) + console.log(`- ${out_path} created`) + } + + const theme_index_data = build_themes_index(themes) + + const themes_json = JSON.stringify(theme_index_data, null, 2) + fs.writeFileSync(TOKENS_FILE, themes_json, { mode: 0o644 }) + console.log(`- ${TOKENS_FILE} created`) + + const token_set_order_data = build_token_set_order(themes) + + const metadata_json = JSON.stringify(token_set_order_data, null, 2) + fs.writeFileSync(METADATA_FILE, metadata_json, { mode: 0o644 }) + console.log(`- ${METADATA_FILE} created`) +} + +const all_themes: ColorScheme[] = themes.map((theme) => + create_color_scheme(theme) +) + +write_tokens(all_themes, TOKENS_DIRECTORY) diff --git a/styles/src/build_types.ts b/styles/src/build_types.ts new file mode 100644 index 0000000000..5d7aa6e0ad --- /dev/null +++ b/styles/src/build_types.ts @@ -0,0 +1,62 @@ +import * as fs from "fs/promises" +import * as fsSync from "fs" +import * as path from "path" +import { compile } from "json-schema-to-typescript" + +const BANNER = `/* +* This file is autogenerated +*/\n\n` +const dirname = __dirname + +async function main() { + const schemas_path = path.join(dirname, "../../", "crates/theme/schemas") + const schema_files = (await fs.readdir(schemas_path)).filter((x) => + x.endsWith(".json") + ) + + const compiled_types = new Set() + + for (const filename of schema_files) { + const file_path = path.join(schemas_path, filename) + const file_contents = await fs.readFile(file_path) + const schema = JSON.parse(file_contents.toString()) + const compiled = await compile(schema, schema.title, { + bannerComment: "", + }) + const each_type = compiled.split("export") + for (const type of each_type) { + if (!type) { + continue + } + compiled_types.add("export " + type.trim()) + } + } + + const output = BANNER + Array.from(compiled_types).join("\n\n") + const output_path = path.join(dirname, "../../styles/src/types/zed.ts") + + try { + const existing = await fs.readFile(output_path) + if (existing.toString() == output) { + // Skip writing if it hasn't changed + console.log("Schemas are up to date") + return + } + } catch (e) { + if (e.code !== "ENOENT") { + throw e + } + } + + const types_dic = path.dirname(output_path) + if (!fsSync.existsSync(types_dic)) { + await fs.mkdir(types_dic) + } + await fs.writeFile(output_path, output) + console.log(`Wrote Typescript types to ${output_path}`) +} + +main().catch((e) => { + console.error(e) + process.exit(1) +}) diff --git a/styles/src/common.ts b/styles/src/common.ts index ee47bcc6bd..054b283791 100644 --- a/styles/src/common.ts +++ b/styles/src/common.ts @@ -2,42 +2,24 @@ import chroma from "chroma-js" export * from "./theme" export { chroma } -export const fontFamilies = { +export const font_families = { sans: "Zed Sans", mono: "Zed Mono", } -export const fontSizes = { - "3xs": 8, +export const font_sizes = { "2xs": 10, xs: 12, sm: 14, md: 16, lg: 18, - xl: 20, } -export type FontWeight = - | "thin" - | "extra_light" - | "light" - | "normal" - | "medium" - | "semibold" - | "bold" - | "extra_bold" - | "black" +export type FontWeight = "normal" | "bold" -export const fontWeights: { [key: string]: FontWeight } = { - thin: "thin", - extra_light: "extra_light", - light: "light", +export const font_weights: { [key: string]: FontWeight } = { normal: "normal", - medium: "medium", - semibold: "semibold", bold: "bold", - extra_bold: "extra_bold", - black: "black", } export const sizes = { diff --git a/styles/src/component/icon_button.ts b/styles/src/component/icon_button.ts index 39c1adb5e5..79891c2477 100644 --- a/styles/src/component/icon_button.ts +++ b/styles/src/component/icon_button.ts @@ -1,6 +1,6 @@ -import { ColorScheme } from "../common" import { interactive, toggleable } from "../element" -import { background, foreground } from "../styleTree/components" +import { background, foreground } from "../style_tree/components" +import { ColorScheme } from "../theme/color_scheme" export type Margin = { top: number diff --git a/styles/src/component/text_button.ts b/styles/src/component/text_button.ts index b8a2272cd3..477c2515e3 100644 --- a/styles/src/component/text_button.ts +++ b/styles/src/component/text_button.ts @@ -1,11 +1,11 @@ -import { ColorScheme } from "../common" import { interactive, toggleable } from "../element" import { TextProperties, background, foreground, text, -} from "../styleTree/components" +} from "../style_tree/components" +import { ColorScheme } from "../theme/color_scheme" import { Margin } from "./icon_button" interface TextButtonOptions { diff --git a/styles/src/element/interactive.test.ts b/styles/src/element/interactive.test.ts index b0cc57875f..0e0013fc07 100644 --- a/styles/src/element/interactive.test.ts +++ b/styles/src/element/interactive.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect } from "vitest" describe("interactive", () => { it("creates an Interactive with base properties and states", () => { const result = interactive({ - base: { fontSize: 10, color: "#FFFFFF" }, + base: { font_size: 10, color: "#FFFFFF" }, state: { hovered: { color: "#EEEEEE" }, clicked: { color: "#CCCCCC" }, @@ -16,25 +16,25 @@ describe("interactive", () => { }) expect(result).toEqual({ - default: { color: "#FFFFFF", fontSize: 10 }, - hovered: { color: "#EEEEEE", fontSize: 10 }, - clicked: { color: "#CCCCCC", fontSize: 10 }, + default: { color: "#FFFFFF", font_size: 10 }, + hovered: { color: "#EEEEEE", font_size: 10 }, + clicked: { color: "#CCCCCC", font_size: 10 }, }) }) it("creates an Interactive with no base properties", () => { const result = interactive({ state: { - default: { color: "#FFFFFF", fontSize: 10 }, + default: { color: "#FFFFFF", font_size: 10 }, hovered: { color: "#EEEEEE" }, clicked: { color: "#CCCCCC" }, }, }) expect(result).toEqual({ - default: { color: "#FFFFFF", fontSize: 10 }, - hovered: { color: "#EEEEEE", fontSize: 10 }, - clicked: { color: "#CCCCCC", fontSize: 10 }, + default: { color: "#FFFFFF", font_size: 10 }, + hovered: { color: "#EEEEEE", font_size: 10 }, + clicked: { color: "#CCCCCC", font_size: 10 }, }) }) @@ -48,7 +48,7 @@ describe("interactive", () => { it("throws error when no other state besides default is present", () => { const state = { - default: { fontSize: 10 }, + default: { font_size: 10 }, } expect(() => interactive({ state })).toThrow(NOT_ENOUGH_STATES_ERROR) diff --git a/styles/src/element/interactive.ts b/styles/src/element/interactive.ts index 03a1f7f5ce..59ccff40f7 100644 --- a/styles/src/element/interactive.ts +++ b/styles/src/element/interactive.ts @@ -37,61 +37,61 @@ interface InteractiveProps { * @param state Object containing optional modified fields to be included in the resulting object for each state. * @returns Interactive object with fields from `base` and `state`. */ -export function interactive({ +export function interactive({ base, state, }: InteractiveProps): Interactive { if (!base && !state.default) throw new Error(NO_DEFAULT_OR_BASE_ERROR) - let defaultState: T + let default_state: T if (state.default && base) { - defaultState = merge(base, state.default) as T + default_state = merge(base, state.default) as T } else { - defaultState = base ? base : (state.default as T) + default_state = base ? base : (state.default as T) } - let interactiveObj: Interactive = { - default: defaultState, + const interactive_obj: Interactive = { + default: default_state, } - let stateCount = 0 + let state_count = 0 if (state.hovered !== undefined) { - interactiveObj.hovered = merge( - interactiveObj.default, + interactive_obj.hovered = merge( + interactive_obj.default, state.hovered ) as T - stateCount++ + state_count++ } if (state.clicked !== undefined) { - interactiveObj.clicked = merge( - interactiveObj.default, + interactive_obj.clicked = merge( + interactive_obj.default, state.clicked ) as T - stateCount++ + state_count++ } if (state.selected !== undefined) { - interactiveObj.selected = merge( - interactiveObj.default, + interactive_obj.selected = merge( + interactive_obj.default, state.selected ) as T - stateCount++ + state_count++ } if (state.disabled !== undefined) { - interactiveObj.disabled = merge( - interactiveObj.default, + interactive_obj.disabled = merge( + interactive_obj.default, state.disabled ) as T - stateCount++ + state_count++ } - if (stateCount < 1) { + if (state_count < 1) { throw new Error(NOT_ENOUGH_STATES_ERROR) } - return interactiveObj + return interactive_obj } diff --git a/styles/src/element/toggle.ts b/styles/src/element/toggle.ts index ead8f1e824..c3cde46d65 100644 --- a/styles/src/element/toggle.ts +++ b/styles/src/element/toggle.ts @@ -35,13 +35,13 @@ export function toggleable( if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR) if (!state.active) throw new Error(NO_ACTIVE_ERROR) - const inactiveState = base + const inactive_state = base ? ((state.inactive ? merge(base, state.inactive) : base) as T) : (state.inactive as T) - const toggleObj: Toggleable = { - inactive: inactiveState, + const toggle_obj: Toggleable = { + inactive: inactive_state, active: merge(base ?? {}, state.active) as T, } - return toggleObj + return toggle_obj } diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts deleted file mode 100644 index d98e00383f..0000000000 --- a/styles/src/styleTree/app.ts +++ /dev/null @@ -1,75 +0,0 @@ -import contactFinder from "./contactFinder" -import contactsPopover from "./contactsPopover" -import commandPalette from "./commandPalette" -import editor from "./editor" -import projectPanel from "./projectPanel" -import search from "./search" -import picker from "./picker" -import workspace from "./workspace" -import contextMenu from "./contextMenu" -import sharedScreen from "./sharedScreen" -import projectDiagnostics from "./projectDiagnostics" -import contactNotification from "./contactNotification" -import updateNotification from "./updateNotification" -import simpleMessageNotification from "./simpleMessageNotification" -import projectSharedNotification from "./projectSharedNotification" -import tooltip from "./tooltip" -import terminal from "./terminal" -import contactList from "./contactList" -import toolbarDropdownMenu from "./toolbarDropdownMenu" -import incomingCallNotification from "./incomingCallNotification" -import { ColorScheme } from "../theme/colorScheme" -import feedback from "./feedback" -import welcome from "./welcome" -import copilot from "./copilot" -import assistant from "./assistant" -import { titlebar } from "./titlebar" - -export default function app(colorScheme: ColorScheme): Object { - return { - meta: { - name: colorScheme.name, - isLight: colorScheme.isLight, - }, - commandPalette: commandPalette(colorScheme), - contactNotification: contactNotification(colorScheme), - projectSharedNotification: projectSharedNotification(colorScheme), - incomingCallNotification: incomingCallNotification(colorScheme), - picker: picker(colorScheme), - workspace: workspace(colorScheme), - titlebar: titlebar(colorScheme), - copilot: copilot(colorScheme), - welcome: welcome(colorScheme), - contextMenu: contextMenu(colorScheme), - editor: editor(colorScheme), - projectDiagnostics: projectDiagnostics(colorScheme), - projectPanel: projectPanel(colorScheme), - contactsPopover: contactsPopover(colorScheme), - contactFinder: contactFinder(colorScheme), - contactList: contactList(colorScheme), - toolbarDropdownMenu: toolbarDropdownMenu(colorScheme), - search: search(colorScheme), - sharedScreen: sharedScreen(colorScheme), - updateNotification: updateNotification(colorScheme), - simpleMessageNotification: simpleMessageNotification(colorScheme), - tooltip: tooltip(colorScheme), - terminal: terminal(colorScheme), - assistant: assistant(colorScheme), - feedback: feedback(colorScheme), - colorScheme: { - ...colorScheme, - players: Object.values(colorScheme.players), - ramps: { - neutral: colorScheme.ramps.neutral.colors(100, "hex"), - red: colorScheme.ramps.red.colors(100, "hex"), - orange: colorScheme.ramps.orange.colors(100, "hex"), - yellow: colorScheme.ramps.yellow.colors(100, "hex"), - green: colorScheme.ramps.green.colors(100, "hex"), - cyan: colorScheme.ramps.cyan.colors(100, "hex"), - blue: colorScheme.ramps.blue.colors(100, "hex"), - violet: colorScheme.ramps.violet.colors(100, "hex"), - magenta: colorScheme.ramps.magenta.colors(100, "hex"), - }, - }, - } -} diff --git a/styles/src/styleTree/contactFinder.ts b/styles/src/styleTree/contactFinder.ts deleted file mode 100644 index e45647c3d6..0000000000 --- a/styles/src/styleTree/contactFinder.ts +++ /dev/null @@ -1,70 +0,0 @@ -import picker from "./picker" -import { ColorScheme } from "../theme/colorScheme" -import { background, border, foreground, text } from "./components" - -export default function contactFinder(colorScheme: ColorScheme): any { - let layer = colorScheme.middle - - const sideMargin = 6 - const contactButton = { - background: background(layer, "variant"), - color: foreground(layer, "variant"), - iconWidth: 8, - buttonWidth: 16, - cornerRadius: 8, - } - - const pickerStyle = picker(colorScheme) - const pickerInput = { - background: background(layer, "on"), - cornerRadius: 6, - text: text(layer, "mono"), - placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }), - selection: colorScheme.players[0], - border: border(layer), - padding: { - bottom: 4, - left: 8, - right: 8, - top: 4, - }, - margin: { - left: sideMargin, - right: sideMargin, - }, - } - - return { - picker: { - emptyContainer: {}, - item: { - ...pickerStyle.item, - margin: { left: sideMargin, right: sideMargin }, - }, - noMatches: pickerStyle.noMatches, - inputEditor: pickerInput, - emptyInputEditor: pickerInput, - }, - rowHeight: 28, - contactAvatar: { - cornerRadius: 10, - width: 18, - }, - contactUsername: { - padding: { - left: 8, - }, - }, - contactButton: { - ...contactButton, - hover: { - background: background(layer, "variant", "hovered"), - }, - }, - disabledContactButton: { - ...contactButton, - background: background(layer, "disabled"), - color: foreground(layer, "disabled"), - }, - } -} diff --git a/styles/src/styleTree/contactNotification.ts b/styles/src/styleTree/contactNotification.ts deleted file mode 100644 index 825c5a389a..0000000000 --- a/styles/src/styleTree/contactNotification.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, foreground, text } from "./components" -import { interactive } from "../element" -const avatarSize = 12 -const headerPadding = 8 - -export default function contactNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.lowest - return { - headerAvatar: { - height: avatarSize, - width: avatarSize, - cornerRadius: 6, - }, - headerMessage: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, - }, - headerHeight: 18, - bodyMessage: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 }, - }, - button: interactive({ - base: { - ...text(layer, "sans", "on", { size: "xs" }), - background: background(layer, "on"), - padding: 4, - cornerRadius: 6, - margin: { left: 6 }, - }, - - state: { - hovered: { - background: background(layer, "on", "hovered"), - }, - }, - }), - - dismissButton: { - default: { - color: foreground(layer, "variant"), - iconWidth: 8, - iconHeight: 8, - buttonWidth: 8, - buttonHeight: 8, - hover: { - color: foreground(layer, "hovered"), - }, - }, - }, - } -} diff --git a/styles/src/styleTree/contactsPopover.ts b/styles/src/styleTree/contactsPopover.ts deleted file mode 100644 index 5946bfb82c..0000000000 --- a/styles/src/styleTree/contactsPopover.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, text } from "./components" - -export default function contactsPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle - const sidePadding = 12 - return { - background: background(layer), - cornerRadius: 6, - padding: { top: 6, bottom: 6 }, - shadow: colorScheme.popoverShadow, - border: border(layer), - width: 300, - height: 400, - } -} diff --git a/styles/src/styleTree/contextMenu.ts b/styles/src/styleTree/contextMenu.ts deleted file mode 100644 index a59284c43a..0000000000 --- a/styles/src/styleTree/contextMenu.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, borderColor, text } from "./components" -import { interactive, toggleable } from "../element" - -export default function contextMenu(colorScheme: ColorScheme) { - let layer = colorScheme.middle - return { - background: background(layer), - cornerRadius: 10, - padding: 4, - shadow: colorScheme.popoverShadow, - border: border(layer), - keystrokeMargin: 30, - item: toggleable({ - base: interactive({ - base: { - iconSpacing: 8, - iconWidth: 14, - padding: { left: 6, right: 6, top: 2, bottom: 2 }, - cornerRadius: 6, - label: text(layer, "sans", { size: "sm" }), - keystroke: { - ...text(layer, "sans", "variant", { - size: "sm", - weight: "bold", - }), - padding: { left: 3, right: 3 }, - }, - }, - state: { - hovered: { - background: background(layer, "hovered"), - label: text(layer, "sans", "hovered", { size: "sm" }), - keystroke: { - ...text(layer, "sans", "hovered", { - size: "sm", - weight: "bold", - }), - padding: { left: 3, right: 3 }, - }, - }, - clicked: { - background: background(layer, "pressed"), - }, - }, - }), - state: { - active: { - default: { - background: background(layer, "active"), - }, - hovered: { - background: background(layer, "hovered"), - }, - clicked: { - background: background(layer, "pressed"), - }, - }, - }, - }), - - separator: { - background: borderColor(layer), - margin: { top: 2, bottom: 2 }, - }, - } -} diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index c53f3ba2ff..e69de29bb2 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -1,314 +0,0 @@ -import { withOpacity } from "../theme/color" -import { ColorScheme, Layer, StyleSets } from "../theme/colorScheme" -import { background, border, borderColor, foreground, text } from "./components" -import hoverPopover from "./hoverPopover" - -import { buildSyntax } from "../theme/syntax" -import { interactive, toggleable } from "../element" - -export default function editor(colorScheme: ColorScheme) { - const { isLight } = colorScheme - - let layer = colorScheme.highest - - const autocompleteItem = { - cornerRadius: 6, - padding: { - bottom: 2, - left: 6, - right: 6, - top: 2, - }, - } - - function diagnostic(layer: Layer, styleSet: StyleSets) { - return { - textScaleFactor: 0.857, - header: { - border: border(layer, { - top: true, - }), - }, - message: { - text: text(layer, "sans", styleSet, "default", { size: "sm" }), - highlightText: text(layer, "sans", styleSet, "default", { - size: "sm", - weight: "bold", - }), - }, - } - } - - const syntax = buildSyntax(colorScheme) - - return { - textColor: syntax.primary.color, - background: background(layer), - activeLineBackground: withOpacity(background(layer, "on"), 0.75), - highlightedLineBackground: background(layer, "on"), - // Inline autocomplete suggestions, Co-pilot suggestions, etc. - suggestion: syntax.predictive, - codeActions: { - indicator: toggleable({ - base: interactive({ - base: { - color: foreground(layer, "variant"), - }, - state: { - hovered: { - color: foreground(layer, "variant", "hovered"), - }, - clicked: { - color: foreground(layer, "variant", "pressed"), - }, - }, - }), - state: { - active: { - default: { - color: foreground(layer, "accent"), - }, - hovered: { - color: foreground(layer, "accent", "hovered"), - }, - clicked: { - color: foreground(layer, "accent", "pressed"), - }, - }, - }, - }), - - verticalScale: 0.55, - }, - folds: { - iconMarginScale: 2.5, - foldedIcon: "icons/chevron_right_8.svg", - foldableIcon: "icons/chevron_down_8.svg", - indicator: toggleable({ - base: interactive({ - base: { - color: foreground(layer, "variant"), - }, - state: { - hovered: { - color: foreground(layer, "on"), - }, - clicked: { - color: foreground(layer, "base"), - }, - }, - }), - state: { - active: { - default: { - color: foreground(layer, "default"), - }, - hovered: { - color: foreground(layer, "variant"), - }, - }, - }, - }), - ellipses: { - textColor: colorScheme.ramps.neutral(0.71).hex(), - cornerRadiusFactor: 0.15, - background: { - // Copied from hover_popover highlight - default: { - color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(), - }, - - hovered: { - color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(), - }, - - clicked: { - color: colorScheme.ramps.neutral(0.5).alpha(0.7).hex(), - }, - }, - }, - foldBackground: foreground(layer, "variant"), - }, - diff: { - deleted: isLight - ? colorScheme.ramps.red(0.5).hex() - : colorScheme.ramps.red(0.4).hex(), - modified: isLight - ? colorScheme.ramps.yellow(0.5).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.4).hex() - : colorScheme.ramps.green(0.5).hex(), - removedWidthEm: 0.275, - widthEm: 0.15, - cornerRadius: 0.05, - }, - /** Highlights matching occurrences of what is under the cursor - * as well as matched brackets - */ - documentHighlightReadBackground: withOpacity( - foreground(layer, "accent"), - 0.1 - ), - documentHighlightWriteBackground: colorScheme.ramps - .neutral(0.5) - .alpha(0.4) - .hex(), // TODO: This was blend * 2 - errorColor: background(layer, "negative"), - gutterBackground: background(layer), - gutterPaddingFactor: 3.5, - lineNumber: withOpacity(foreground(layer), 0.35), - lineNumberActive: foreground(layer), - renameFade: 0.6, - unnecessaryCodeFade: 0.5, - selection: colorScheme.players[0], - whitespace: colorScheme.ramps.neutral(0.5).hex(), - guestSelections: [ - colorScheme.players[1], - colorScheme.players[2], - colorScheme.players[3], - colorScheme.players[4], - colorScheme.players[5], - colorScheme.players[6], - colorScheme.players[7], - ], - autocomplete: { - background: background(colorScheme.middle), - cornerRadius: 8, - padding: 4, - margin: { - left: -14, - }, - border: border(colorScheme.middle), - shadow: colorScheme.popoverShadow, - matchHighlight: foreground(colorScheme.middle, "accent"), - item: autocompleteItem, - hoveredItem: { - ...autocompleteItem, - matchHighlight: foreground( - colorScheme.middle, - "accent", - "hovered" - ), - background: background(colorScheme.middle, "hovered"), - }, - selectedItem: { - ...autocompleteItem, - matchHighlight: foreground( - colorScheme.middle, - "accent", - "active" - ), - background: background(colorScheme.middle, "active"), - }, - }, - diagnosticHeader: { - background: background(colorScheme.middle), - iconWidthFactor: 1.5, - textScaleFactor: 0.857, - border: border(colorScheme.middle, { - bottom: true, - top: true, - }), - code: { - ...text(colorScheme.middle, "mono", { size: "sm" }), - margin: { - left: 10, - }, - }, - source: { - text: text(colorScheme.middle, "sans", { - size: "sm", - weight: "bold", - }), - }, - message: { - highlightText: text(colorScheme.middle, "sans", { - size: "sm", - weight: "bold", - }), - text: text(colorScheme.middle, "sans", { size: "sm" }), - }, - }, - diagnosticPathHeader: { - background: background(colorScheme.middle), - textScaleFactor: 0.857, - filename: text(colorScheme.middle, "mono", { size: "sm" }), - path: { - ...text(colorScheme.middle, "mono", { size: "sm" }), - margin: { - left: 12, - }, - }, - }, - errorDiagnostic: diagnostic(colorScheme.middle, "negative"), - warningDiagnostic: diagnostic(colorScheme.middle, "warning"), - informationDiagnostic: diagnostic(colorScheme.middle, "accent"), - hintDiagnostic: diagnostic(colorScheme.middle, "warning"), - invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"), - invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"), - invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"), - invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"), - hoverPopover: hoverPopover(colorScheme), - linkDefinition: { - color: syntax.linkUri.color, - underline: syntax.linkUri.underline, - }, - jumpIcon: interactive({ - base: { - color: foreground(layer, "on"), - iconWidth: 20, - buttonWidth: 20, - cornerRadius: 6, - padding: { - top: 6, - bottom: 6, - left: 6, - right: 6, - }, - }, - state: { - hovered: { - background: background(layer, "on", "hovered"), - }, - }, - }), - - scrollbar: { - width: 12, - minHeightFactor: 1.0, - track: { - border: border(layer, "variant", { left: true }), - }, - thumb: { - background: withOpacity(background(layer, "inverted"), 0.3), - border: { - width: 1, - color: borderColor(layer, "variant"), - top: false, - right: true, - left: true, - bottom: false, - }, - }, - git: { - deleted: isLight - ? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8), - modified: isLight - ? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8), - inserted: isLight - ? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8) - : withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8), - }, - }, - compositionMark: { - underline: { - thickness: 1.0, - color: borderColor(layer), - }, - }, - syntax, - } -} diff --git a/styles/src/styleTree/feedback.ts b/styles/src/styleTree/feedback.ts deleted file mode 100644 index c98cbe768f..0000000000 --- a/styles/src/styleTree/feedback.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, text } from "./components" -import { interactive } from "../element" - -export default function feedback(colorScheme: ColorScheme) { - let layer = colorScheme.highest - - return { - submit_button: interactive({ - base: { - ...text(layer, "mono", "on"), - background: background(layer, "on"), - cornerRadius: 6, - border: border(layer, "on"), - margin: { - right: 4, - }, - padding: { - bottom: 2, - left: 10, - right: 10, - top: 2, - }, - }, - state: { - clicked: { - ...text(layer, "mono", "on", "pressed"), - background: background(layer, "on", "pressed"), - border: border(layer, "on", "pressed"), - }, - hovered: { - ...text(layer, "mono", "on", "hovered"), - background: background(layer, "on", "hovered"), - border: border(layer, "on", "hovered"), - }, - }, - }), - button_margin: 8, - info_text_default: text(layer, "sans", "default", { size: "xs" }), - link_text_default: text(layer, "sans", "default", { - size: "xs", - underline: true, - }), - link_text_hover: text(layer, "sans", "hovered", { - size: "xs", - underline: true, - }), - } -} diff --git a/styles/src/styleTree/hoverPopover.ts b/styles/src/styleTree/hoverPopover.ts deleted file mode 100644 index f8988f1f3a..0000000000 --- a/styles/src/styleTree/hoverPopover.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, foreground, text } from "./components" - -export default function HoverPopover(colorScheme: ColorScheme) { - let layer = colorScheme.middle - let baseContainer = { - background: background(layer), - cornerRadius: 8, - padding: { - left: 8, - right: 8, - top: 4, - bottom: 4, - }, - shadow: colorScheme.popoverShadow, - border: border(layer), - margin: { - left: -8, - }, - } - - return { - container: baseContainer, - infoContainer: { - ...baseContainer, - background: background(layer, "accent"), - border: border(layer, "accent"), - }, - warningContainer: { - ...baseContainer, - background: background(layer, "warning"), - border: border(layer, "warning"), - }, - errorContainer: { - ...baseContainer, - background: background(layer, "negative"), - border: border(layer, "negative"), - }, - blockStyle: { - padding: { top: 4 }, - }, - prose: text(layer, "sans", { size: "sm" }), - diagnosticSourceHighlight: { color: foreground(layer, "accent") }, - highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better - } -} diff --git a/styles/src/styleTree/incomingCallNotification.ts b/styles/src/styleTree/incomingCallNotification.ts deleted file mode 100644 index c42558059c..0000000000 --- a/styles/src/styleTree/incomingCallNotification.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, text } from "./components" - -export default function incomingCallNotification( - colorScheme: ColorScheme -): Object { - let layer = colorScheme.middle - const avatarSize = 48 - return { - windowHeight: 74, - windowWidth: 380, - background: background(layer), - callerContainer: { - padding: 12, - }, - callerAvatar: { - height: avatarSize, - width: avatarSize, - cornerRadius: avatarSize / 2, - }, - callerMetadata: { - margin: { left: 10 }, - }, - callerUsername: { - ...text(layer, "sans", { size: "sm", weight: "bold" }), - margin: { top: -3 }, - }, - callerMessage: { - ...text(layer, "sans", "variant", { size: "xs" }), - margin: { top: -3 }, - }, - worktreeRoots: { - ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), - margin: { top: -3 }, - }, - buttonWidth: 96, - acceptButton: { - background: background(layer, "accent"), - border: border(layer, { left: true, bottom: true }), - ...text(layer, "sans", "positive", { - size: "xs", - weight: "extra_bold", - }), - }, - declineButton: { - border: border(layer, { left: true }), - ...text(layer, "sans", "negative", { - size: "xs", - weight: "extra_bold", - }), - }, - } -} diff --git a/styles/src/styleTree/projectDiagnostics.ts b/styles/src/styleTree/projectDiagnostics.ts deleted file mode 100644 index cf0f07dd8c..0000000000 --- a/styles/src/styleTree/projectDiagnostics.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, text } from "./components" - -export default function projectDiagnostics(colorScheme: ColorScheme) { - let layer = colorScheme.highest - return { - background: background(layer), - tabIconSpacing: 4, - tabIconWidth: 13, - tabSummarySpacing: 10, - emptyMessage: text(layer, "sans", "variant", { size: "md" }), - } -} diff --git a/styles/src/styleTree/projectPanel.ts b/styles/src/styleTree/projectPanel.ts deleted file mode 100644 index 3727c1916c..0000000000 --- a/styles/src/styleTree/projectPanel.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { withOpacity } from "../theme/color" -import { - Border, - TextStyle, - background, - border, - foreground, - text, -} from "./components" -import { interactive, toggleable } from "../element" -import merge from "ts-deepmerge" -export default function projectPanel(colorScheme: ColorScheme) { - const { isLight } = colorScheme - - let layer = colorScheme.middle - - type EntryStateProps = { - background?: string - border?: Border - text?: TextStyle - iconColor?: string - } - - type EntryState = { - default: EntryStateProps - hovered?: EntryStateProps - clicked?: EntryStateProps - } - - const entry = (unselected?: EntryState, selected?: EntryState) => { - const git_status = { - git: { - modified: isLight - ? colorScheme.ramps.yellow(0.6).hex() - : colorScheme.ramps.yellow(0.5).hex(), - inserted: isLight - ? colorScheme.ramps.green(0.45).hex() - : colorScheme.ramps.green(0.5).hex(), - conflict: isLight - ? colorScheme.ramps.red(0.6).hex() - : colorScheme.ramps.red(0.5).hex(), - }, - } - - const base_properties = { - height: 22, - background: background(layer), - iconColor: foreground(layer, "variant"), - iconSize: 7, - iconSpacing: 5, - text: text(layer, "mono", "variant", { size: "sm" }), - status: { - ...git_status, - }, - } - - const selectedStyle: EntryState | undefined = selected - ? selected - : unselected - - const unselected_default_style = merge( - base_properties, - unselected?.default ?? {}, - {} - ) - const unselected_hovered_style = merge( - base_properties, - unselected?.hovered ?? {}, - { background: background(layer, "variant", "hovered") } - ) - const unselected_clicked_style = merge( - base_properties, - unselected?.clicked ?? {}, - { background: background(layer, "variant", "pressed") } - ) - const selected_default_style = merge( - base_properties, - selectedStyle?.default ?? {}, - { background: background(layer) } - ) - const selected_hovered_style = merge( - base_properties, - selectedStyle?.hovered ?? {}, - { background: background(layer, "variant", "hovered") } - ) - const selected_clicked_style = merge( - base_properties, - selectedStyle?.clicked ?? {}, - { background: background(layer, "variant", "pressed") } - ) - - return toggleable({ - state: { - inactive: interactive({ - state: { - default: unselected_default_style, - hovered: unselected_hovered_style, - clicked: unselected_clicked_style, - }, - }), - active: interactive({ - state: { - default: selected_default_style, - hovered: selected_hovered_style, - clicked: selected_clicked_style, - }, - }), - }, - }) - } - - const defaultEntry = entry() - - return { - openProjectButton: interactive({ - base: { - background: background(layer), - border: border(layer, "active"), - cornerRadius: 4, - margin: { - top: 16, - left: 16, - right: 16, - }, - padding: { - top: 3, - bottom: 3, - left: 7, - right: 7, - }, - ...text(layer, "sans", "default", { size: "sm" }), - }, - state: { - hovered: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "hovered"), - border: border(layer, "active"), - }, - clicked: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "pressed"), - border: border(layer, "active"), - }, - }, - }), - background: background(layer), - padding: { left: 6, right: 6, top: 0, bottom: 6 }, - indentWidth: 12, - entry: defaultEntry, - draggedEntry: { - ...defaultEntry.inactive.default, - text: text(layer, "mono", "on", { size: "sm" }), - background: withOpacity(background(layer, "on"), 0.9), - border: border(layer), - }, - ignoredEntry: entry( - { - default: { - text: text(layer, "mono", "disabled"), - }, - }, - { - default: { - iconColor: foreground(layer, "variant"), - }, - } - ), - cutEntry: entry( - { - default: { - text: text(layer, "mono", "disabled"), - }, - }, - { - default: { - background: background(layer, "active"), - text: text(layer, "mono", "disabled", { size: "sm" }), - }, - } - ), - filenameEditor: { - background: background(layer, "on"), - text: text(layer, "mono", "on", { size: "sm" }), - selection: colorScheme.players[0], - }, - } -} diff --git a/styles/src/styleTree/projectSharedNotification.ts b/styles/src/styleTree/projectSharedNotification.ts deleted file mode 100644 index d05eb1b0c5..0000000000 --- a/styles/src/styleTree/projectSharedNotification.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, text } from "./components" - -export default function projectSharedNotification( - colorScheme: ColorScheme -): Object { - let layer = colorScheme.middle - - const avatarSize = 48 - return { - windowHeight: 74, - windowWidth: 380, - background: background(layer), - ownerContainer: { - padding: 12, - }, - ownerAvatar: { - height: avatarSize, - width: avatarSize, - cornerRadius: avatarSize / 2, - }, - ownerMetadata: { - margin: { left: 10 }, - }, - ownerUsername: { - ...text(layer, "sans", { size: "sm", weight: "bold" }), - margin: { top: -3 }, - }, - message: { - ...text(layer, "sans", "variant", { size: "xs" }), - margin: { top: -3 }, - }, - worktreeRoots: { - ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }), - margin: { top: -3 }, - }, - buttonWidth: 96, - openButton: { - background: background(layer, "accent"), - border: border(layer, { left: true, bottom: true }), - ...text(layer, "sans", "accent", { - size: "xs", - weight: "extra_bold", - }), - }, - dismissButton: { - border: border(layer, { left: true }), - ...text(layer, "sans", "variant", { - size: "xs", - weight: "extra_bold", - }), - }, - } -} diff --git a/styles/src/styleTree/search.ts b/styles/src/styleTree/search.ts deleted file mode 100644 index b471e6cbda..0000000000 --- a/styles/src/styleTree/search.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { withOpacity } from "../theme/color" -import { background, border, foreground, text } from "./components" -import { interactive, toggleable } from "../element" - -export default function search(colorScheme: ColorScheme) { - let layer = colorScheme.highest - - // Search input - const editor = { - background: background(layer), - cornerRadius: 8, - minWidth: 200, - maxWidth: 500, - placeholderText: text(layer, "mono", "disabled"), - selection: colorScheme.players[0], - text: text(layer, "mono", "default"), - border: border(layer), - margin: { - right: 12, - }, - padding: { - top: 3, - bottom: 3, - left: 12, - right: 8, - }, - } - - const includeExcludeEditor = { - ...editor, - minWidth: 100, - maxWidth: 250, - } - - return { - // TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive - matchBackground: withOpacity(foreground(layer, "accent"), 0.4), - optionButton: toggleable({ - base: interactive({ - base: { - ...text(layer, "mono", "on"), - background: background(layer, "on"), - cornerRadius: 6, - border: border(layer, "on"), - margin: { - right: 4, - }, - padding: { - bottom: 2, - left: 10, - right: 10, - top: 2, - }, - }, - state: { - hovered: { - ...text(layer, "mono", "on", "hovered"), - background: background(layer, "on", "hovered"), - border: border(layer, "on", "hovered"), - }, - clicked: { - ...text(layer, "mono", "on", "pressed"), - background: background(layer, "on", "pressed"), - border: border(layer, "on", "pressed"), - }, - }, - }), - state: { - active: { - default: { - ...text(layer, "mono", "accent"), - }, - hovered: { - ...text(layer, "mono", "accent", "hovered"), - }, - clicked: { - ...text(layer, "mono", "accent", "pressed"), - }, - }, - }, - }), - editor, - invalidEditor: { - ...editor, - border: border(layer, "negative"), - }, - includeExcludeEditor, - invalidIncludeExcludeEditor: { - ...includeExcludeEditor, - border: border(layer, "negative"), - }, - matchIndex: { - ...text(layer, "mono", "variant"), - padding: { - left: 6, - }, - }, - optionButtonGroup: { - padding: { - left: 12, - right: 12, - }, - }, - includeExcludeInputs: { - ...text(layer, "mono", "variant"), - padding: { - right: 6, - }, - }, - resultsStatus: { - ...text(layer, "mono", "on"), - size: 18, - }, - dismissButton: interactive({ - base: { - color: foreground(layer, "variant"), - iconWidth: 12, - buttonWidth: 14, - padding: { - left: 10, - right: 10, - }, - }, - state: { - hovered: { - color: foreground(layer, "hovered"), - }, - clicked: { - color: foreground(layer, "pressed"), - }, - }, - }), - } -} diff --git a/styles/src/styleTree/sharedScreen.ts b/styles/src/styleTree/sharedScreen.ts deleted file mode 100644 index 2563e718ff..0000000000 --- a/styles/src/styleTree/sharedScreen.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background } from "./components" - -export default function sharedScreen(colorScheme: ColorScheme) { - let layer = colorScheme.highest - return { - background: background(layer), - } -} diff --git a/styles/src/styleTree/simpleMessageNotification.ts b/styles/src/styleTree/simpleMessageNotification.ts deleted file mode 100644 index e894db3514..0000000000 --- a/styles/src/styleTree/simpleMessageNotification.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, foreground, text } from "./components" -import { interactive } from "../element" - -const headerPadding = 8 - -export default function simpleMessageNotification( - colorScheme: ColorScheme -): Object { - let layer = colorScheme.middle - return { - message: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, - }, - actionMessage: interactive({ - base: { - ...text(layer, "sans", { size: "xs" }), - border: border(layer, "active"), - cornerRadius: 4, - padding: { - top: 3, - bottom: 3, - left: 7, - right: 7, - }, - - margin: { left: headerPadding, top: 6, bottom: 6 }, - }, - state: { - hovered: { - ...text(layer, "sans", "default", { size: "xs" }), - background: background(layer, "hovered"), - border: border(layer, "active"), - }, - }, - }), - dismissButton: interactive({ - base: { - color: foreground(layer), - iconWidth: 8, - iconHeight: 8, - buttonWidth: 8, - buttonHeight: 8, - }, - state: { - hovered: { - color: foreground(layer, "hovered"), - }, - }, - }), - } -} diff --git a/styles/src/styleTree/terminal.ts b/styles/src/styleTree/terminal.ts deleted file mode 100644 index 8a7eb7a549..0000000000 --- a/styles/src/styleTree/terminal.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" - -export default function terminal(colorScheme: ColorScheme) { - /** - * Colors are controlled per-cell in the terminal grid. - * Cells can be set to any of these more 'theme-capable' colors - * or can be set directly with RGB values. - * Here are the common interpretations of these names: - * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors - */ - return { - black: colorScheme.ramps.neutral(0).hex(), - red: colorScheme.ramps.red(0.5).hex(), - green: colorScheme.ramps.green(0.5).hex(), - yellow: colorScheme.ramps.yellow(0.5).hex(), - blue: colorScheme.ramps.blue(0.5).hex(), - magenta: colorScheme.ramps.magenta(0.5).hex(), - cyan: colorScheme.ramps.cyan(0.5).hex(), - white: colorScheme.ramps.neutral(1).hex(), - brightBlack: colorScheme.ramps.neutral(0.4).hex(), - brightRed: colorScheme.ramps.red(0.25).hex(), - brightGreen: colorScheme.ramps.green(0.25).hex(), - brightYellow: colorScheme.ramps.yellow(0.25).hex(), - brightBlue: colorScheme.ramps.blue(0.25).hex(), - brightMagenta: colorScheme.ramps.magenta(0.25).hex(), - brightCyan: colorScheme.ramps.cyan(0.25).hex(), - brightWhite: colorScheme.ramps.neutral(1).hex(), - /** - * Default color for characters - */ - foreground: colorScheme.ramps.neutral(1).hex(), - /** - * Default color for the rectangle background of a cell - */ - background: colorScheme.ramps.neutral(0).hex(), - modalBackground: colorScheme.ramps.neutral(0.1).hex(), - /** - * Default color for the cursor - */ - cursor: colorScheme.players[0].cursor, - dimBlack: colorScheme.ramps.neutral(1).hex(), - dimRed: colorScheme.ramps.red(0.75).hex(), - dimGreen: colorScheme.ramps.green(0.75).hex(), - dimYellow: colorScheme.ramps.yellow(0.75).hex(), - dimBlue: colorScheme.ramps.blue(0.75).hex(), - dimMagenta: colorScheme.ramps.magenta(0.75).hex(), - dimCyan: colorScheme.ramps.cyan(0.75).hex(), - dimWhite: colorScheme.ramps.neutral(0.6).hex(), - brightForeground: colorScheme.ramps.neutral(1).hex(), - dimForeground: colorScheme.ramps.neutral(0).hex(), - } -} diff --git a/styles/src/styleTree/toggle.ts b/styles/src/styleTree/toggle.ts deleted file mode 100644 index 2b6858e15b..0000000000 --- a/styles/src/styleTree/toggle.ts +++ /dev/null @@ -1,47 +0,0 @@ -import merge from "ts-deepmerge" - -type ToggleState = "inactive" | "active" - -type Toggleable = Record - -const NO_INACTIVE_OR_BASE_ERROR = - "A toggleable object must have an inactive state, or a base property." -const NO_ACTIVE_ERROR = "A toggleable object must have an active state." - -interface ToggleableProps { - base?: T - state: Partial> -} - -/** - * Helper function for creating Toggleable objects. - * @template T The type of the object being toggled. - * @param props Object containing the base (inactive) state and state modifications to create the active state. - * @returns A Toggleable object containing both the inactive and active states. - * @example - * ``` - * toggleable({ - * base: { background: "#000000", text: "#CCCCCC" }, - * state: { active: { text: "#CCCCCC" } }, - * }) - * ``` - */ -export function toggleable( - props: ToggleableProps -): Toggleable { - const { base, state } = props - - if (!base && !state.inactive) throw new Error(NO_INACTIVE_OR_BASE_ERROR) - if (!state.active) throw new Error(NO_ACTIVE_ERROR) - - const inactiveState = base - ? ((state.inactive ? merge(base, state.inactive) : base) as T) - : (state.inactive as T) - - const toggleObj: Toggleable = { - inactive: inactiveState, - active: merge(base ?? {}, state.active) as T, - } - - return toggleObj -} diff --git a/styles/src/styleTree/toolbarDropdownMenu.ts b/styles/src/styleTree/toolbarDropdownMenu.ts deleted file mode 100644 index d82e5f1cde..0000000000 --- a/styles/src/styleTree/toolbarDropdownMenu.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, text } from "./components" -import { interactive, toggleable } from "../element" -export default function dropdownMenu(colorScheme: ColorScheme) { - let layer = colorScheme.middle - - return { - rowHeight: 30, - background: background(layer), - border: border(layer), - shadow: colorScheme.popoverShadow, - header: interactive({ - base: { - ...text(layer, "sans", { size: "sm" }), - secondaryText: text(layer, "sans", { - size: "sm", - color: "#aaaaaa", - }), - secondaryTextSpacing: 10, - padding: { left: 8, right: 8, top: 2, bottom: 2 }, - cornerRadius: 6, - background: background(layer, "on"), - }, - state: { - hovered: { - background: background(layer, "hovered"), - }, - clicked: { - background: background(layer, "pressed"), - }, - }, - }), - sectionHeader: { - ...text(layer, "sans", { size: "sm" }), - padding: { left: 8, right: 8, top: 8, bottom: 8 }, - }, - item: toggleable({ - base: interactive({ - base: { - ...text(layer, "sans", { size: "sm" }), - secondaryTextSpacing: 10, - secondaryText: text(layer, "sans", { size: "sm" }), - padding: { left: 18, right: 18, top: 2, bottom: 2 }, - }, - state: { - hovered: { - background: background(layer, "hovered"), - ...text(layer, "sans", "hovered", { size: "sm" }), - }, - }, - }), - state: { - active: { - default: { - background: background(layer, "active"), - }, - hovered: { - background: background(layer, "hovered"), - }, - }, - }, - }), - } -} diff --git a/styles/src/styleTree/tooltip.ts b/styles/src/styleTree/tooltip.ts deleted file mode 100644 index 1666ce5658..0000000000 --- a/styles/src/styleTree/tooltip.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, text } from "./components" - -export default function tooltip(colorScheme: ColorScheme) { - let layer = colorScheme.middle - return { - background: background(layer), - border: border(layer), - padding: { top: 4, bottom: 4, left: 8, right: 8 }, - margin: { top: 6, left: 6 }, - shadow: colorScheme.popoverShadow, - cornerRadius: 6, - text: text(layer, "sans", { size: "xs" }), - keystroke: { - background: background(layer, "on"), - cornerRadius: 4, - margin: { left: 6 }, - padding: { left: 4, right: 4 }, - ...text(layer, "mono", "on", { size: "xs", weight: "bold" }), - }, - maxTextWidth: 200, - } -} diff --git a/styles/src/styleTree/updateNotification.ts b/styles/src/styleTree/updateNotification.ts deleted file mode 100644 index c6ef81c667..0000000000 --- a/styles/src/styleTree/updateNotification.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { foreground, text } from "./components" -import { interactive } from "../element" - -const headerPadding = 8 - -export default function updateNotification(colorScheme: ColorScheme): Object { - let layer = colorScheme.middle - return { - message: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, right: headerPadding }, - }, - actionMessage: interactive({ - base: { - ...text(layer, "sans", { size: "xs" }), - margin: { left: headerPadding, top: 6, bottom: 6 }, - }, - state: { - hovered: { - color: foreground(layer, "hovered"), - }, - }, - }), - dismissButton: interactive({ - base: { - color: foreground(layer), - iconWidth: 8, - iconHeight: 8, - buttonWidth: 8, - buttonHeight: 8, - }, - state: { - hovered: { - color: foreground(layer, "hovered"), - }, - }, - }), - } -} diff --git a/styles/src/styleTree/welcome.ts b/styles/src/styleTree/welcome.ts deleted file mode 100644 index 311ff6daff..0000000000 --- a/styles/src/styleTree/welcome.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { withOpacity } from "../theme/color" -import { - border, - background, - foreground, - text, - TextProperties, - svg, -} from "./components" -import { interactive } from "../element" - -export default function welcome(colorScheme: ColorScheme) { - let layer = colorScheme.highest - - let checkboxBase = { - cornerRadius: 4, - padding: { - left: 3, - right: 3, - top: 3, - bottom: 3, - }, - // shadow: colorScheme.popoverShadow, - border: border(layer), - margin: { - right: 8, - top: 5, - bottom: 5, - }, - } - - let interactive_text_size: TextProperties = { size: "sm" } - - return { - pageWidth: 320, - logo: svg(foreground(layer, "default"), "icons/logo_96.svg", 64, 64), - logoSubheading: { - ...text(layer, "sans", "variant", { size: "md" }), - margin: { - top: 10, - bottom: 7, - }, - }, - buttonGroup: { - margin: { - top: 8, - bottom: 16, - }, - }, - headingGroup: { - margin: { - top: 8, - bottom: 12, - }, - }, - checkboxGroup: { - border: border(layer, "variant"), - background: withOpacity(background(layer, "hovered"), 0.25), - cornerRadius: 4, - padding: { - left: 12, - top: 2, - bottom: 2, - }, - }, - button: interactive({ - base: { - background: background(layer), - border: border(layer, "active"), - cornerRadius: 4, - margin: { - top: 4, - bottom: 4, - }, - padding: { - top: 3, - bottom: 3, - left: 7, - right: 7, - }, - ...text(layer, "sans", "default", interactive_text_size), - }, - state: { - hovered: { - ...text(layer, "sans", "default", interactive_text_size), - background: background(layer, "hovered"), - }, - }, - }), - - usageNote: { - ...text(layer, "sans", "variant", { size: "2xs" }), - padding: { - top: -4, - }, - }, - checkboxContainer: { - margin: { - top: 4, - }, - padding: { - bottom: 8, - }, - }, - checkbox: { - label: { - ...text(layer, "sans", interactive_text_size), - // Also supports margin, container, border, etc. - }, - icon: svg(foreground(layer, "on"), "icons/check_12.svg", 12, 12), - default: { - ...checkboxBase, - background: background(layer, "default"), - border: border(layer, "active"), - }, - checked: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active"), - }, - hovered: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active"), - }, - hoveredAndChecked: { - ...checkboxBase, - background: background(layer, "hovered"), - border: border(layer, "active"), - }, - }, - } -} diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts deleted file mode 100644 index afc2ea4d98..0000000000 --- a/styles/src/styleTree/workspace.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { ColorScheme } from "../theme/colorScheme" -import { withOpacity } from "../theme/color" -import { - background, - border, - borderColor, - foreground, - svg, - text, -} from "./components" -import statusBar from "./statusBar" -import tabBar from "./tabBar" -import { interactive } from "../element" - -import { titlebar } from "./titlebar" -export default function workspace(colorScheme: ColorScheme) { - const layer = colorScheme.lowest - const isLight = colorScheme.isLight - - return { - background: background(colorScheme.lowest), - blankPane: { - logoContainer: { - width: 256, - height: 256, - }, - logo: svg( - withOpacity("#000000", colorScheme.isLight ? 0.6 : 0.8), - "icons/logo_96.svg", - 256, - 256 - ), - - logoShadow: svg( - withOpacity( - colorScheme.isLight - ? "#FFFFFF" - : colorScheme.lowest.base.default.background, - colorScheme.isLight ? 1 : 0.6 - ), - "icons/logo_96.svg", - 256, - 256 - ), - keyboardHints: { - margin: { - top: 96, - }, - cornerRadius: 4, - }, - keyboardHint: interactive({ - base: { - ...text(layer, "sans", "variant", { size: "sm" }), - padding: { - top: 3, - left: 8, - right: 8, - bottom: 3, - }, - cornerRadius: 8, - }, - state: { - hovered: { - ...text(layer, "sans", "active", { size: "sm" }), - }, - }, - }), - - keyboardHintWidth: 320, - }, - joiningProjectAvatar: { - cornerRadius: 40, - width: 80, - }, - joiningProjectMessage: { - padding: 12, - ...text(layer, "sans", { size: "lg" }), - }, - externalLocationMessage: { - background: background(colorScheme.middle, "accent"), - border: border(colorScheme.middle, "accent"), - cornerRadius: 6, - padding: 12, - margin: { bottom: 8, right: 8 }, - ...text(colorScheme.middle, "sans", "accent", { size: "xs" }), - }, - leaderBorderOpacity: 0.7, - leaderBorderWidth: 2.0, - tabBar: tabBar(colorScheme), - modal: { - margin: { - bottom: 52, - top: 52, - }, - cursor: "Arrow", - }, - zoomedBackground: { - cursor: "Arrow", - background: isLight - ? withOpacity(background(colorScheme.lowest), 0.8) - : withOpacity(background(colorScheme.highest), 0.6), - }, - zoomedPaneForeground: { - margin: 16, - shadow: colorScheme.modalShadow, - border: border(colorScheme.lowest, { overlay: true }), - }, - zoomedPanelForeground: { - margin: 16, - border: border(colorScheme.lowest, { overlay: true }), - }, - dock: { - left: { - border: border(layer, { right: true }), - }, - bottom: { - border: border(layer, { top: true }), - }, - right: { - border: border(layer, { left: true }), - }, - }, - paneDivider: { - color: borderColor(layer), - width: 1, - }, - statusBar: statusBar(colorScheme), - titlebar: titlebar(colorScheme), - toolbar: { - height: 34, - background: background(colorScheme.highest), - border: border(colorScheme.highest, { bottom: true }), - itemSpacing: 8, - navButton: interactive({ - base: { - color: foreground(colorScheme.highest, "on"), - iconWidth: 12, - buttonWidth: 24, - cornerRadius: 6, - }, - state: { - hovered: { - color: foreground(colorScheme.highest, "on", "hovered"), - background: background( - colorScheme.highest, - "on", - "hovered" - ), - }, - disabled: { - color: foreground( - colorScheme.highest, - "on", - "disabled" - ), - }, - }, - }), - padding: { left: 8, right: 8, top: 4, bottom: 4 }, - }, - breadcrumbHeight: 24, - breadcrumbs: interactive({ - base: { - ...text(colorScheme.highest, "sans", "variant"), - cornerRadius: 6, - padding: { - left: 6, - right: 6, - }, - }, - state: { - hovered: { - color: foreground(colorScheme.highest, "on", "hovered"), - background: background( - colorScheme.highest, - "on", - "hovered" - ), - }, - }, - }), - disconnectedOverlay: { - ...text(layer, "sans"), - background: withOpacity(background(layer), 0.8), - }, - notification: { - margin: { top: 10 }, - background: background(colorScheme.middle), - cornerRadius: 6, - padding: 12, - border: border(colorScheme.middle), - shadow: colorScheme.popoverShadow, - }, - notifications: { - width: 400, - margin: { right: 10, bottom: 10 }, - }, - dropTargetOverlayColor: withOpacity(foreground(layer, "variant"), 0.5), - } -} diff --git a/styles/src/style_tree/app.ts b/styles/src/style_tree/app.ts new file mode 100644 index 0000000000..684614d073 --- /dev/null +++ b/styles/src/style_tree/app.ts @@ -0,0 +1,75 @@ +import contact_finder from "./contact_finder" +import contacts_popover from "./contacts_popover" +import command_palette from "./command_palette" +import project_panel from "./project_panel" +import search from "./search" +import picker from "./picker" +import workspace from "./workspace" +import context_menu from "./context_menu" +import shared_screen from "./shared_screen" +import project_diagnostics from "./project_diagnostics" +import contact_notification from "./contact_notification" +import update_notification from "./update_notification" +import simple_message_notification from "./simple_message_notification" +import project_shared_notification from "./project_shared_notification" +import tooltip from "./tooltip" +import terminal from "./terminal" +import contact_list from "./contact_list" +import toolbar_dropdown_menu from "./toolbar_dropdown_menu" +import incoming_call_notification from "./incoming_call_notification" +import { ColorScheme } from "../theme/color_scheme" +import welcome from "./welcome" +import copilot from "./copilot" +import assistant from "./assistant" +import { titlebar } from "./titlebar" +import editor from "./editor" +import feedback from "./feedback" + +export default function app(theme: ColorScheme): any { + return { + meta: { + name: theme.name, + is_light: theme.is_light, + }, + command_palette: command_palette(theme), + contact_notification: contact_notification(theme), + project_shared_notification: project_shared_notification(theme), + incoming_call_notification: incoming_call_notification(theme), + picker: picker(theme), + workspace: workspace(theme), + titlebar: titlebar(theme), + copilot: copilot(theme), + welcome: welcome(theme), + context_menu: context_menu(theme), + editor: editor(theme), + project_diagnostics: project_diagnostics(theme), + project_panel: project_panel(theme), + contacts_popover: contacts_popover(theme), + contact_finder: contact_finder(theme), + contact_list: contact_list(theme), + toolbar_dropdown_menu: toolbar_dropdown_menu(theme), + search: search(theme), + shared_screen: shared_screen(theme), + update_notification: update_notification(theme), + simple_message_notification: simple_message_notification(theme), + tooltip: tooltip(theme), + terminal: terminal(theme), + assistant: assistant(theme), + feedback: feedback(theme), + color_scheme: { + ...theme, + players: Object.values(theme.players), + ramps: { + neutral: theme.ramps.neutral.colors(100, "hex"), + red: theme.ramps.red.colors(100, "hex"), + orange: theme.ramps.orange.colors(100, "hex"), + yellow: theme.ramps.yellow.colors(100, "hex"), + green: theme.ramps.green.colors(100, "hex"), + cyan: theme.ramps.cyan.colors(100, "hex"), + blue: theme.ramps.blue.colors(100, "hex"), + violet: theme.ramps.violet.colors(100, "hex"), + magenta: theme.ramps.magenta.colors(100, "hex"), + }, + }, + } +} diff --git a/styles/src/styleTree/assistant.ts b/styles/src/style_tree/assistant.ts similarity index 61% rename from styles/src/styleTree/assistant.ts rename to styles/src/style_tree/assistant.ts index 30e109df1a..bdde221aca 100644 --- a/styles/src/styleTree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -1,23 +1,21 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { text, border, background, foreground } from "./components" -import editor from "./editor" import { interactive } from "../element" -export default function assistant(colorScheme: ColorScheme) { - const layer = colorScheme.highest +export default function assistant(theme: ColorScheme): any { return { container: { - background: editor(colorScheme).background, + background: background(theme.highest), padding: { left: 12 }, }, - messageHeader: { + message_header: { margin: { bottom: 6, top: 6 }, - background: editor(colorScheme).background, + background: background(theme.highest), }, - hamburgerButton: interactive({ + hamburger_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/hamburger_15.svg", dimensions: { width: 15, @@ -31,15 +29,15 @@ export default function assistant(colorScheme: ColorScheme) { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), - splitButton: interactive({ + split_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/split_message_15.svg", dimensions: { width: 15, @@ -53,15 +51,15 @@ export default function assistant(colorScheme: ColorScheme) { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), - quoteButton: interactive({ + quote_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/quote_15.svg", dimensions: { width: 15, @@ -75,15 +73,15 @@ export default function assistant(colorScheme: ColorScheme) { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), - assistButton: interactive({ + assist_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/assist_15.svg", dimensions: { width: 15, @@ -97,15 +95,15 @@ export default function assistant(colorScheme: ColorScheme) { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), - zoomInButton: interactive({ + zoom_in_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/maximize_8.svg", dimensions: { width: 12, @@ -119,15 +117,15 @@ export default function assistant(colorScheme: ColorScheme) { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), - zoomOutButton: interactive({ + zoom_out_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/minimize_8.svg", dimensions: { width: 12, @@ -141,15 +139,15 @@ export default function assistant(colorScheme: ColorScheme) { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), - plusButton: interactive({ + plus_button: interactive({ base: { icon: { - color: foreground(layer, "variant"), + color: foreground(theme.highest, "variant"), asset: "icons/plus_12.svg", dimensions: { width: 12, @@ -163,109 +161,109 @@ export default function assistant(colorScheme: ColorScheme) { state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.highest, "hovered"), }, }, }, }), title: { - ...text(layer, "sans", "default", { size: "sm" }), + ...text(theme.highest, "sans", "default", { size: "sm" }), }, - savedConversation: { + saved_conversation: { container: interactive({ base: { - background: background(layer, "on"), + background: background(theme.highest, "on"), padding: { top: 4, bottom: 4 }, }, state: { hovered: { - background: background(layer, "on", "hovered"), + background: background(theme.highest, "on", "hovered"), }, }, }), - savedAt: { + saved_at: { margin: { left: 8 }, - ...text(layer, "sans", "default", { size: "xs" }), + ...text(theme.highest, "sans", "default", { size: "xs" }), }, title: { margin: { left: 16 }, - ...text(layer, "sans", "default", { + ...text(theme.highest, "sans", "default", { size: "sm", weight: "bold", }), }, }, - userSender: { + user_sender: { default: { - ...text(layer, "sans", "default", { + ...text(theme.highest, "sans", "default", { size: "sm", weight: "bold", }), }, }, - assistantSender: { + assistant_sender: { default: { - ...text(layer, "sans", "accent", { + ...text(theme.highest, "sans", "accent", { size: "sm", weight: "bold", }), }, }, - systemSender: { + system_sender: { default: { - ...text(layer, "sans", "variant", { + ...text(theme.highest, "sans", "variant", { size: "sm", weight: "bold", }), }, }, - sentAt: { + sent_at: { margin: { top: 2, left: 8 }, - ...text(layer, "sans", "default", { size: "2xs" }), + ...text(theme.highest, "sans", "default", { size: "2xs" }), }, model: interactive({ base: { - background: background(layer, "on"), + background: background(theme.highest, "on"), margin: { left: 12, right: 12, top: 12 }, padding: 4, - cornerRadius: 4, - ...text(layer, "sans", "default", { size: "xs" }), + corner_radius: 4, + ...text(theme.highest, "sans", "default", { size: "xs" }), }, state: { hovered: { - background: background(layer, "on", "hovered"), - border: border(layer, "on", { overlay: true }), + background: background(theme.highest, "on", "hovered"), + border: border(theme.highest, "on", { overlay: true }), }, }, }), - remainingTokens: { - background: background(layer, "on"), + remaining_tokens: { + background: background(theme.highest, "on"), margin: { top: 12, right: 24 }, padding: 4, - cornerRadius: 4, - ...text(layer, "sans", "positive", { size: "xs" }), + corner_radius: 4, + ...text(theme.highest, "sans", "positive", { size: "xs" }), }, - noRemainingTokens: { - background: background(layer, "on"), + no_remaining_tokens: { + background: background(theme.highest, "on"), margin: { top: 12, right: 24 }, padding: 4, - cornerRadius: 4, - ...text(layer, "sans", "negative", { size: "xs" }), + corner_radius: 4, + ...text(theme.highest, "sans", "negative", { size: "xs" }), }, - errorIcon: { + error_icon: { margin: { left: 8 }, - color: foreground(layer, "negative"), + color: foreground(theme.highest, "negative"), width: 12, }, - apiKeyEditor: { - background: background(layer, "on"), - cornerRadius: 6, - text: text(layer, "mono", "on"), - placeholderText: text(layer, "mono", "on", "disabled", { + api_key_editor: { + background: background(theme.highest, "on"), + corner_radius: 6, + text: text(theme.highest, "mono", "on"), + placeholder_text: text(theme.highest, "mono", "on", "disabled", { size: "xs", }), - selection: colorScheme.players[0], - border: border(layer, "on"), + selection: theme.players[0], + border: border(theme.highest, "on"), padding: { bottom: 4, left: 8, @@ -273,9 +271,9 @@ export default function assistant(colorScheme: ColorScheme) { top: 4, }, }, - apiKeyPrompt: { + api_key_prompt: { padding: 10, - ...text(layer, "sans", "default", { size: "xs" }), + ...text(theme.highest, "sans", "default", { size: "xs" }), }, } } diff --git a/styles/src/styleTree/commandPalette.ts b/styles/src/style_tree/command_palette.ts similarity index 53% rename from styles/src/styleTree/commandPalette.ts rename to styles/src/style_tree/command_palette.ts index 5d4b7373c3..289deef54b 100644 --- a/styles/src/styleTree/commandPalette.ts +++ b/styles/src/style_tree/command_palette.ts @@ -1,16 +1,16 @@ -import { ColorScheme } from "../theme/colorScheme" -import { withOpacity } from "../theme/color" +import { ColorScheme } from "../theme/color_scheme" +import { with_opacity } from "../theme/color" import { text, background } from "./components" import { toggleable } from "../element" -export default function commandPalette(colorScheme: ColorScheme) { - let layer = colorScheme.highest - +export default function command_palette(theme: ColorScheme): any { const key = toggleable({ base: { - text: text(layer, "mono", "variant", "default", { size: "xs" }), - cornerRadius: 2, - background: background(layer, "on"), + text: text(theme.highest, "mono", "variant", "default", { + size: "xs", + }), + corner_radius: 2, + background: background(theme.highest, "on"), padding: { top: 1, bottom: 1, @@ -25,14 +25,16 @@ export default function commandPalette(colorScheme: ColorScheme) { }, state: { active: { - text: text(layer, "mono", "on", "default", { size: "xs" }), - background: withOpacity(background(layer, "on"), 0.2), + text: text(theme.highest, "mono", "on", "default", { + size: "xs", + }), + background: with_opacity(background(theme.highest, "on"), 0.2), }, }, }) return { - keystrokeSpacing: 8, + keystroke_spacing: 8, // TODO: This should be a Toggle on the rust side so we don't have to do this key: { inactive: { ...key.inactive }, diff --git a/styles/src/styleTree/components.ts b/styles/src/style_tree/components.ts similarity index 66% rename from styles/src/styleTree/components.ts rename to styles/src/style_tree/components.ts index 3b6f26066b..db32712f41 100644 --- a/styles/src/styleTree/components.ts +++ b/styles/src/style_tree/components.ts @@ -1,7 +1,7 @@ -import { fontFamilies, fontSizes, FontWeight } from "../common" -import { Layer, Styles, StyleSets, Style } from "../theme/colorScheme" +import { font_families, font_sizes, FontWeight } from "../common" +import { Layer, Styles, StyleSets, Style } from "../theme/color_scheme" -function isStyleSet(key: any): key is StyleSets { +function is_style_set(key: any): key is StyleSets { return [ "base", "variant", @@ -13,7 +13,7 @@ function isStyleSet(key: any): key is StyleSets { ].includes(key) } -function isStyle(key: any): key is Styles { +function is_style(key: any): key is Styles { return [ "default", "active", @@ -23,78 +23,70 @@ function isStyle(key: any): key is Styles { "inverted", ].includes(key) } -function getStyle( +function get_style( layer: Layer, - possibleStyleSetOrStyle?: any, - possibleStyle?: any + possible_style_set_or_style?: any, + possible_style?: any ): Style { - let styleSet: StyleSets = "base" + let style_set: StyleSets = "base" let style: Styles = "default" - if (isStyleSet(possibleStyleSetOrStyle)) { - styleSet = possibleStyleSetOrStyle - } else if (isStyle(possibleStyleSetOrStyle)) { - style = possibleStyleSetOrStyle + if (is_style_set(possible_style_set_or_style)) { + style_set = possible_style_set_or_style + } else if (is_style(possible_style_set_or_style)) { + style = possible_style_set_or_style } - if (isStyle(possibleStyle)) { - style = possibleStyle + if (is_style(possible_style)) { + style = possible_style } - return layer[styleSet][style] + return layer[style_set][style] } export function background(layer: Layer, style?: Styles): string export function background( layer: Layer, - styleSet?: StyleSets, + style_set?: StyleSets, style?: Styles ): string export function background( layer: Layer, - styleSetOrStyles?: StyleSets | Styles, + style_set_or_styles?: StyleSets | Styles, style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).background + return get_style(layer, style_set_or_styles, style).background } -export function borderColor(layer: Layer, style?: Styles): string -export function borderColor( +export function border_color(layer: Layer, style?: Styles): string +export function border_color( layer: Layer, - styleSet?: StyleSets, + style_set?: StyleSets, style?: Styles ): string -export function borderColor( +export function border_color( layer: Layer, - styleSetOrStyles?: StyleSets | Styles, + style_set_or_styles?: StyleSets | Styles, style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).border + return get_style(layer, style_set_or_styles, style).border } export function foreground(layer: Layer, style?: Styles): string export function foreground( layer: Layer, - styleSet?: StyleSets, + style_set?: StyleSets, style?: Styles ): string export function foreground( layer: Layer, - styleSetOrStyles?: StyleSets | Styles, + style_set_or_styles?: StyleSets | Styles, style?: Styles ): string { - return getStyle(layer, styleSetOrStyles, style).foreground -} - -interface Text extends Object { - family: keyof typeof fontFamilies - color: string - size: number - weight?: FontWeight - underline?: boolean + return get_style(layer, style_set_or_styles, style).foreground } export interface TextStyle extends Object { - family: keyof typeof fontFamilies + family: keyof typeof font_families color: string size: number weight?: FontWeight @@ -102,7 +94,7 @@ export interface TextStyle extends Object { } export interface TextProperties { - size?: keyof typeof fontSizes + size?: keyof typeof font_sizes weight?: FontWeight underline?: boolean color?: string @@ -182,49 +174,53 @@ interface FontFeatures { export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSet: StyleSets, + font_family: keyof typeof font_families, + style_set: StyleSets, style: Styles, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSet: StyleSets, + font_family: keyof typeof font_families, + style_set: StyleSets, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, + font_family: keyof typeof font_families, style: Styles, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, + font_family: keyof typeof font_families, properties?: TextProperties -): Text +): TextStyle export function text( layer: Layer, - fontFamily: keyof typeof fontFamilies, - styleSetStyleOrProperties?: StyleSets | Styles | TextProperties, - styleOrProperties?: Styles | TextProperties, + font_family: keyof typeof font_families, + style_set_style_or_properties?: StyleSets | Styles | TextProperties, + style_or_properties?: Styles | TextProperties, properties?: TextProperties ) { - let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + const style = get_style( + layer, + style_set_style_or_properties, + style_or_properties + ) - if (typeof styleSetStyleOrProperties === "object") { - properties = styleSetStyleOrProperties + if (typeof style_set_style_or_properties === "object") { + properties = style_set_style_or_properties } - if (typeof styleOrProperties === "object") { - properties = styleOrProperties + if (typeof style_or_properties === "object") { + properties = style_or_properties } - let size = fontSizes[properties?.size || "sm"] - let color = properties?.color || style.foreground + const size = font_sizes[properties?.size || "sm"] + const color = properties?.color || style.foreground return { - family: fontFamilies[fontFamily], + family: font_families[font_family], ...properties, color, size, @@ -252,13 +248,13 @@ export interface BorderProperties { export function border( layer: Layer, - styleSet: StyleSets, + style_set: StyleSets, style: Styles, properties?: BorderProperties ): Border export function border( layer: Layer, - styleSet: StyleSets, + style_set: StyleSets, properties?: BorderProperties ): Border export function border( @@ -269,17 +265,17 @@ export function border( export function border(layer: Layer, properties?: BorderProperties): Border export function border( layer: Layer, - styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties, - styleOrProperties?: Styles | BorderProperties, + style_set_or_properties?: StyleSets | Styles | BorderProperties, + style_or_properties?: Styles | BorderProperties, properties?: BorderProperties ): Border { - let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties) + const style = get_style(layer, style_set_or_properties, style_or_properties) - if (typeof styleSetStyleOrProperties === "object") { - properties = styleSetStyleOrProperties + if (typeof style_set_or_properties === "object") { + properties = style_set_or_properties } - if (typeof styleOrProperties === "object") { - properties = styleOrProperties + if (typeof style_or_properties === "object") { + properties = style_or_properties } return { @@ -291,9 +287,9 @@ export function border( export function svg( color: string, - asset: String, - width: Number, - height: Number + asset: string, + width: number, + height: number ) { return { color, diff --git a/styles/src/style_tree/contact_finder.ts b/styles/src/style_tree/contact_finder.ts new file mode 100644 index 0000000000..e61d100264 --- /dev/null +++ b/styles/src/style_tree/contact_finder.ts @@ -0,0 +1,70 @@ +import picker from "./picker" +import { ColorScheme } from "../theme/color_scheme" +import { background, border, foreground, text } from "./components" + +export default function contact_finder(theme: ColorScheme): any { + const side_margin = 6 + const contact_button = { + background: background(theme.middle, "variant"), + color: foreground(theme.middle, "variant"), + icon_width: 8, + button_width: 16, + corner_radius: 8, + } + + const picker_style = picker(theme) + const picker_input = { + background: background(theme.middle, "on"), + corner_radius: 6, + text: text(theme.middle, "mono"), + placeholder_text: text(theme.middle, "mono", "on", "disabled", { + size: "xs", + }), + selection: theme.players[0], + border: border(theme.middle), + padding: { + bottom: 4, + left: 8, + right: 8, + top: 4, + }, + margin: { + left: side_margin, + right: side_margin, + }, + } + + return { + picker: { + empty_container: {}, + item: { + ...picker_style.item, + margin: { left: side_margin, right: side_margin }, + }, + no_matches: picker_style.no_matches, + input_editor: picker_input, + empty_input_editor: picker_input, + }, + row_height: 28, + contact_avatar: { + corner_radius: 10, + width: 18, + }, + contact_username: { + padding: { + left: 8, + }, + }, + contact_button: { + ...contact_button, + hover: { + background: background(theme.middle, "variant", "hovered"), + }, + }, + disabled_contact_button: { + ...contact_button, + background: background(theme.middle, "disabled"), + color: foreground(theme.middle, "disabled"), + }, + } +} diff --git a/styles/src/styleTree/contactList.ts b/styles/src/style_tree/contact_list.ts similarity index 69% rename from styles/src/styleTree/contactList.ts rename to styles/src/style_tree/contact_list.ts index 88ae04277e..93f88e2d4a 100644 --- a/styles/src/styleTree/contactList.ts +++ b/styles/src/style_tree/contact_list.ts @@ -1,56 +1,62 @@ -import { ColorScheme } from "../theme/colorScheme" -import { background, border, borderColor, foreground, text } from "./components" +import { ColorScheme } from "../theme/color_scheme" +import { + background, + border, + border_color, + foreground, + text, +} from "./components" import { interactive, toggleable } from "../element" -export default function contactsPanel(colorScheme: ColorScheme) { - const nameMargin = 8 - const sidePadding = 12 +export default function contacts_panel(theme: ColorScheme): any { + const name_margin = 8 + const side_padding = 12 - let layer = colorScheme.middle + const layer = theme.middle - const contactButton = { + const contact_button = { background: background(layer, "on"), color: foreground(layer, "on"), - iconWidth: 8, - buttonWidth: 16, - cornerRadius: 8, + icon_width: 8, + button_width: 16, + corner_radius: 8, } - const projectRow = { - guestAvatarSpacing: 4, + const project_row = { + guest_avatar_spacing: 4, height: 24, - guestAvatar: { - cornerRadius: 8, + guest_avatar: { + corner_radius: 8, width: 14, }, name: { ...text(layer, "mono", { size: "sm" }), margin: { - left: nameMargin, + left: name_margin, right: 6, }, }, guests: { margin: { - left: nameMargin, - right: nameMargin, + left: name_margin, + right: name_margin, }, }, padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, } return { background: background(layer), padding: { top: 12 }, - userQueryEditor: { + user_query_editor: { background: background(layer, "on"), - cornerRadius: 6, + corner_radius: 6, text: text(layer, "mono", "on"), - placeholderText: text(layer, "mono", "on", "disabled", { + placeholder_text: text(layer, "mono", "on", "disabled", { size: "xs", }), - selection: colorScheme.players[0], + selection: theme.players[0], border: border(layer, "on"), padding: { bottom: 4, @@ -62,23 +68,23 @@ export default function contactsPanel(colorScheme: ColorScheme) { left: 6, }, }, - userQueryEditorHeight: 33, - addContactButton: { + user_query_editor_height: 33, + add_contact_button: { margin: { left: 6, right: 12 }, color: foreground(layer, "on"), - buttonWidth: 28, - iconWidth: 16, + button_width: 28, + icon_width: 16, }, - rowHeight: 28, - sectionIconSize: 8, - headerRow: toggleable({ + row_height: 28, + section_icon_size: 8, + header_row: toggleable({ base: interactive({ base: { ...text(layer, "mono", { size: "sm" }), margin: { top: 14 }, padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, background: background(layer, "default"), // posiewic: breaking change }, @@ -106,11 +112,11 @@ export default function contactsPanel(colorScheme: ColorScheme) { }, }, }), - leaveCall: interactive({ + leave_call: interactive({ base: { background: background(layer), border: border(layer), - cornerRadius: 6, + corner_radius: 6, margin: { top: 1, }, @@ -130,12 +136,12 @@ export default function contactsPanel(colorScheme: ColorScheme) { }, }, }), - contactRow: { + contact_row: { inactive: { default: { padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, }, }, @@ -143,84 +149,83 @@ export default function contactsPanel(colorScheme: ColorScheme) { default: { background: background(layer, "active"), padding: { - left: sidePadding, - right: sidePadding, + left: side_padding, + right: side_padding, }, }, }, }, - - contactAvatar: { - cornerRadius: 10, + contact_avatar: { + corner_radius: 10, width: 18, }, - contactStatusFree: { - cornerRadius: 4, + contact_status_free: { + corner_radius: 4, padding: 4, margin: { top: 12, left: 12 }, background: foreground(layer, "positive"), }, - contactStatusBusy: { - cornerRadius: 4, + contact_status_busy: { + corner_radius: 4, padding: 4, margin: { top: 12, left: 12 }, background: foreground(layer, "negative"), }, - contactUsername: { + contact_username: { ...text(layer, "mono", { size: "sm" }), margin: { - left: nameMargin, + left: name_margin, }, }, - contactButtonSpacing: nameMargin, - contactButton: interactive({ - base: { ...contactButton }, + contact_button_spacing: name_margin, + contact_button: interactive({ + base: { ...contact_button }, state: { hovered: { background: background(layer, "hovered"), }, }, }), - disabledButton: { - ...contactButton, + disabled_button: { + ...contact_button, background: background(layer, "on"), color: foreground(layer, "on"), }, - callingIndicator: { + calling_indicator: { ...text(layer, "mono", "variant", { size: "xs" }), }, - treeBranch: toggleable({ + tree_branch: toggleable({ base: interactive({ base: { - color: borderColor(layer), + color: border_color(layer), width: 1, }, state: { hovered: { - color: borderColor(layer), + color: border_color(layer), }, }, }), state: { active: { default: { - color: borderColor(layer), + color: border_color(layer), }, }, }, }), - projectRow: toggleable({ + project_row: toggleable({ base: interactive({ base: { - ...projectRow, + ...project_row, background: background(layer), icon: { - margin: { left: nameMargin }, + margin: { left: name_margin }, color: foreground(layer, "variant"), width: 12, }, name: { - ...projectRow.name, + ...project_row.name, ...text(layer, "mono", { size: "sm" }), }, }, diff --git a/styles/src/style_tree/contact_notification.ts b/styles/src/style_tree/contact_notification.ts new file mode 100644 index 0000000000..8de5496d91 --- /dev/null +++ b/styles/src/style_tree/contact_notification.ts @@ -0,0 +1,53 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, foreground, text } from "./components" +import { interactive } from "../element" + +export default function contact_notification(theme: ColorScheme): any { + const avatar_size = 12 + const header_padding = 8 + + return { + header_avatar: { + height: avatar_size, + width: avatar_size, + corner_radius: 6, + }, + header_message: { + ...text(theme.lowest, "sans", { size: "xs" }), + margin: { left: header_padding, right: header_padding }, + }, + header_height: 18, + body_message: { + ...text(theme.lowest, "sans", { size: "xs" }), + margin: { left: avatar_size + header_padding, top: 6, bottom: 6 }, + }, + button: interactive({ + base: { + ...text(theme.lowest, "sans", "on", { size: "xs" }), + background: background(theme.lowest, "on"), + padding: 4, + corner_radius: 6, + margin: { left: 6 }, + }, + + state: { + hovered: { + background: background(theme.lowest, "on", "hovered"), + }, + }, + }), + + dismiss_button: { + default: { + color: foreground(theme.lowest, "variant"), + icon_width: 8, + icon_height: 8, + button_width: 8, + button_height: 8, + hover: { + color: foreground(theme.lowest, "hovered"), + }, + }, + }, + } +} diff --git a/styles/src/style_tree/contacts_popover.ts b/styles/src/style_tree/contacts_popover.ts new file mode 100644 index 0000000000..4e3f8899e0 --- /dev/null +++ b/styles/src/style_tree/contacts_popover.ts @@ -0,0 +1,14 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border } from "./components" + +export default function contacts_popover(theme: ColorScheme): any { + return { + background: background(theme.middle), + corner_radius: 6, + padding: { top: 6, bottom: 6 }, + shadow: theme.popover_shadow, + border: border(theme.middle), + width: 300, + height: 400, + } +} diff --git a/styles/src/style_tree/context_menu.ts b/styles/src/style_tree/context_menu.ts new file mode 100644 index 0000000000..af45942d2d --- /dev/null +++ b/styles/src/style_tree/context_menu.ts @@ -0,0 +1,68 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, border_color, text } from "./components" +import { interactive, toggleable } from "../element" + +export default function context_menu(theme: ColorScheme): any { + return { + background: background(theme.middle), + corner_radius: 10, + padding: 4, + shadow: theme.popover_shadow, + border: border(theme.middle), + keystroke_margin: 30, + item: toggleable({ + base: interactive({ + base: { + icon_spacing: 8, + icon_width: 14, + padding: { left: 6, right: 6, top: 2, bottom: 2 }, + corner_radius: 6, + label: text(theme.middle, "sans", { size: "sm" }), + keystroke: { + ...text(theme.middle, "sans", "variant", { + size: "sm", + weight: "bold", + }), + padding: { left: 3, right: 3 }, + }, + }, + state: { + hovered: { + background: background(theme.middle, "hovered"), + label: text(theme.middle, "sans", "hovered", { + size: "sm", + }), + keystroke: { + ...text(theme.middle, "sans", "hovered", { + size: "sm", + weight: "bold", + }), + padding: { left: 3, right: 3 }, + }, + }, + clicked: { + background: background(theme.middle, "pressed"), + }, + }, + }), + state: { + active: { + default: { + background: background(theme.middle, "active"), + }, + hovered: { + background: background(theme.middle, "hovered"), + }, + clicked: { + background: background(theme.middle, "pressed"), + }, + }, + }, + }), + + separator: { + background: border_color(theme.middle), + margin: { top: 2, bottom: 2 }, + }, + } +} diff --git a/styles/src/styleTree/copilot.ts b/styles/src/style_tree/copilot.ts similarity index 69% rename from styles/src/styleTree/copilot.ts rename to styles/src/style_tree/copilot.ts index 1e09f4ff6b..eee6880e0c 100644 --- a/styles/src/styleTree/copilot.ts +++ b/styles/src/style_tree/copilot.ts @@ -1,18 +1,16 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, svg, text } from "./components" import { interactive } from "../element" -export default function copilot(colorScheme: ColorScheme) { - let layer = colorScheme.middle +export default function copilot(theme: ColorScheme): any { + const content_width = 264 - let content_width = 264 - - let ctaButton = + const cta_button = // Copied from welcome screen. FIXME: Move this into a ZDS component interactive({ base: { - background: background(layer), - border: border(layer, "default"), - cornerRadius: 4, + background: background(theme.middle), + border: border(theme.middle, "default"), + corner_radius: 4, margin: { top: 4, bottom: 4, @@ -25,49 +23,52 @@ export default function copilot(colorScheme: ColorScheme) { left: 7, right: 7, }, - ...text(layer, "sans", "default", { size: "sm" }), + ...text(theme.middle, "sans", "default", { size: "sm" }), }, state: { hovered: { - ...text(layer, "sans", "default", { size: "sm" }), - background: background(layer, "hovered"), - border: border(layer, "active"), + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "hovered"), + border: border(theme.middle, "active"), }, }, }) return { - outLinkIcon: interactive({ + out_link_icon: interactive({ base: { icon: svg( - foreground(layer, "variant"), + foreground(theme.middle, "variant"), "icons/link_out_12.svg", 12, 12 ), container: { - cornerRadius: 6, + corner_radius: 6, padding: { left: 6 }, }, }, state: { hovered: { icon: { - color: foreground(layer, "hovered"), + color: foreground(theme.middle, "hovered"), }, }, }, }), modal: { - titleText: { + title_text: { default: { - ...text(layer, "sans", { size: "xs", weight: "bold" }), + ...text(theme.middle, "sans", { + size: "xs", + weight: "bold", + }), }, }, titlebar: { - background: background(colorScheme.lowest), - border: border(layer, "active"), + background: background(theme.lowest), + border: border(theme.middle, "active"), padding: { top: 4, bottom: 4, @@ -76,7 +77,7 @@ export default function copilot(colorScheme: ColorScheme) { }, }, container: { - background: background(colorScheme.lowest), + background: background(theme.lowest), padding: { top: 0, left: 0, @@ -84,16 +85,16 @@ export default function copilot(colorScheme: ColorScheme) { bottom: 8, }, }, - closeIcon: interactive({ + close_icon: interactive({ base: { icon: svg( - foreground(layer, "variant"), + foreground(theme.middle, "variant"), "icons/x_mark_8.svg", 8, 8 ), container: { - cornerRadius: 2, + corner_radius: 2, padding: { top: 4, bottom: 4, @@ -108,7 +109,7 @@ export default function copilot(colorScheme: ColorScheme) { state: { hovered: { icon: svg( - foreground(layer, "on"), + foreground(theme.middle, "on"), "icons/x_mark_8.svg", 8, 8 @@ -116,7 +117,7 @@ export default function copilot(colorScheme: ColorScheme) { }, clicked: { icon: svg( - foreground(layer, "base"), + foreground(theme.middle, "base"), "icons/x_mark_8.svg", 8, 8 @@ -133,11 +134,11 @@ export default function copilot(colorScheme: ColorScheme) { auth: { content_width, - ctaButton, + cta_button, header: { icon: svg( - foreground(layer, "default"), + foreground(theme.middle, "default"), "icons/zed_plus_copilot_32.svg", 92, 32 @@ -154,7 +155,7 @@ export default function copilot(colorScheme: ColorScheme) { prompting: { subheading: { - ...text(layer, "sans", { size: "xs" }), + ...text(theme.middle, "sans", { size: "xs" }), margin: { top: 6, bottom: 12, @@ -164,19 +165,22 @@ export default function copilot(colorScheme: ColorScheme) { }, hint: { - ...text(layer, "sans", { size: "xs", color: "#838994" }), + ...text(theme.middle, "sans", { + size: "xs", + color: "#838994", + }), margin: { top: 6, bottom: 2, }, }, - deviceCode: { - text: text(layer, "mono", { size: "sm" }), + device_code: { + text: text(theme.middle, "mono", { size: "sm" }), cta: { - ...ctaButton, - background: background(colorScheme.lowest), - border: border(colorScheme.lowest, "inverted"), + ...cta_button, + background: background(theme.lowest), + border: border(theme.lowest, "inverted"), padding: { top: 0, bottom: 0, @@ -189,7 +193,7 @@ export default function copilot(colorScheme: ColorScheme) { }, }, left: content_width / 2, - leftContainer: { + left_container: { padding: { top: 3, bottom: 3, @@ -198,9 +202,9 @@ export default function copilot(colorScheme: ColorScheme) { }, }, right: (content_width * 1) / 3, - rightContainer: interactive({ + right_container: interactive({ base: { - border: border(colorScheme.lowest, "inverted", { + border: border(theme.lowest, "inverted", { bottom: false, right: false, top: false, @@ -215,7 +219,7 @@ export default function copilot(colorScheme: ColorScheme) { }, state: { hovered: { - border: border(layer, "active", { + border: border(theme.middle, "active", { bottom: false, right: false, top: false, @@ -227,9 +231,9 @@ export default function copilot(colorScheme: ColorScheme) { }, }, - notAuthorized: { + not_authorized: { subheading: { - ...text(layer, "sans", { size: "xs" }), + ...text(theme.middle, "sans", { size: "xs" }), margin: { top: 16, @@ -240,13 +244,13 @@ export default function copilot(colorScheme: ColorScheme) { }, warning: { - ...text(layer, "sans", { + ...text(theme.middle, "sans", { size: "xs", - color: foreground(layer, "warning"), + color: foreground(theme.middle, "warning"), }), - border: border(layer, "warning"), - background: background(layer, "warning"), - cornerRadius: 2, + border: border(theme.middle, "warning"), + background: background(theme.middle, "warning"), + corner_radius: 2, padding: { top: 4, left: 4, @@ -263,7 +267,7 @@ export default function copilot(colorScheme: ColorScheme) { authorized: { subheading: { - ...text(layer, "sans", { size: "xs" }), + ...text(theme.middle, "sans", { size: "xs" }), margin: { top: 16, @@ -272,7 +276,10 @@ export default function copilot(colorScheme: ColorScheme) { }, hint: { - ...text(layer, "sans", { size: "xs", color: "#838994" }), + ...text(theme.middle, "sans", { + size: "xs", + color: "#838994", + }), margin: { top: 24, bottom: 4, diff --git a/styles/src/style_tree/editor.ts b/styles/src/style_tree/editor.ts new file mode 100644 index 0000000000..af58276d16 --- /dev/null +++ b/styles/src/style_tree/editor.ts @@ -0,0 +1,313 @@ +import { with_opacity } from "../theme/color" +import { ColorScheme, Layer, StyleSets } from "../theme/color_scheme" +import { + background, + border, + border_color, + foreground, + text, +} from "./components" +import hover_popover from "./hover_popover" + +import { build_syntax } from "../theme/syntax" +import { interactive, toggleable } from "../element" + +export default function editor(theme: ColorScheme): any { + const { is_light } = theme + + const layer = theme.highest + + const autocomplete_item = { + corner_radius: 6, + padding: { + bottom: 2, + left: 6, + right: 6, + top: 2, + }, + } + + function diagnostic(layer: Layer, style_set: StyleSets) { + return { + text_scale_factor: 0.857, + header: { + border: border(layer, { + top: true, + }), + }, + message: { + text: text(layer, "sans", style_set, "default", { size: "sm" }), + highlight_text: text(layer, "sans", style_set, "default", { + size: "sm", + weight: "bold", + }), + }, + } + } + + const syntax = build_syntax(theme) + + return { + text_color: syntax.primary.color, + background: background(layer), + active_line_background: with_opacity(background(layer, "on"), 0.75), + highlighted_line_background: background(layer, "on"), + // Inline autocomplete suggestions, Co-pilot suggestions, etc. + hint: syntax.hint, + suggestion: syntax.predictive, + code_actions: { + indicator: toggleable({ + base: interactive({ + base: { + color: foreground(layer, "variant"), + }, + state: { + hovered: { + color: foreground(layer, "variant", "hovered"), + }, + clicked: { + color: foreground(layer, "variant", "pressed"), + }, + }, + }), + state: { + active: { + default: { + color: foreground(layer, "accent"), + }, + hovered: { + color: foreground(layer, "accent", "hovered"), + }, + clicked: { + color: foreground(layer, "accent", "pressed"), + }, + }, + }, + }), + + vertical_scale: 0.55, + }, + folds: { + icon_margin_scale: 2.5, + folded_icon: "icons/chevron_right_8.svg", + foldable_icon: "icons/chevron_down_8.svg", + indicator: toggleable({ + base: interactive({ + base: { + color: foreground(layer, "variant"), + }, + state: { + hovered: { + color: foreground(layer, "on"), + }, + clicked: { + color: foreground(layer, "base"), + }, + }, + }), + state: { + active: { + default: { + color: foreground(layer, "default"), + }, + hovered: { + color: foreground(layer, "variant"), + }, + }, + }, + }), + ellipses: { + text_color: theme.ramps.neutral(0.71).hex(), + corner_radius_factor: 0.15, + background: { + // Copied from hover_popover highlight + default: { + color: theme.ramps.neutral(0.5).alpha(0.0).hex(), + }, + + hovered: { + color: theme.ramps.neutral(0.5).alpha(0.5).hex(), + }, + + clicked: { + color: theme.ramps.neutral(0.5).alpha(0.7).hex(), + }, + }, + }, + fold_background: foreground(layer, "variant"), + }, + diff: { + deleted: is_light + ? theme.ramps.red(0.5).hex() + : theme.ramps.red(0.4).hex(), + modified: is_light + ? theme.ramps.yellow(0.5).hex() + : theme.ramps.yellow(0.5).hex(), + inserted: is_light + ? theme.ramps.green(0.4).hex() + : theme.ramps.green(0.5).hex(), + removed_width_em: 0.275, + width_em: 0.15, + corner_radius: 0.05, + }, + /** Highlights matching occurrences of what is under the cursor + * as well as matched brackets + */ + document_highlight_read_background: with_opacity( + foreground(layer, "accent"), + 0.1 + ), + document_highlight_write_background: theme.ramps + .neutral(0.5) + .alpha(0.4) + .hex(), // TODO: This was blend * 2 + error_color: background(layer, "negative"), + gutter_background: background(layer), + gutter_padding_factor: 3.5, + line_number: with_opacity(foreground(layer), 0.35), + line_number_active: foreground(layer), + rename_fade: 0.6, + unnecessary_code_fade: 0.5, + selection: theme.players[0], + whitespace: theme.ramps.neutral(0.5).hex(), + guest_selections: [ + theme.players[1], + theme.players[2], + theme.players[3], + theme.players[4], + theme.players[5], + theme.players[6], + theme.players[7], + ], + autocomplete: { + background: background(theme.middle), + corner_radius: 8, + padding: 4, + margin: { + left: -14, + }, + border: border(theme.middle), + shadow: theme.popover_shadow, + match_highlight: foreground(theme.middle, "accent"), + item: autocomplete_item, + hovered_item: { + ...autocomplete_item, + match_highlight: foreground(theme.middle, "accent", "hovered"), + background: background(theme.middle, "hovered"), + }, + selected_item: { + ...autocomplete_item, + match_highlight: foreground(theme.middle, "accent", "active"), + background: background(theme.middle, "active"), + }, + }, + diagnostic_header: { + background: background(theme.middle), + icon_width_factor: 1.5, + text_scale_factor: 0.857, + border: border(theme.middle, { + bottom: true, + top: true, + }), + code: { + ...text(theme.middle, "mono", { size: "sm" }), + margin: { + left: 10, + }, + }, + source: { + text: text(theme.middle, "sans", { + size: "sm", + weight: "bold", + }), + }, + message: { + highlight_text: text(theme.middle, "sans", { + size: "sm", + weight: "bold", + }), + text: text(theme.middle, "sans", { size: "sm" }), + }, + }, + diagnostic_path_header: { + background: background(theme.middle), + text_scale_factor: 0.857, + filename: text(theme.middle, "mono", { size: "sm" }), + path: { + ...text(theme.middle, "mono", { size: "sm" }), + margin: { + left: 12, + }, + }, + }, + error_diagnostic: diagnostic(theme.middle, "negative"), + warning_diagnostic: diagnostic(theme.middle, "warning"), + information_diagnostic: diagnostic(theme.middle, "accent"), + hint_diagnostic: diagnostic(theme.middle, "warning"), + invalid_error_diagnostic: diagnostic(theme.middle, "base"), + invalid_hint_diagnostic: diagnostic(theme.middle, "base"), + invalid_information_diagnostic: diagnostic(theme.middle, "base"), + invalid_warning_diagnostic: diagnostic(theme.middle, "base"), + hover_popover: hover_popover(theme), + link_definition: { + color: syntax.link_uri.color, + underline: syntax.link_uri.underline, + }, + jump_icon: interactive({ + base: { + color: foreground(layer, "on"), + icon_width: 20, + button_width: 20, + corner_radius: 6, + padding: { + top: 6, + bottom: 6, + left: 6, + right: 6, + }, + }, + state: { + hovered: { + background: background(layer, "on", "hovered"), + }, + }, + }), + + scrollbar: { + width: 12, + min_height_factor: 1.0, + track: { + border: border(layer, "variant", { left: true }), + }, + thumb: { + background: with_opacity(background(layer, "inverted"), 0.3), + border: { + width: 1, + color: border_color(layer, "variant"), + top: false, + right: true, + left: true, + bottom: false, + }, + }, + git: { + deleted: is_light + ? with_opacity(theme.ramps.red(0.5).hex(), 0.8) + : with_opacity(theme.ramps.red(0.4).hex(), 0.8), + modified: is_light + ? with_opacity(theme.ramps.yellow(0.5).hex(), 0.8) + : with_opacity(theme.ramps.yellow(0.4).hex(), 0.8), + inserted: is_light + ? with_opacity(theme.ramps.green(0.5).hex(), 0.8) + : with_opacity(theme.ramps.green(0.4).hex(), 0.8), + }, + }, + composition_mark: { + underline: { + thickness: 1.0, + color: border_color(layer), + }, + }, + syntax, + } +} diff --git a/styles/src/style_tree/feedback.ts b/styles/src/style_tree/feedback.ts new file mode 100644 index 0000000000..9217b60929 --- /dev/null +++ b/styles/src/style_tree/feedback.ts @@ -0,0 +1,49 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, text } from "./components" +import { interactive } from "../element" + +export default function feedback(theme: ColorScheme): any { + return { + submit_button: interactive({ + base: { + ...text(theme.highest, "mono", "on"), + background: background(theme.highest, "on"), + corner_radius: 6, + border: border(theme.highest, "on"), + margin: { + right: 4, + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + }, + state: { + clicked: { + ...text(theme.highest, "mono", "on", "pressed"), + background: background(theme.highest, "on", "pressed"), + border: border(theme.highest, "on", "pressed"), + }, + hovered: { + ...text(theme.highest, "mono", "on", "hovered"), + background: background(theme.highest, "on", "hovered"), + border: border(theme.highest, "on", "hovered"), + }, + }, + }), + button_margin: 8, + info_text_default: text(theme.highest, "sans", "default", { + size: "xs", + }), + link_text_default: text(theme.highest, "sans", "default", { + size: "xs", + underline: true, + }), + link_text_hover: text(theme.highest, "sans", "hovered", { + size: "xs", + underline: true, + }), + } +} diff --git a/styles/src/style_tree/hover_popover.ts b/styles/src/style_tree/hover_popover.ts new file mode 100644 index 0000000000..f469505741 --- /dev/null +++ b/styles/src/style_tree/hover_popover.ts @@ -0,0 +1,47 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, foreground, text } from "./components" + +export default function hover_popover(theme: ColorScheme): any { + const base_container = { + background: background(theme.middle), + corner_radius: 8, + padding: { + left: 8, + right: 8, + top: 4, + bottom: 4, + }, + shadow: theme.popover_shadow, + border: border(theme.middle), + margin: { + left: -8, + }, + } + + return { + container: base_container, + info_container: { + ...base_container, + background: background(theme.middle, "accent"), + border: border(theme.middle, "accent"), + }, + warning_container: { + ...base_container, + background: background(theme.middle, "warning"), + border: border(theme.middle, "warning"), + }, + error_container: { + ...base_container, + background: background(theme.middle, "negative"), + border: border(theme.middle, "negative"), + }, + block_style: { + padding: { top: 4 }, + }, + prose: text(theme.middle, "sans", { size: "sm" }), + diagnostic_source_highlight: { + color: foreground(theme.middle, "accent"), + }, + highlight: theme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better + } +} diff --git a/styles/src/style_tree/incoming_call_notification.ts b/styles/src/style_tree/incoming_call_notification.ts new file mode 100644 index 0000000000..ca46596e57 --- /dev/null +++ b/styles/src/style_tree/incoming_call_notification.ts @@ -0,0 +1,55 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, text } from "./components" + +export default function incoming_call_notification( + theme: ColorScheme +): unknown { + const avatar_size = 48 + return { + window_height: 74, + window_width: 380, + background: background(theme.middle), + caller_container: { + padding: 12, + }, + caller_avatar: { + height: avatar_size, + width: avatar_size, + corner_radius: avatar_size / 2, + }, + caller_metadata: { + margin: { left: 10 }, + }, + caller_username: { + ...text(theme.middle, "sans", { size: "sm", weight: "bold" }), + margin: { top: -3 }, + }, + caller_message: { + ...text(theme.middle, "sans", "variant", { size: "xs" }), + margin: { top: -3 }, + }, + worktree_roots: { + ...text(theme.middle, "sans", "variant", { + size: "xs", + weight: "bold", + }), + margin: { top: -3 }, + }, + button_width: 96, + accept_button: { + background: background(theme.middle, "accent"), + border: border(theme.middle, { left: true, bottom: true }), + ...text(theme.middle, "sans", "positive", { + size: "xs", + weight: "bold", + }), + }, + decline_button: { + border: border(theme.middle, { left: true }), + ...text(theme.middle, "sans", "negative", { + size: "xs", + weight: "bold", + }), + }, + } +} diff --git a/styles/src/styleTree/picker.ts b/styles/src/style_tree/picker.ts similarity index 53% rename from styles/src/styleTree/picker.ts rename to styles/src/style_tree/picker.ts index 5501bd4df2..7ca76cd85f 100644 --- a/styles/src/styleTree/picker.ts +++ b/styles/src/style_tree/picker.ts @@ -1,24 +1,23 @@ -import { ColorScheme } from "../theme/colorScheme" -import { withOpacity } from "../theme/color" +import { ColorScheme } from "../theme/color_scheme" +import { with_opacity } from "../theme/color" import { background, border, text } from "./components" import { interactive, toggleable } from "../element" -export default function picker(colorScheme: ColorScheme): any { - let layer = colorScheme.lowest +export default function picker(theme: ColorScheme): any { const container = { - background: background(layer), - border: border(layer), - shadow: colorScheme.modalShadow, - cornerRadius: 12, + background: background(theme.lowest), + border: border(theme.lowest), + shadow: theme.modal_shadow, + corner_radius: 12, padding: { bottom: 4, }, } - const inputEditor = { - placeholderText: text(layer, "sans", "on", "disabled"), - selection: colorScheme.players[0], - text: text(layer, "mono", "on"), - border: border(layer, { bottom: true }), + const input_editor = { + placeholder_text: text(theme.lowest, "sans", "on", "disabled"), + selection: theme.players[0], + text: text(theme.lowest, "mono", "on"), + border: border(theme.lowest, { bottom: true }), padding: { bottom: 8, left: 16, @@ -29,13 +28,13 @@ export default function picker(colorScheme: ColorScheme): any { bottom: 4, }, } - const emptyInputEditor: any = { ...inputEditor } - delete emptyInputEditor.border - delete emptyInputEditor.margin + const empty_input_editor: any = { ...input_editor } + delete empty_input_editor.border + delete empty_input_editor.margin return { ...container, - emptyContainer: { + empty_container: { ...container, padding: {}, }, @@ -53,22 +52,22 @@ export default function picker(colorScheme: ColorScheme): any { left: 4, right: 4, }, - cornerRadius: 8, - text: text(layer, "sans", "variant"), - highlightText: text(layer, "sans", "accent", { + corner_radius: 8, + text: text(theme.lowest, "sans", "variant"), + highlight_text: text(theme.lowest, "sans", "accent", { weight: "bold", }), }, state: { hovered: { - background: withOpacity( - background(layer, "hovered"), + background: with_opacity( + background(theme.lowest, "hovered"), 0.5 ), }, clicked: { - background: withOpacity( - background(layer, "pressed"), + background: with_opacity( + background(theme.lowest, "pressed"), 0.5 ), }, @@ -77,20 +76,20 @@ export default function picker(colorScheme: ColorScheme): any { state: { active: { default: { - background: withOpacity( - background(layer, "base", "active"), + background: with_opacity( + background(theme.lowest, "base", "active"), 0.5 ), }, hovered: { - background: withOpacity( - background(layer, "hovered"), + background: with_opacity( + background(theme.lowest, "hovered"), 0.5 ), }, clicked: { - background: withOpacity( - background(layer, "pressed"), + background: with_opacity( + background(theme.lowest, "pressed"), 0.5 ), }, @@ -98,10 +97,10 @@ export default function picker(colorScheme: ColorScheme): any { }, }), - inputEditor, - emptyInputEditor, - noMatches: { - text: text(layer, "sans", "variant"), + input_editor, + empty_input_editor, + no_matches: { + text: text(theme.lowest, "sans", "variant"), padding: { bottom: 8, left: 16, diff --git a/styles/src/style_tree/project_diagnostics.ts b/styles/src/style_tree/project_diagnostics.ts new file mode 100644 index 0000000000..5962b98a26 --- /dev/null +++ b/styles/src/style_tree/project_diagnostics.ts @@ -0,0 +1,12 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, text } from "./components" + +export default function project_diagnostics(theme: ColorScheme): any { + return { + background: background(theme.highest), + tab_icon_spacing: 4, + tab_icon_width: 13, + tab_summary_spacing: 10, + empty_message: text(theme.highest, "sans", "variant", { size: "md" }), + } +} diff --git a/styles/src/style_tree/project_panel.ts b/styles/src/style_tree/project_panel.ts new file mode 100644 index 0000000000..d1024778f1 --- /dev/null +++ b/styles/src/style_tree/project_panel.ts @@ -0,0 +1,202 @@ +import { ColorScheme } from "../theme/color_scheme" +import { with_opacity } from "../theme/color" +import { + Border, + TextStyle, + background, + border, + foreground, + text, +} from "./components" +import { interactive, toggleable } from "../element" +import merge from "ts-deepmerge" +export default function project_panel(theme: ColorScheme): any { + const { is_light } = theme + + type EntryStateProps = { + background?: string + border?: Border + text?: TextStyle + icon_color?: string + } + + type EntryState = { + default: EntryStateProps + hovered?: EntryStateProps + clicked?: EntryStateProps + } + + const entry = (unselected?: EntryState, selected?: EntryState) => { + const git_status = { + git: { + modified: is_light + ? theme.ramps.yellow(0.6).hex() + : theme.ramps.yellow(0.5).hex(), + inserted: is_light + ? theme.ramps.green(0.45).hex() + : theme.ramps.green(0.5).hex(), + conflict: is_light + ? theme.ramps.red(0.6).hex() + : theme.ramps.red(0.5).hex(), + }, + } + + const base_properties = { + height: 22, + background: background(theme.middle), + icon_color: foreground(theme.middle, "variant"), + icon_size: 7, + icon_spacing: 5, + text: text(theme.middle, "sans", "variant", { size: "sm" }), + status: { + ...git_status, + }, + } + + const selected_style: EntryState | undefined = selected + ? selected + : unselected + + const unselected_default_style = merge( + base_properties, + unselected?.default ?? {}, + {} + ) + const unselected_hovered_style = merge( + base_properties, + { background: background(theme.middle, "hovered") }, + unselected?.hovered ?? {}, + ) + const unselected_clicked_style = merge( + base_properties, + { background: background(theme.middle, "pressed"), } + , + unselected?.clicked ?? {}, + ) + const selected_default_style = merge( + base_properties, + { + background: background(theme.lowest), + text: text(theme.lowest, "sans", { size: "sm" }), + }, + selected_style?.default ?? {}, + + ) + const selected_hovered_style = merge( + base_properties, + { + background: background(theme.lowest, "hovered"), + text: text(theme.lowest, "sans", { size: "sm" }), + + }, + selected_style?.hovered ?? {}, + + ) + const selected_clicked_style = merge( + base_properties, + { + background: background(theme.lowest, "pressed"), + text: text(theme.lowest, "sans", { size: "sm" }), + }, + selected_style?.clicked ?? {}, + + ) + + return toggleable({ + state: { + inactive: interactive({ + state: { + default: unselected_default_style, + hovered: unselected_hovered_style, + clicked: unselected_clicked_style, + }, + }), + active: interactive({ + state: { + default: selected_default_style, + hovered: selected_hovered_style, + clicked: selected_clicked_style, + }, + }), + }, + }) + } + + const default_entry = entry() + + return { + open_project_button: interactive({ + base: { + background: background(theme.middle), + border: border(theme.middle, "active"), + corner_radius: 4, + margin: { + top: 16, + left: 16, + right: 16, + }, + padding: { + top: 3, + bottom: 3, + left: 7, + right: 7, + }, + ...text(theme.middle, "sans", "default", { size: "sm" }), + }, + state: { + hovered: { + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "hovered"), + border: border(theme.middle, "active"), + }, + clicked: { + ...text(theme.middle, "sans", "default", { size: "sm" }), + background: background(theme.middle, "pressed"), + border: border(theme.middle, "active"), + }, + }, + }), + background: background(theme.middle), + padding: { left: 6, right: 6, top: 0, bottom: 6 }, + indent_width: 12, + entry: default_entry, + dragged_entry: { + ...default_entry.inactive.default, + text: text(theme.middle, "sans", "on", { size: "sm" }), + background: with_opacity(background(theme.middle, "on"), 0.9), + border: border(theme.middle), + }, + ignored_entry: entry( + { + default: { + text: text(theme.middle, "sans", "disabled"), + }, + }, + { + default: { + icon_color: foreground(theme.middle, "variant"), + }, + } + ), + cut_entry: entry( + { + default: { + text: text(theme.middle, "sans", "disabled"), + }, + }, + { + default: { + background: background(theme.middle, "active"), + text: text(theme.middle, "sans", "disabled", { + size: "sm", + }), + }, + } + ), + filename_editor: { + background: background(theme.middle, "on"), + text: text(theme.middle, "sans", "on", { size: "sm" }), + selection: theme.players[0], + }, + } +} diff --git a/styles/src/style_tree/project_shared_notification.ts b/styles/src/style_tree/project_shared_notification.ts new file mode 100644 index 0000000000..ffda0f4b70 --- /dev/null +++ b/styles/src/style_tree/project_shared_notification.ts @@ -0,0 +1,55 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, text } from "./components" + +export default function project_shared_notification( + theme: ColorScheme +): unknown { + const avatar_size = 48 + return { + window_height: 74, + window_width: 380, + background: background(theme.middle), + owner_container: { + padding: 12, + }, + owner_avatar: { + height: avatar_size, + width: avatar_size, + corner_radius: avatar_size / 2, + }, + owner_metadata: { + margin: { left: 10 }, + }, + owner_username: { + ...text(theme.middle, "sans", { size: "sm", weight: "bold" }), + margin: { top: -3 }, + }, + message: { + ...text(theme.middle, "sans", "variant", { size: "xs" }), + margin: { top: -3 }, + }, + worktree_roots: { + ...text(theme.middle, "sans", "variant", { + size: "xs", + weight: "bold", + }), + margin: { top: -3 }, + }, + button_width: 96, + open_button: { + background: background(theme.middle, "accent"), + border: border(theme.middle, { left: true, bottom: true }), + ...text(theme.middle, "sans", "accent", { + size: "xs", + weight: "bold", + }), + }, + dismiss_button: { + border: border(theme.middle, { left: true }), + ...text(theme.middle, "sans", "variant", { + size: "xs", + weight: "bold", + }), + }, + } +} diff --git a/styles/src/style_tree/search.ts b/styles/src/style_tree/search.ts new file mode 100644 index 0000000000..df260f95b7 --- /dev/null +++ b/styles/src/style_tree/search.ts @@ -0,0 +1,136 @@ +import { ColorScheme } from "../theme/color_scheme" +import { with_opacity } from "../theme/color" +import { background, border, foreground, text } from "./components" +import { interactive, toggleable } from "../element" + +export default function search(theme: ColorScheme): any { + // Search input + const editor = { + background: background(theme.highest), + corner_radius: 8, + min_width: 200, + max_width: 500, + placeholder_text: text(theme.highest, "mono", "disabled"), + selection: theme.players[0], + text: text(theme.highest, "mono", "default"), + border: border(theme.highest), + margin: { + right: 12, + }, + padding: { + top: 3, + bottom: 3, + left: 12, + right: 8, + }, + } + + const include_exclude_editor = { + ...editor, + min_width: 100, + max_width: 250, + } + + return { + // TODO: Add an activeMatchBackground on the rust side to differentiate between active and inactive + match_background: with_opacity( + foreground(theme.highest, "accent"), + 0.4 + ), + option_button: toggleable({ + base: interactive({ + base: { + ...text(theme.highest, "mono", "on"), + background: background(theme.highest, "on"), + corner_radius: 6, + border: border(theme.highest, "on"), + margin: { + right: 4, + }, + padding: { + bottom: 2, + left: 10, + right: 10, + top: 2, + }, + }, + state: { + hovered: { + ...text(theme.highest, "mono", "on", "hovered"), + background: background(theme.highest, "on", "hovered"), + border: border(theme.highest, "on", "hovered"), + }, + clicked: { + ...text(theme.highest, "mono", "on", "pressed"), + background: background(theme.highest, "on", "pressed"), + border: border(theme.highest, "on", "pressed"), + }, + }, + }), + state: { + active: { + default: { + ...text(theme.highest, "mono", "accent"), + }, + hovered: { + ...text(theme.highest, "mono", "accent", "hovered"), + }, + clicked: { + ...text(theme.highest, "mono", "accent", "pressed"), + }, + }, + }, + }), + editor, + invalid_editor: { + ...editor, + border: border(theme.highest, "negative"), + }, + include_exclude_editor, + invalid_include_exclude_editor: { + ...include_exclude_editor, + border: border(theme.highest, "negative"), + }, + match_index: { + ...text(theme.highest, "mono", "variant"), + padding: { + left: 6, + }, + }, + option_button_group: { + padding: { + left: 12, + right: 12, + }, + }, + include_exclude_inputs: { + ...text(theme.highest, "mono", "variant"), + padding: { + right: 6, + }, + }, + results_status: { + ...text(theme.highest, "mono", "on"), + size: 18, + }, + dismiss_button: interactive({ + base: { + color: foreground(theme.highest, "variant"), + icon_width: 12, + button_width: 14, + padding: { + left: 10, + right: 10, + }, + }, + state: { + hovered: { + color: foreground(theme.highest, "hovered"), + }, + clicked: { + color: foreground(theme.highest, "pressed"), + }, + }, + }), + } +} diff --git a/styles/src/style_tree/shared_screen.ts b/styles/src/style_tree/shared_screen.ts new file mode 100644 index 0000000000..b57c483f1c --- /dev/null +++ b/styles/src/style_tree/shared_screen.ts @@ -0,0 +1,8 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background } from "./components" + +export default function sharedScreen(theme: ColorScheme) { + return { + background: background(theme.highest), + } +} diff --git a/styles/src/style_tree/simple_message_notification.ts b/styles/src/style_tree/simple_message_notification.ts new file mode 100644 index 0000000000..0b5c1e0c29 --- /dev/null +++ b/styles/src/style_tree/simple_message_notification.ts @@ -0,0 +1,50 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, foreground, text } from "./components" +import { interactive } from "../element" + +export default function simple_message_notification(theme: ColorScheme): any { + const header_padding = 8 + + return { + message: { + ...text(theme.middle, "sans", { size: "xs" }), + margin: { left: header_padding, right: header_padding }, + }, + action_message: interactive({ + base: { + ...text(theme.middle, "sans", { size: "xs" }), + border: border(theme.middle, "active"), + corner_radius: 4, + padding: { + top: 3, + bottom: 3, + left: 7, + right: 7, + }, + + margin: { left: header_padding, top: 6, bottom: 6 }, + }, + state: { + hovered: { + ...text(theme.middle, "sans", "default", { size: "xs" }), + background: background(theme.middle, "hovered"), + border: border(theme.middle, "active"), + }, + }, + }), + dismiss_button: interactive({ + base: { + color: foreground(theme.middle), + icon_width: 8, + icon_height: 8, + button_width: 8, + button_height: 8, + }, + state: { + hovered: { + color: foreground(theme.middle, "hovered"), + }, + }, + }), + } +} diff --git a/styles/src/styleTree/statusBar.ts b/styles/src/style_tree/status_bar.ts similarity index 61% rename from styles/src/styleTree/statusBar.ts rename to styles/src/style_tree/status_bar.ts index 339e2e40cf..bde40d570c 100644 --- a/styles/src/styleTree/statusBar.ts +++ b/styles/src/style_tree/status_bar.ts @@ -1,22 +1,22 @@ -import { ColorScheme } from "../theme/colorScheme" +import { ColorScheme } from "../theme/color_scheme" import { background, border, foreground, text } from "./components" import { interactive, toggleable } from "../element" -export default function statusBar(colorScheme: ColorScheme) { - let layer = colorScheme.lowest +export default function status_bar(theme: ColorScheme): any { + const layer = theme.lowest - const statusContainer = { - cornerRadius: 6, + const status_container = { + corner_radius: 6, padding: { top: 3, bottom: 3, left: 6, right: 6 }, } - const diagnosticStatusContainer = { - cornerRadius: 6, + const diagnostic_status_container = { + corner_radius: 6, padding: { top: 1, bottom: 1, left: 6, right: 6 }, } return { height: 30, - itemSpacing: 8, + item_spacing: 8, padding: { top: 1, bottom: 1, @@ -24,8 +24,8 @@ export default function statusBar(colorScheme: ColorScheme) { right: 6, }, border: border(layer, { top: true, overlay: true }), - cursorPosition: text(layer, "sans", "variant"), - activeLanguage: interactive({ + cursor_position: text(layer, "sans", "variant"), + active_language: interactive({ base: { padding: { left: 6, right: 6 }, ...text(layer, "sans", "variant"), @@ -36,83 +36,83 @@ export default function statusBar(colorScheme: ColorScheme) { }, }, }), - autoUpdateProgressMessage: text(layer, "sans", "variant"), - autoUpdateDoneMessage: text(layer, "sans", "variant"), - lspStatus: interactive({ + auto_update_progress_message: text(layer, "sans", "variant"), + auto_update_done_message: text(layer, "sans", "variant"), + lsp_status: interactive({ base: { - ...diagnosticStatusContainer, - iconSpacing: 4, - iconWidth: 14, + ...diagnostic_status_container, + icon_spacing: 4, + icon_width: 14, height: 18, message: text(layer, "sans"), - iconColor: foreground(layer), + icon_color: foreground(layer), }, state: { hovered: { message: text(layer, "sans"), - iconColor: foreground(layer), + icon_color: foreground(layer), background: background(layer, "hovered"), }, }, }), - diagnosticMessage: interactive({ + diagnostic_message: interactive({ base: { ...text(layer, "sans"), }, state: { hovered: text(layer, "sans", "hovered") }, }), - diagnosticSummary: interactive({ + diagnostic_summary: interactive({ base: { height: 20, - iconWidth: 16, - iconSpacing: 2, - summarySpacing: 6, + icon_width: 16, + icon_spacing: 2, + summary_spacing: 6, text: text(layer, "sans", { size: "sm" }), - iconColorOk: foreground(layer, "variant"), - iconColorWarning: foreground(layer, "warning"), - iconColorError: foreground(layer, "negative"), - containerOk: { - cornerRadius: 6, + icon_color_ok: foreground(layer, "variant"), + icon_color_warning: foreground(layer, "warning"), + icon_color_error: foreground(layer, "negative"), + container_ok: { + corner_radius: 6, padding: { top: 3, bottom: 3, left: 7, right: 7 }, }, - containerWarning: { - ...diagnosticStatusContainer, + container_warning: { + ...diagnostic_status_container, background: background(layer, "warning"), border: border(layer, "warning"), }, - containerError: { - ...diagnosticStatusContainer, + container_error: { + ...diagnostic_status_container, background: background(layer, "negative"), border: border(layer, "negative"), }, }, state: { hovered: { - iconColorOk: foreground(layer, "on"), - containerOk: { + icon_color_ok: foreground(layer, "on"), + container_ok: { background: background(layer, "on", "hovered"), }, - containerWarning: { + container_warning: { background: background(layer, "warning", "hovered"), border: border(layer, "warning", "hovered"), }, - containerError: { + container_error: { background: background(layer, "negative", "hovered"), border: border(layer, "negative", "hovered"), }, }, }, }), - panelButtons: { - groupLeft: {}, - groupBottom: {}, - groupRight: {}, + panel_buttons: { + group_left: {}, + group_bottom: {}, + group_right: {}, button: toggleable({ base: interactive({ base: { - ...statusContainer, - iconSize: 16, - iconColor: foreground(layer, "variant"), + ...status_container, + icon_size: 16, + icon_color: foreground(layer, "variant"), label: { margin: { left: 6 }, ...text(layer, "sans", { size: "sm" }), @@ -120,7 +120,7 @@ export default function statusBar(colorScheme: ColorScheme) { }, state: { hovered: { - iconColor: foreground(layer, "hovered"), + icon_color: foreground(layer, "hovered"), background: background(layer, "variant"), }, }, @@ -128,22 +128,22 @@ export default function statusBar(colorScheme: ColorScheme) { state: { active: { default: { - iconColor: foreground(layer, "active"), + icon_color: foreground(layer, "active"), background: background(layer, "active"), }, hovered: { - iconColor: foreground(layer, "hovered"), + icon_color: foreground(layer, "hovered"), background: background(layer, "hovered"), }, clicked: { - iconColor: foreground(layer, "pressed"), + icon_color: foreground(layer, "pressed"), background: background(layer, "pressed"), }, }, }, }), badge: { - cornerRadius: 3, + corner_radius: 3, padding: 2, margin: { bottom: -1, right: -1 }, border: border(layer), diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/style_tree/tab_bar.ts similarity index 63% rename from styles/src/styleTree/tabBar.ts rename to styles/src/style_tree/tab_bar.ts index af35a8fef4..55fd2c314a 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/style_tree/tab_bar.ts @@ -1,13 +1,13 @@ -import { ColorScheme } from "../theme/colorScheme" -import { withOpacity } from "../theme/color" +import { ColorScheme } from "../theme/color_scheme" +import { with_opacity } from "../theme/color" import { text, border, background, foreground } from "./components" import { interactive, toggleable } from "../element" -export default function tabBar(colorScheme: ColorScheme) { +export default function tab_bar(theme: ColorScheme): any { const height = 32 - let activeLayer = colorScheme.highest - let layer = colorScheme.middle + const active_layer = theme.highest + const layer = theme.middle const tab = { height, @@ -25,16 +25,16 @@ export default function tabBar(colorScheme: ColorScheme) { spacing: 8, // Tab type icons (e.g. Project Search) - typeIconWidth: 14, + type_icon_width: 14, // Close icons - closeIconWidth: 8, - iconClose: foreground(layer, "variant"), - iconCloseActive: foreground(layer, "hovered"), + close_icon_width: 8, + icon_close: foreground(layer, "variant"), + icon_close_active: foreground(layer, "hovered"), // Indicators - iconConflict: foreground(layer, "warning"), - iconDirty: foreground(layer, "accent"), + icon_conflict: foreground(layer, "warning"), + icon_dirty: foreground(layer, "accent"), // When two tabs of the same name are open, a label appears next to them description: { @@ -43,25 +43,25 @@ export default function tabBar(colorScheme: ColorScheme) { }, } - const activePaneActiveTab = { + const active_pane_active_tab = { ...tab, - background: background(activeLayer), - text: text(activeLayer, "sans", "active", { size: "sm" }), + background: background(active_layer), + text: text(active_layer, "sans", "active", { size: "sm" }), border: { ...tab.border, bottom: false, }, } - const inactivePaneInactiveTab = { + const inactive_pane_inactive_tab = { ...tab, background: background(layer), text: text(layer, "sans", "variant", { size: "sm" }), } - const inactivePaneActiveTab = { + const inactive_pane_active_tab = { ...tab, - background: background(activeLayer), + background: background(active_layer), text: text(layer, "sans", "variant", { size: "sm" }), border: { ...tab.border, @@ -69,31 +69,31 @@ export default function tabBar(colorScheme: ColorScheme) { }, } - const draggedTab = { - ...activePaneActiveTab, - background: withOpacity(tab.background, 0.9), + const dragged_tab = { + ...active_pane_active_tab, + background: with_opacity(tab.background, 0.9), border: undefined as any, - shadow: colorScheme.popoverShadow, + shadow: theme.popover_shadow, } return { height, background: background(layer), - activePane: { - activeTab: activePaneActiveTab, - inactiveTab: tab, + active_pane: { + active_tab: active_pane_active_tab, + inactive_tab: tab, }, - inactivePane: { - activeTab: inactivePaneActiveTab, - inactiveTab: inactivePaneInactiveTab, + inactive_pane: { + active_tab: inactive_pane_active_tab, + inactive_tab: inactive_pane_inactive_tab, }, - draggedTab, - paneButton: toggleable({ + dragged_tab, + pane_button: toggleable({ base: interactive({ base: { color: foreground(layer, "variant"), - iconWidth: 12, - buttonWidth: activePaneActiveTab.height, + icon_width: 12, + button_width: active_pane_active_tab.height, }, state: { hovered: { @@ -118,7 +118,7 @@ export default function tabBar(colorScheme: ColorScheme) { }, }, }), - paneButtonContainer: { + pane_button_container: { background: tab.background, border: { ...tab.border, diff --git a/styles/src/style_tree/terminal.ts b/styles/src/style_tree/terminal.ts new file mode 100644 index 0000000000..e902aa4264 --- /dev/null +++ b/styles/src/style_tree/terminal.ts @@ -0,0 +1,52 @@ +import { ColorScheme } from "../theme/color_scheme" + +export default function terminal(theme: ColorScheme) { + /** + * Colors are controlled per-cell in the terminal grid. + * Cells can be set to any of these more 'theme-capable' colors + * or can be set directly with RGB values. + * Here are the common interpretations of these names: + * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors + */ + return { + black: theme.ramps.neutral(0).hex(), + red: theme.ramps.red(0.5).hex(), + green: theme.ramps.green(0.5).hex(), + yellow: theme.ramps.yellow(0.5).hex(), + blue: theme.ramps.blue(0.5).hex(), + magenta: theme.ramps.magenta(0.5).hex(), + cyan: theme.ramps.cyan(0.5).hex(), + white: theme.ramps.neutral(1).hex(), + bright_black: theme.ramps.neutral(0.4).hex(), + bright_red: theme.ramps.red(0.25).hex(), + bright_green: theme.ramps.green(0.25).hex(), + bright_yellow: theme.ramps.yellow(0.25).hex(), + bright_blue: theme.ramps.blue(0.25).hex(), + bright_magenta: theme.ramps.magenta(0.25).hex(), + bright_cyan: theme.ramps.cyan(0.25).hex(), + bright_white: theme.ramps.neutral(1).hex(), + /** + * Default color for characters + */ + foreground: theme.ramps.neutral(1).hex(), + /** + * Default color for the rectangle background of a cell + */ + background: theme.ramps.neutral(0).hex(), + modal_background: theme.ramps.neutral(0.1).hex(), + /** + * Default color for the cursor + */ + cursor: theme.players[0].cursor, + dim_black: theme.ramps.neutral(1).hex(), + dim_red: theme.ramps.red(0.75).hex(), + dim_green: theme.ramps.green(0.75).hex(), + dim_yellow: theme.ramps.yellow(0.75).hex(), + dim_blue: theme.ramps.blue(0.75).hex(), + dim_magenta: theme.ramps.magenta(0.75).hex(), + dim_cyan: theme.ramps.cyan(0.75).hex(), + dim_white: theme.ramps.neutral(0.6).hex(), + bright_foreground: theme.ramps.neutral(1).hex(), + dim_foreground: theme.ramps.neutral(0).hex(), + } +} diff --git a/styles/src/styleTree/titlebar.ts b/styles/src/style_tree/titlebar.ts similarity index 79% rename from styles/src/styleTree/titlebar.ts rename to styles/src/style_tree/titlebar.ts index 3c7318a56e..067d619bb5 100644 --- a/styles/src/styleTree/titlebar.ts +++ b/styles/src/style_tree/titlebar.ts @@ -2,7 +2,7 @@ import { ColorScheme } from "../common" import { icon_button, toggleable_icon_button } from "../component/icon_button" import { toggleable_text_button } from "../component/text_button" import { interactive, toggleable } from "../element" -import { withOpacity } from "../theme/color" +import { with_opacity } from "../theme/color" import { background, border, foreground, text } from "./components" const ITEM_SPACING = 8 @@ -17,8 +17,8 @@ function build_spacing( group: spacing, item: spacing / 2, half_item: spacing / 4, - marginY: (container_height - element_height) / 2, - marginX: (container_height - element_height) / 2, + margin_y: (container_height - element_height) / 2, + margin_x: (container_height - element_height) / 2, } } @@ -26,15 +26,15 @@ function call_controls(theme: ColorScheme) { const button_height = 18 const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING) - const marginY = { - top: space.marginY, - bottom: space.marginY, + const margin_y = { + top: space.margin_y, + bottom: space.margin_y, } return { toggle_microphone_button: toggleable_icon_button(theme, { margin: { - ...marginY, + ...margin_y, left: space.group, right: space.half_item, }, @@ -43,7 +43,7 @@ function call_controls(theme: ColorScheme) { toggle_speakers_button: toggleable_icon_button(theme, { margin: { - ...marginY, + ...margin_y, left: space.half_item, right: space.half_item, }, @@ -51,7 +51,7 @@ function call_controls(theme: ColorScheme) { screen_share_button: toggleable_icon_button(theme, { margin: { - ...marginY, + ...margin_y, left: space.half_item, right: space.group, }, @@ -78,7 +78,7 @@ function user_menu(theme: ColorScheme) { const button = toggleable({ base: interactive({ base: { - cornerRadius: 6, + corner_radius: 6, height: button_height, width: online ? 37 : 24, padding: { @@ -150,20 +150,20 @@ function user_menu(theme: ColorScheme) { } } return { - userMenuButtonOnline: build_button({ online: true }), - userMenuButtonOffline: build_button({ online: false }), + user_menu_button_online: build_button({ online: true }), + user_menu_button_offline: build_button({ online: false }), } } -export function titlebar(theme: ColorScheme) { - const avatarWidth = 15 - const avatarOuterWidth = avatarWidth + 4 - const followerAvatarWidth = 14 - const followerAvatarOuterWidth = followerAvatarWidth + 4 +export function titlebar(theme: ColorScheme): any { + const avatar_width = 15 + const avatar_outer_width = avatar_width + 4 + const follower_avatar_width = 14 + const follower_avatar_outer_width = follower_avatar_width + 4 return { item_spacing: ITEM_SPACING, - facePileSpacing: 2, + face_pile_spacing: 2, height: TITLEBAR_HEIGHT, background: background(theme.lowest), border: border(theme.lowest, { bottom: true }), @@ -177,21 +177,21 @@ export function titlebar(theme: ColorScheme) { highlight_color: text(theme.lowest, "sans", "active").color, // Collaborators - leaderAvatar: { - width: avatarWidth, - outerWidth: avatarOuterWidth, - cornerRadius: avatarWidth / 2, - outerCornerRadius: avatarOuterWidth / 2, + leader_avatar: { + width: avatar_width, + outer_width: avatar_outer_width, + corner_radius: avatar_width / 2, + outer_corner_radius: avatar_outer_width / 2, }, - followerAvatar: { - width: followerAvatarWidth, - outerWidth: followerAvatarOuterWidth, - cornerRadius: followerAvatarWidth / 2, - outerCornerRadius: followerAvatarOuterWidth / 2, + follower_avatar: { + width: follower_avatar_width, + outer_width: follower_avatar_outer_width, + corner_radius: follower_avatar_width / 2, + outer_corner_radius: follower_avatar_outer_width / 2, }, - inactiveAvatarGrayscale: true, - followerAvatarOverlap: 8, - leaderSelection: { + inactive_avatar_grayscale: true, + follower_avatar_overlap: 8, + leader_selection: { margin: { top: 4, bottom: 4, @@ -202,16 +202,16 @@ export function titlebar(theme: ColorScheme) { top: 2, bottom: 2, }, - cornerRadius: 6, + corner_radius: 6, }, - avatarRibbon: { + avatar_ribbon: { height: 3, width: 14, // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded. }, sign_in_button: toggleable_text_button(theme, {}), - offlineIcon: { + offline_icon: { color: foreground(theme.lowest, "variant"), width: 16, margin: { @@ -223,9 +223,9 @@ export function titlebar(theme: ColorScheme) { }, // When the collaboration server is out of date, show a warning - outdatedWarning: { + outdated_warning: { ...text(theme.lowest, "sans", "warning", { size: "xs" }), - background: withOpacity(background(theme.lowest, "warning"), 0.3), + background: with_opacity(background(theme.lowest, "warning"), 0.3), border: border(theme.lowest, "warning"), margin: { left: ITEM_SPACING, @@ -234,7 +234,7 @@ export function titlebar(theme: ColorScheme) { left: 8, right: 8, }, - cornerRadius: 6, + corner_radius: 6, }, leave_call_button: icon_button(theme, { @@ -253,14 +253,14 @@ export function titlebar(theme: ColorScheme) { }), // Jewel that notifies you that there are new contact requests - toggleContactsBadge: { - cornerRadius: 3, + toggle_contacts_badge: { + corner_radius: 3, padding: 2, margin: { top: 3, left: 3 }, border: border(theme.lowest), background: foreground(theme.lowest, "accent"), }, - shareButton: toggleable_text_button(theme, {}), + share_button: toggleable_text_button(theme, {}), user_menu: user_menu(theme), } } diff --git a/styles/src/style_tree/toolbar_dropdown_menu.ts b/styles/src/style_tree/toolbar_dropdown_menu.ts new file mode 100644 index 0000000000..dc22ac73cf --- /dev/null +++ b/styles/src/style_tree/toolbar_dropdown_menu.ts @@ -0,0 +1,64 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, text } from "./components" +import { interactive, toggleable } from "../element" +export default function dropdown_menu(theme: ColorScheme): any { + return { + row_height: 30, + background: background(theme.middle), + border: border(theme.middle), + shadow: theme.popover_shadow, + header: interactive({ + base: { + ...text(theme.middle, "sans", { size: "sm" }), + secondary_text: text(theme.middle, "sans", { + size: "sm", + color: "#aaaaaa", + }), + secondary_text_spacing: 10, + padding: { left: 8, right: 8, top: 2, bottom: 2 }, + corner_radius: 6, + background: background(theme.middle, "on"), + }, + state: { + hovered: { + background: background(theme.middle, "hovered"), + }, + clicked: { + background: background(theme.middle, "pressed"), + }, + }, + }), + section_header: { + ...text(theme.middle, "sans", { size: "sm" }), + padding: { left: 8, right: 8, top: 8, bottom: 8 }, + }, + item: toggleable({ + base: interactive({ + base: { + ...text(theme.middle, "sans", { size: "sm" }), + secondary_text_spacing: 10, + secondary_text: text(theme.middle, "sans", { size: "sm" }), + padding: { left: 18, right: 18, top: 2, bottom: 2 }, + }, + state: { + hovered: { + background: background(theme.middle, "hovered"), + ...text(theme.middle, "sans", "hovered", { + size: "sm", + }), + }, + }, + }), + state: { + active: { + default: { + background: background(theme.middle, "active"), + }, + hovered: { + background: background(theme.middle, "hovered"), + }, + }, + }, + }), + } +} diff --git a/styles/src/style_tree/tooltip.ts b/styles/src/style_tree/tooltip.ts new file mode 100644 index 0000000000..2fa5db04d4 --- /dev/null +++ b/styles/src/style_tree/tooltip.ts @@ -0,0 +1,22 @@ +import { ColorScheme } from "../theme/color_scheme" +import { background, border, text } from "./components" + +export default function tooltip(theme: ColorScheme): any { + return { + background: background(theme.middle), + border: border(theme.middle), + padding: { top: 4, bottom: 4, left: 8, right: 8 }, + margin: { top: 6, left: 6 }, + shadow: theme.popover_shadow, + corner_radius: 6, + text: text(theme.middle, "sans", { size: "xs" }), + keystroke: { + background: background(theme.middle, "on"), + corner_radius: 4, + margin: { left: 6 }, + padding: { left: 4, right: 4 }, + ...text(theme.middle, "mono", "on", { size: "xs", weight: "bold" }), + }, + max_text_width: 200, + } +} diff --git a/styles/src/style_tree/update_notification.ts b/styles/src/style_tree/update_notification.ts new file mode 100644 index 0000000000..d14e840450 --- /dev/null +++ b/styles/src/style_tree/update_notification.ts @@ -0,0 +1,39 @@ +import { ColorScheme } from "../theme/color_scheme" +import { foreground, text } from "./components" +import { interactive } from "../element" + +export default function update_notification(theme: ColorScheme): any { + const header_padding = 8 + + return { + message: { + ...text(theme.middle, "sans", { size: "xs" }), + margin: { left: header_padding, right: header_padding }, + }, + action_message: interactive({ + base: { + ...text(theme.middle, "sans", { size: "xs" }), + margin: { left: header_padding, top: 6, bottom: 6 }, + }, + state: { + hovered: { + color: foreground(theme.middle, "hovered"), + }, + }, + }), + dismiss_button: interactive({ + base: { + color: foreground(theme.middle), + icon_width: 8, + icon_height: 8, + button_width: 8, + button_height: 8, + }, + state: { + hovered: { + color: foreground(theme.middle, "hovered"), + }, + }, + }), + } +} diff --git a/styles/src/style_tree/welcome.ts b/styles/src/style_tree/welcome.ts new file mode 100644 index 0000000000..fad8dfe235 --- /dev/null +++ b/styles/src/style_tree/welcome.ts @@ -0,0 +1,155 @@ +import { ColorScheme } from "../theme/color_scheme" +import { with_opacity } from "../theme/color" +import { + border, + background, + foreground, + text, + TextProperties, + svg, +} from "./components" +import { interactive } from "../element" + +export default function welcome(theme: ColorScheme): any { + const checkbox_base = { + corner_radius: 4, + padding: { + left: 3, + right: 3, + top: 3, + bottom: 3, + }, + // shadow: theme.popover_shadow, + border: border(theme.highest), + margin: { + right: 8, + top: 5, + bottom: 5, + }, + } + + const interactive_text_size: TextProperties = { size: "sm" } + + return { + page_width: 320, + logo: svg( + foreground(theme.highest, "default"), + "icons/logo_96.svg", + 64, + 64 + ), + logo_subheading: { + ...text(theme.highest, "sans", "variant", { size: "md" }), + margin: { + top: 10, + bottom: 7, + }, + }, + button_group: { + margin: { + top: 8, + bottom: 16, + }, + }, + heading_group: { + margin: { + top: 8, + bottom: 12, + }, + }, + checkbox_group: { + border: border(theme.highest, "variant"), + background: with_opacity( + background(theme.highest, "hovered"), + 0.25 + ), + corner_radius: 4, + padding: { + left: 12, + top: 2, + bottom: 2, + }, + }, + button: interactive({ + base: { + background: background(theme.highest), + border: border(theme.highest, "active"), + corner_radius: 4, + margin: { + top: 4, + bottom: 4, + }, + padding: { + top: 3, + bottom: 3, + left: 7, + right: 7, + }, + ...text( + theme.highest, + "sans", + "default", + interactive_text_size + ), + }, + state: { + hovered: { + ...text( + theme.highest, + "sans", + "default", + interactive_text_size + ), + background: background(theme.highest, "hovered"), + }, + }, + }), + + usage_note: { + ...text(theme.highest, "sans", "variant", { size: "2xs" }), + padding: { + top: -4, + }, + }, + checkbox_container: { + margin: { + top: 4, + }, + padding: { + bottom: 8, + }, + }, + checkbox: { + label: { + ...text(theme.highest, "sans", interactive_text_size), + // Also supports margin, container, border, etc. + }, + icon: svg( + foreground(theme.highest, "on"), + "icons/check_12.svg", + 12, + 12 + ), + default: { + ...checkbox_base, + background: background(theme.highest, "default"), + border: border(theme.highest, "active"), + }, + checked: { + ...checkbox_base, + background: background(theme.highest, "hovered"), + border: border(theme.highest, "active"), + }, + hovered: { + ...checkbox_base, + background: background(theme.highest, "hovered"), + border: border(theme.highest, "active"), + }, + hovered_and_checked: { + ...checkbox_base, + background: background(theme.highest, "hovered"), + border: border(theme.highest, "active"), + }, + }, + } +} diff --git a/styles/src/style_tree/workspace.ts b/styles/src/style_tree/workspace.ts new file mode 100644 index 0000000000..0326de414a --- /dev/null +++ b/styles/src/style_tree/workspace.ts @@ -0,0 +1,190 @@ +import { ColorScheme } from "../theme/color_scheme" +import { with_opacity } from "../theme/color" +import { + background, + border, + border_color, + foreground, + svg, + text, +} from "./components" +import statusBar from "./status_bar" +import tabBar from "./tab_bar" +import { interactive } from "../element" + +import { titlebar } from "./titlebar" +export default function workspace(theme: ColorScheme): any { + const { is_light } = theme + + return { + background: background(theme.lowest), + blank_pane: { + logo_container: { + width: 256, + height: 256, + }, + logo: svg( + with_opacity("#000000", theme.is_light ? 0.6 : 0.8), + "icons/logo_96.svg", + 256, + 256 + ), + + logo_shadow: svg( + with_opacity( + theme.is_light + ? "#FFFFFF" + : theme.lowest.base.default.background, + theme.is_light ? 1 : 0.6 + ), + "icons/logo_96.svg", + 256, + 256 + ), + keyboard_hints: { + margin: { + top: 96, + }, + corner_radius: 4, + }, + keyboard_hint: interactive({ + base: { + ...text(theme.lowest, "sans", "variant", { size: "sm" }), + padding: { + top: 3, + left: 8, + right: 8, + bottom: 3, + }, + corner_radius: 8, + }, + state: { + hovered: { + ...text(theme.lowest, "sans", "active", { size: "sm" }), + }, + }, + }), + + keyboard_hint_width: 320, + }, + joining_project_avatar: { + corner_radius: 40, + width: 80, + }, + joining_project_message: { + padding: 12, + ...text(theme.lowest, "sans", { size: "lg" }), + }, + external_location_message: { + background: background(theme.middle, "accent"), + border: border(theme.middle, "accent"), + corner_radius: 6, + padding: 12, + margin: { bottom: 8, right: 8 }, + ...text(theme.middle, "sans", "accent", { size: "xs" }), + }, + leader_border_opacity: 0.7, + leader_border_width: 2.0, + tab_bar: tabBar(theme), + modal: { + margin: { + bottom: 52, + top: 52, + }, + cursor: "Arrow", + }, + zoomed_background: { + cursor: "Arrow", + background: is_light + ? with_opacity(background(theme.lowest), 0.8) + : with_opacity(background(theme.highest), 0.6), + }, + zoomed_pane_foreground: { + margin: 16, + shadow: theme.modal_shadow, + border: border(theme.lowest, { overlay: true }), + }, + zoomed_panel_foreground: { + margin: 16, + border: border(theme.lowest, { overlay: true }), + }, + dock: { + left: { + border: border(theme.lowest, { right: true }), + }, + bottom: { + border: border(theme.lowest, { top: true }), + }, + right: { + border: border(theme.lowest, { left: true }), + }, + }, + pane_divider: { + color: border_color(theme.lowest), + width: 1, + }, + status_bar: statusBar(theme), + titlebar: titlebar(theme), + toolbar: { + height: 34, + background: background(theme.highest), + border: border(theme.highest, { bottom: true }), + item_spacing: 8, + nav_button: interactive({ + base: { + color: foreground(theme.highest, "on"), + icon_width: 12, + button_width: 24, + corner_radius: 6, + }, + state: { + hovered: { + color: foreground(theme.highest, "on", "hovered"), + background: background(theme.highest, "on", "hovered"), + }, + disabled: { + color: foreground(theme.highest, "on", "disabled"), + }, + }, + }), + padding: { left: 8, right: 8, top: 4, bottom: 4 }, + }, + breadcrumb_height: 24, + breadcrumbs: interactive({ + base: { + ...text(theme.highest, "sans", "variant"), + corner_radius: 6, + padding: { + left: 6, + right: 6, + }, + }, + state: { + hovered: { + color: foreground(theme.highest, "on", "hovered"), + background: background(theme.highest, "on", "hovered"), + }, + }, + }), + disconnected_overlay: { + ...text(theme.lowest, "sans"), + background: with_opacity(background(theme.lowest), 0.8), + }, + notification: { + margin: { top: 10 }, + background: background(theme.middle), + corner_radius: 6, + padding: 12, + border: border(theme.middle), + shadow: theme.popover_shadow, + }, + notifications: { + width: 400, + margin: { right: 10, bottom: 10 }, + }, + drop_target_overlay_color: with_opacity( + foreground(theme.lowest, "variant"), + 0.5 + ), + } +} diff --git a/styles/src/system/lib/convert.ts b/styles/src/system/lib/convert.ts deleted file mode 100644 index 998f95a636..0000000000 --- a/styles/src/system/lib/convert.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** Converts a percentage scale value (0-100) to normalized scale (0-1) value. */ -export function percentageToNormalized(value: number) { - const normalized = value / 100 - return normalized -} - -/** Converts a normalized scale (0-1) value to a percentage scale (0-100) value. */ -export function normalizedToPercetage(value: number) { - const percentage = value * 100 - return percentage -} diff --git a/styles/src/system/lib/curve.ts b/styles/src/system/lib/curve.ts deleted file mode 100644 index b24f2948cf..0000000000 --- a/styles/src/system/lib/curve.ts +++ /dev/null @@ -1,26 +0,0 @@ -import bezier from "bezier-easing" -import { Curve } from "../ref/curves" - -/** - * Formats our Curve data structure into a bezier easing function. - * @param {Curve} curve - The curve to format. - * @param {Boolean} inverted - Whether or not to invert the curve. - * @returns {EasingFunction} The formatted easing function. - */ -export function curve(curve: Curve, inverted?: Boolean) { - if (inverted) { - return bezier( - curve.value[3], - curve.value[2], - curve.value[1], - curve.value[0] - ) - } - - return bezier( - curve.value[0], - curve.value[1], - curve.value[2], - curve.value[3] - ) -} diff --git a/styles/src/system/lib/generate.ts b/styles/src/system/lib/generate.ts deleted file mode 100644 index 40f7a9154c..0000000000 --- a/styles/src/system/lib/generate.ts +++ /dev/null @@ -1,159 +0,0 @@ -import bezier from "bezier-easing" -import chroma from "chroma-js" -import { Color, ColorFamily, ColorFamilyConfig, ColorScale } from "../types" -import { percentageToNormalized } from "./convert" -import { curve } from "./curve" - -// Re-export interface in a more standard format -export type EasingFunction = bezier.EasingFunction - -/** - * Generates a color, outputs it in multiple formats, and returns a variety of useful metadata. - * - * @param {EasingFunction} hueEasing - An easing function for the hue component of the color. - * @param {EasingFunction} saturationEasing - An easing function for the saturation component of the color. - * @param {EasingFunction} lightnessEasing - An easing function for the lightness component of the color. - * @param {ColorFamilyConfig} family - Configuration for the color family. - * @param {number} step - The current step. - * @param {number} steps - The total number of steps in the color scale. - * - * @returns {Color} The generated color, with its calculated contrast against black and white, as well as its LCH values, RGBA array, hexadecimal representation, and a flag indicating if it is light or dark. - */ -function generateColor( - hueEasing: EasingFunction, - saturationEasing: EasingFunction, - lightnessEasing: EasingFunction, - family: ColorFamilyConfig, - step: number, - steps: number -) { - const { hue, saturation, lightness } = family.color - - const stepHue = hueEasing(step / steps) * (hue.end - hue.start) + hue.start - const stepSaturation = - saturationEasing(step / steps) * (saturation.end - saturation.start) + - saturation.start - const stepLightness = - lightnessEasing(step / steps) * (lightness.end - lightness.start) + - lightness.start - - const color = chroma.hsl( - stepHue, - percentageToNormalized(stepSaturation), - percentageToNormalized(stepLightness) - ) - - const contrast = { - black: { - value: chroma.contrast(color, "black"), - aaPass: chroma.contrast(color, "black") >= 4.5, - aaaPass: chroma.contrast(color, "black") >= 7, - }, - white: { - value: chroma.contrast(color, "white"), - aaPass: chroma.contrast(color, "white") >= 4.5, - aaaPass: chroma.contrast(color, "white") >= 7, - }, - } - - const lch = color.lch() - const rgba = color.rgba() - const hex = color.hex() - - // 55 is a magic number. It's the lightness value at which we consider a color to be "light". - // It was picked by eye with some testing. We might want to use a more scientific approach in the future. - const isLight = lch[0] > 55 - - const result: Color = { - step, - lch, - hex, - rgba, - contrast, - isLight, - } - - return result -} - -/** - * Generates a color scale based on a color family configuration. - * - * @param {ColorFamilyConfig} config - The configuration for the color family. - * @param {Boolean} inverted - Specifies whether the color scale should be inverted or not. - * - * @returns {ColorScale} The generated color scale. - * - * @example - * ```ts - * const colorScale = generateColorScale({ - * name: "blue", - * color: { - * hue: { - * start: 210, - * end: 240, - * curve: "easeInOut" - * }, - * saturation: { - * start: 100, - * end: 100, - * curve: "easeInOut" - * }, - * lightness: { - * start: 50, - * end: 50, - * curve: "easeInOut" - * } - * } - * }); - * ``` - */ - -export function generateColorScale( - config: ColorFamilyConfig, - inverted: Boolean = false -) { - const { hue, saturation, lightness } = config.color - - // 101 steps means we get values from 0-100 - const NUM_STEPS = 101 - - const hueEasing = curve(hue.curve, inverted) - const saturationEasing = curve(saturation.curve, inverted) - const lightnessEasing = curve(lightness.curve, inverted) - - let scale: ColorScale = { - colors: [], - values: [], - } - - for (let i = 0; i < NUM_STEPS; i++) { - const color = generateColor( - hueEasing, - saturationEasing, - lightnessEasing, - config, - i, - NUM_STEPS - ) - - scale.colors.push(color) - scale.values.push(color.hex) - } - - return scale -} - -/** Generates a color family with a scale and an inverted scale. */ -export function generateColorFamily(config: ColorFamilyConfig) { - const scale = generateColorScale(config, false) - const invertedScale = generateColorScale(config, true) - - const family: ColorFamily = { - name: config.name, - scale, - invertedScale, - } - - return family -} diff --git a/styles/src/system/ref/color.ts b/styles/src/system/ref/color.ts deleted file mode 100644 index 6c0b53c35b..0000000000 --- a/styles/src/system/ref/color.ts +++ /dev/null @@ -1,445 +0,0 @@ -import { generateColorFamily } from "../lib/generate" -import { curve } from "./curves" - -// These are the source colors for the color scales in the system. -// These should never directly be used directly in components or themes as they generate thousands of lines of code. -// Instead, use the outputs from the reference palette which exports a smaller subset of colors. - -// Token or user-facing colors should use short, clear names and a 100-900 scale to match the font weight scale. - -// Light Gray ======================================== // - -export const lightgray = generateColorFamily({ - name: "lightgray", - color: { - hue: { - start: 210, - end: 210, - curve: curve.linear, - }, - saturation: { - start: 10, - end: 15, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 50, - curve: curve.linear, - }, - }, -}) - -// Light Dark ======================================== // - -export const darkgray = generateColorFamily({ - name: "darkgray", - color: { - hue: { - start: 210, - end: 210, - curve: curve.linear, - }, - saturation: { - start: 15, - end: 20, - curve: curve.saturation, - }, - lightness: { - start: 55, - end: 8, - curve: curve.linear, - }, - }, -}) - -// Red ======================================== // - -export const red = generateColorFamily({ - name: "red", - color: { - hue: { - start: 0, - end: 0, - curve: curve.linear, - }, - saturation: { - start: 95, - end: 75, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 25, - curve: curve.lightness, - }, - }, -}) - -// Sunset ======================================== // - -export const sunset = generateColorFamily({ - name: "sunset", - color: { - hue: { - start: 15, - end: 15, - curve: curve.linear, - }, - saturation: { - start: 100, - end: 90, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 25, - curve: curve.lightness, - }, - }, -}) - -// Orange ======================================== // - -export const orange = generateColorFamily({ - name: "orange", - color: { - hue: { - start: 25, - end: 25, - curve: curve.linear, - }, - saturation: { - start: 100, - end: 95, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Amber ======================================== // - -export const amber = generateColorFamily({ - name: "amber", - color: { - hue: { - start: 38, - end: 38, - curve: curve.linear, - }, - saturation: { - start: 100, - end: 100, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 18, - curve: curve.lightness, - }, - }, -}) - -// Yellow ======================================== // - -export const yellow = generateColorFamily({ - name: "yellow", - color: { - hue: { - start: 48, - end: 48, - curve: curve.linear, - }, - saturation: { - start: 90, - end: 100, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Lemon ======================================== // - -export const lemon = generateColorFamily({ - name: "lemon", - color: { - hue: { - start: 55, - end: 55, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 95, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Citron ======================================== // - -export const citron = generateColorFamily({ - name: "citron", - color: { - hue: { - start: 70, - end: 70, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 90, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Lime ======================================== // - -export const lime = generateColorFamily({ - name: "lime", - color: { - hue: { - start: 85, - end: 85, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 80, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 18, - curve: curve.lightness, - }, - }, -}) - -// Green ======================================== // - -export const green = generateColorFamily({ - name: "green", - color: { - hue: { - start: 108, - end: 108, - curve: curve.linear, - }, - saturation: { - start: 60, - end: 70, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 18, - curve: curve.lightness, - }, - }, -}) - -// Mint ======================================== // - -export const mint = generateColorFamily({ - name: "mint", - color: { - hue: { - start: 142, - end: 142, - curve: curve.linear, - }, - saturation: { - start: 60, - end: 75, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Cyan ======================================== // - -export const cyan = generateColorFamily({ - name: "cyan", - color: { - hue: { - start: 179, - end: 179, - curve: curve.linear, - }, - saturation: { - start: 70, - end: 80, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Sky ======================================== // - -export const sky = generateColorFamily({ - name: "sky", - color: { - hue: { - start: 195, - end: 205, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 90, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Blue ======================================== // - -export const blue = generateColorFamily({ - name: "blue", - color: { - hue: { - start: 218, - end: 218, - curve: curve.linear, - }, - saturation: { - start: 85, - end: 70, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 15, - curve: curve.lightness, - }, - }, -}) - -// Indigo ======================================== // - -export const indigo = generateColorFamily({ - name: "indigo", - color: { - hue: { - start: 245, - end: 245, - curve: curve.linear, - }, - saturation: { - start: 60, - end: 50, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 22, - curve: curve.lightness, - }, - }, -}) - -// Purple ======================================== // - -export const purple = generateColorFamily({ - name: "purple", - color: { - hue: { - start: 260, - end: 270, - curve: curve.linear, - }, - saturation: { - start: 65, - end: 55, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 20, - curve: curve.lightness, - }, - }, -}) - -// Pink ======================================== // - -export const pink = generateColorFamily({ - name: "pink", - color: { - hue: { - start: 320, - end: 330, - curve: curve.linear, - }, - saturation: { - start: 70, - end: 65, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 32, - curve: curve.lightness, - }, - }, -}) - -// Rose ======================================== // - -export const rose = generateColorFamily({ - name: "rose", - color: { - hue: { - start: 345, - end: 345, - curve: curve.linear, - }, - saturation: { - start: 90, - end: 70, - curve: curve.saturation, - }, - lightness: { - start: 97, - end: 32, - curve: curve.lightness, - }, - }, -}) diff --git a/styles/src/system/ref/curves.ts b/styles/src/system/ref/curves.ts deleted file mode 100644 index 02002dbe9b..0000000000 --- a/styles/src/system/ref/curves.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface Curve { - name: string - value: number[] -} - -export interface Curves { - lightness: Curve - saturation: Curve - linear: Curve -} - -export const curve: Curves = { - lightness: { - name: "lightnessCurve", - value: [0.2, 0, 0.75, 1.0], - }, - saturation: { - name: "saturationCurve", - value: [0.67, 0.6, 0.55, 1.0], - }, - linear: { - name: "linear", - value: [0.5, 0.5, 0.5, 0.5], - }, -} diff --git a/styles/src/system/system.ts b/styles/src/system/system.ts deleted file mode 100644 index 619b0795c8..0000000000 --- a/styles/src/system/system.ts +++ /dev/null @@ -1,32 +0,0 @@ -import chroma from "chroma-js" -import * as colorFamily from "./ref/color" - -const color = { - lightgray: chroma - .scale(colorFamily.lightgray.scale.values) - .mode("lch") - .colors(9), - darkgray: chroma - .scale(colorFamily.darkgray.scale.values) - .mode("lch") - .colors(9), - red: chroma.scale(colorFamily.red.scale.values).mode("lch").colors(9), - sunset: chroma.scale(colorFamily.sunset.scale.values).mode("lch").colors(9), - orange: chroma.scale(colorFamily.orange.scale.values).mode("lch").colors(9), - amber: chroma.scale(colorFamily.amber.scale.values).mode("lch").colors(9), - yellow: chroma.scale(colorFamily.yellow.scale.values).mode("lch").colors(9), - lemon: chroma.scale(colorFamily.lemon.scale.values).mode("lch").colors(9), - citron: chroma.scale(colorFamily.citron.scale.values).mode("lch").colors(9), - lime: chroma.scale(colorFamily.lime.scale.values).mode("lch").colors(9), - green: chroma.scale(colorFamily.green.scale.values).mode("lch").colors(9), - mint: chroma.scale(colorFamily.mint.scale.values).mode("lch").colors(9), - cyan: chroma.scale(colorFamily.cyan.scale.values).mode("lch").colors(9), - sky: chroma.scale(colorFamily.sky.scale.values).mode("lch").colors(9), - blue: chroma.scale(colorFamily.blue.scale.values).mode("lch").colors(9), - indigo: chroma.scale(colorFamily.indigo.scale.values).mode("lch").colors(9), - purple: chroma.scale(colorFamily.purple.scale.values).mode("lch").colors(9), - pink: chroma.scale(colorFamily.pink.scale.values).mode("lch").colors(9), - rose: chroma.scale(colorFamily.rose.scale.values).mode("lch").colors(9), -} - -export { color } diff --git a/styles/src/system/types.ts b/styles/src/system/types.ts deleted file mode 100644 index 8de65a37eb..0000000000 --- a/styles/src/system/types.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Curve } from "./ref/curves" - -export interface ColorAccessibilityValue { - value: number - aaPass: boolean - aaaPass: boolean -} - -/** - * Calculates the color contrast between a specified color and its corresponding background and foreground colors. - * - * @note This implementation is currently basic – Currently we only calculate contrasts against black and white, in the future will allow for dynamic color contrast calculation based on the colors present in a given palette. - * @note The goal is to align with WCAG3 accessibility standards as they become stabilized. See the [WCAG 3 Introduction](https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/) for more information. - */ -export interface ColorAccessibility { - black: ColorAccessibilityValue - white: ColorAccessibilityValue -} - -export type Color = { - step: number - contrast: ColorAccessibility - hex: string - lch: number[] - rgba: number[] - isLight: boolean -} - -export interface ColorScale { - colors: Color[] - // An array of hex values for each color in the scale - values: string[] -} - -export type ColorFamily = { - name: string - scale: ColorScale - invertedScale: ColorScale -} - -export interface ColorFamilyHue { - start: number - end: number - curve: Curve -} - -export interface ColorFamilySaturation { - start: number - end: number - curve: Curve -} - -export interface ColorFamilyLightness { - start: number - end: number - curve: Curve -} - -export interface ColorFamilyConfig { - name: string - color: { - hue: ColorFamilyHue - saturation: ColorFamilySaturation - lightness: ColorFamilyLightness - } -} diff --git a/styles/src/theme/color.ts b/styles/src/theme/color.ts index 58ee4ccc7c..83c2107483 100644 --- a/styles/src/theme/color.ts +++ b/styles/src/theme/color.ts @@ -1,5 +1,5 @@ import chroma from "chroma-js" -export function withOpacity(color: string, opacity: number): string { +export function with_opacity(color: string, opacity: number): string { return chroma(color).alpha(opacity).hex() } diff --git a/styles/src/theme/colorScheme.ts b/styles/src/theme/colorScheme.ts deleted file mode 100644 index 9a81073086..0000000000 --- a/styles/src/theme/colorScheme.ts +++ /dev/null @@ -1,286 +0,0 @@ -import { Scale, Color } from "chroma-js" -import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax" -export { Syntax, ThemeSyntax, SyntaxHighlightStyle } -import { - ThemeConfig, - ThemeAppearance, - ThemeConfigInputColors, -} from "./themeConfig" -import { getRamps } from "./ramps" - -export interface ColorScheme { - name: string - isLight: boolean - - lowest: Layer - middle: Layer - highest: Layer - - ramps: RampSet - - popoverShadow: Shadow - modalShadow: Shadow - - players: Players - syntax?: Partial -} - -export interface Meta { - name: string - author: string - url: string - license: License -} - -export interface License { - SPDX: SPDXExpression -} - -// License name -> License text -export interface Licenses { - [key: string]: string -} - -// FIXME: Add support for the SPDX expression syntax -export type SPDXExpression = "MIT" - -export interface Player { - cursor: string - selection: string -} - -export interface Players { - "0": Player - "1": Player - "2": Player - "3": Player - "4": Player - "5": Player - "6": Player - "7": Player -} - -export interface Shadow { - blur: number - color: string - offset: number[] -} - -export type StyleSets = keyof Layer -export interface Layer { - base: StyleSet - variant: StyleSet - on: StyleSet - accent: StyleSet - positive: StyleSet - warning: StyleSet - negative: StyleSet -} - -export interface RampSet { - neutral: Scale - red: Scale - orange: Scale - yellow: Scale - green: Scale - cyan: Scale - blue: Scale - violet: Scale - magenta: Scale -} - -export type Styles = keyof StyleSet -export interface StyleSet { - default: Style - active: Style - disabled: Style - hovered: Style - pressed: Style - inverted: Style -} - -export interface Style { - background: string - border: string - foreground: string -} - -export function createColorScheme(theme: ThemeConfig): ColorScheme { - const { - name, - appearance, - inputColor, - override: { syntax }, - } = theme - - const isLight = appearance === ThemeAppearance.Light - const colorRamps: ThemeConfigInputColors = inputColor - - // Chromajs scales from 0 to 1 flipped if isLight is true - const ramps = getRamps(isLight, colorRamps) - const lowest = lowestLayer(ramps) - const middle = middleLayer(ramps) - const highest = highestLayer(ramps) - - const popoverShadow = { - blur: 4, - color: ramps - .neutral(isLight ? 7 : 0) - .darken() - .alpha(0.2) - .hex(), // TODO used blend previously. Replace with something else - offset: [1, 2], - } - - const modalShadow = { - blur: 16, - color: ramps - .neutral(isLight ? 7 : 0) - .darken() - .alpha(0.2) - .hex(), // TODO used blend previously. Replace with something else - offset: [0, 2], - } - - const players = { - "0": player(ramps.blue), - "1": player(ramps.green), - "2": player(ramps.magenta), - "3": player(ramps.orange), - "4": player(ramps.violet), - "5": player(ramps.cyan), - "6": player(ramps.red), - "7": player(ramps.yellow), - } - - return { - name, - isLight, - - ramps, - - lowest, - middle, - highest, - - popoverShadow, - modalShadow, - - players, - syntax, - } -} - -function player(ramp: Scale): Player { - return { - selection: ramp(0.5).alpha(0.24).hex(), - cursor: ramp(0.5).hex(), - } -} - -function lowestLayer(ramps: RampSet): Layer { - return { - base: buildStyleSet(ramps.neutral, 0.2, 1), - variant: buildStyleSet(ramps.neutral, 0.2, 0.7), - on: buildStyleSet(ramps.neutral, 0.1, 1), - accent: buildStyleSet(ramps.blue, 0.1, 0.5), - positive: buildStyleSet(ramps.green, 0.1, 0.5), - warning: buildStyleSet(ramps.yellow, 0.1, 0.5), - negative: buildStyleSet(ramps.red, 0.1, 0.5), - } -} - -function middleLayer(ramps: RampSet): Layer { - return { - base: buildStyleSet(ramps.neutral, 0.1, 1), - variant: buildStyleSet(ramps.neutral, 0.1, 0.7), - on: buildStyleSet(ramps.neutral, 0, 1), - accent: buildStyleSet(ramps.blue, 0.1, 0.5), - positive: buildStyleSet(ramps.green, 0.1, 0.5), - warning: buildStyleSet(ramps.yellow, 0.1, 0.5), - negative: buildStyleSet(ramps.red, 0.1, 0.5), - } -} - -function highestLayer(ramps: RampSet): Layer { - return { - base: buildStyleSet(ramps.neutral, 0, 1), - variant: buildStyleSet(ramps.neutral, 0, 0.7), - on: buildStyleSet(ramps.neutral, 0.1, 1), - accent: buildStyleSet(ramps.blue, 0.1, 0.5), - positive: buildStyleSet(ramps.green, 0.1, 0.5), - warning: buildStyleSet(ramps.yellow, 0.1, 0.5), - negative: buildStyleSet(ramps.red, 0.1, 0.5), - } -} - -function buildStyleSet( - ramp: Scale, - backgroundBase: number, - foregroundBase: number, - step: number = 0.08 -): StyleSet { - let styleDefinitions = buildStyleDefinition( - backgroundBase, - foregroundBase, - step - ) - - function colorString(indexOrColor: number | Color): string { - if (typeof indexOrColor === "number") { - return ramp(indexOrColor).hex() - } else { - return indexOrColor.hex() - } - } - - function buildStyle(style: Styles): Style { - return { - background: colorString(styleDefinitions.background[style]), - border: colorString(styleDefinitions.border[style]), - foreground: colorString(styleDefinitions.foreground[style]), - } - } - - return { - default: buildStyle("default"), - hovered: buildStyle("hovered"), - pressed: buildStyle("pressed"), - active: buildStyle("active"), - disabled: buildStyle("disabled"), - inverted: buildStyle("inverted"), - } -} - -function buildStyleDefinition( - bgBase: number, - fgBase: number, - step: number = 0.08 -) { - return { - background: { - default: bgBase, - hovered: bgBase + step, - pressed: bgBase + step * 1.5, - active: bgBase + step * 2.2, - disabled: bgBase, - inverted: fgBase + step * 6, - }, - border: { - default: bgBase + step * 1, - hovered: bgBase + step, - pressed: bgBase + step, - active: bgBase + step * 3, - disabled: bgBase + step * 0.5, - inverted: bgBase - step * 3, - }, - foreground: { - default: fgBase, - hovered: fgBase, - pressed: fgBase, - active: fgBase + step * 6, - disabled: bgBase + step * 4, - inverted: bgBase + step * 2, - }, - } -} diff --git a/styles/src/theme/color_scheme.ts b/styles/src/theme/color_scheme.ts new file mode 100644 index 0000000000..933c616053 --- /dev/null +++ b/styles/src/theme/color_scheme.ts @@ -0,0 +1,282 @@ +import { Scale, Color } from "chroma-js" +import { Syntax, ThemeSyntax, SyntaxHighlightStyle } from "./syntax" +export { Syntax, ThemeSyntax, SyntaxHighlightStyle } +import { + ThemeConfig, + ThemeAppearance, + ThemeConfigInputColors, +} from "./theme_config" +import { get_ramps } from "./ramps" + +export interface ColorScheme { + name: string + is_light: boolean + + lowest: Layer + middle: Layer + highest: Layer + + ramps: RampSet + + popover_shadow: Shadow + modal_shadow: Shadow + + players: Players + syntax?: Partial +} + +export interface Meta { + name: string + author: string + url: string + license: License +} + +export interface License { + SPDX: SPDXExpression +} + +// License name -> License text +export interface Licenses { + [key: string]: string +} + +// FIXME: Add support for the SPDX expression syntax +export type SPDXExpression = "MIT" + +export interface Player { + cursor: string + selection: string +} + +export interface Players { + "0": Player + "1": Player + "2": Player + "3": Player + "4": Player + "5": Player + "6": Player + "7": Player +} + +export interface Shadow { + blur: number + color: string + offset: number[] +} + +export type StyleSets = keyof Layer +export interface Layer { + base: StyleSet + variant: StyleSet + on: StyleSet + accent: StyleSet + positive: StyleSet + warning: StyleSet + negative: StyleSet +} + +export interface RampSet { + neutral: Scale + red: Scale + orange: Scale + yellow: Scale + green: Scale + cyan: Scale + blue: Scale + violet: Scale + magenta: Scale +} + +export type Styles = keyof StyleSet +export interface StyleSet { + default: Style + active: Style + disabled: Style + hovered: Style + pressed: Style + inverted: Style +} + +export interface Style { + background: string + border: string + foreground: string +} + +export function create_color_scheme(theme: ThemeConfig): ColorScheme { + const { + name, + appearance, + input_color, + override: { syntax }, + } = theme + + const is_light = appearance === ThemeAppearance.Light + const color_ramps: ThemeConfigInputColors = input_color + + // Chromajs scales from 0 to 1 flipped if is_light is true + const ramps = get_ramps(is_light, color_ramps) + const lowest = lowest_layer(ramps) + const middle = middle_layer(ramps) + const highest = highest_layer(ramps) + + const popover_shadow = { + blur: 4, + color: ramps + .neutral(is_light ? 7 : 0) + .darken() + .alpha(0.2) + .hex(), // TODO used blend previously. Replace with something else + offset: [1, 2], + } + + const modal_shadow = { + blur: 16, + color: ramps + .neutral(is_light ? 7 : 0) + .darken() + .alpha(0.2) + .hex(), // TODO used blend previously. Replace with something else + offset: [0, 2], + } + + const players = { + "0": player(ramps.blue), + "1": player(ramps.green), + "2": player(ramps.magenta), + "3": player(ramps.orange), + "4": player(ramps.violet), + "5": player(ramps.cyan), + "6": player(ramps.red), + "7": player(ramps.yellow), + } + + return { + name, + is_light, + + ramps, + + lowest, + middle, + highest, + + popover_shadow, + modal_shadow, + + players, + syntax, + } +} + +function player(ramp: Scale): Player { + return { + selection: ramp(0.5).alpha(0.24).hex(), + cursor: ramp(0.5).hex(), + } +} + +function lowest_layer(ramps: RampSet): Layer { + return { + base: build_style_set(ramps.neutral, 0.2, 1), + variant: build_style_set(ramps.neutral, 0.2, 0.7), + on: build_style_set(ramps.neutral, 0.1, 1), + accent: build_style_set(ramps.blue, 0.1, 0.5), + positive: build_style_set(ramps.green, 0.1, 0.5), + warning: build_style_set(ramps.yellow, 0.1, 0.5), + negative: build_style_set(ramps.red, 0.1, 0.5), + } +} + +function middle_layer(ramps: RampSet): Layer { + return { + base: build_style_set(ramps.neutral, 0.1, 1), + variant: build_style_set(ramps.neutral, 0.1, 0.7), + on: build_style_set(ramps.neutral, 0, 1), + accent: build_style_set(ramps.blue, 0.1, 0.5), + positive: build_style_set(ramps.green, 0.1, 0.5), + warning: build_style_set(ramps.yellow, 0.1, 0.5), + negative: build_style_set(ramps.red, 0.1, 0.5), + } +} + +function highest_layer(ramps: RampSet): Layer { + return { + base: build_style_set(ramps.neutral, 0, 1), + variant: build_style_set(ramps.neutral, 0, 0.7), + on: build_style_set(ramps.neutral, 0.1, 1), + accent: build_style_set(ramps.blue, 0.1, 0.5), + positive: build_style_set(ramps.green, 0.1, 0.5), + warning: build_style_set(ramps.yellow, 0.1, 0.5), + negative: build_style_set(ramps.red, 0.1, 0.5), + } +} + +function build_style_set( + ramp: Scale, + background_base: number, + foreground_base: number, + step = 0.08 +): StyleSet { + const style_definitions = build_style_definition( + background_base, + foreground_base, + step + ) + + function color_string(index_or_color: number | Color): string { + if (typeof index_or_color === "number") { + return ramp(index_or_color).hex() + } else { + return index_or_color.hex() + } + } + + function build_style(style: Styles): Style { + return { + background: color_string(style_definitions.background[style]), + border: color_string(style_definitions.border[style]), + foreground: color_string(style_definitions.foreground[style]), + } + } + + return { + default: build_style("default"), + hovered: build_style("hovered"), + pressed: build_style("pressed"), + active: build_style("active"), + disabled: build_style("disabled"), + inverted: build_style("inverted"), + } +} + +function build_style_definition(bg_base: number, fg_base: number, step = 0.08) { + return { + background: { + default: bg_base, + hovered: bg_base + step, + pressed: bg_base + step * 1.5, + active: bg_base + step * 2.2, + disabled: bg_base, + inverted: fg_base + step * 6, + }, + border: { + default: bg_base + step * 1, + hovered: bg_base + step, + pressed: bg_base + step, + active: bg_base + step * 3, + disabled: bg_base + step * 0.5, + inverted: bg_base - step * 3, + }, + foreground: { + default: fg_base, + hovered: fg_base, + pressed: fg_base, + active: fg_base + step * 6, + disabled: bg_base + step * 4, + inverted: bg_base + step * 2, + }, + } +} diff --git a/styles/src/theme/index.ts b/styles/src/theme/index.ts index 2bf625521c..22287bf669 100644 --- a/styles/src/theme/index.ts +++ b/styles/src/theme/index.ts @@ -1,4 +1,4 @@ -export * from "./colorScheme" +export * from "./color_scheme" export * from "./ramps" export * from "./syntax" -export * from "./themeConfig" +export * from "./theme_config" diff --git a/styles/src/theme/ramps.ts b/styles/src/theme/ramps.ts index f8c44ba3f9..118d0c7274 100644 --- a/styles/src/theme/ramps.ts +++ b/styles/src/theme/ramps.ts @@ -1,14 +1,14 @@ import chroma, { Color, Scale } from "chroma-js" -import { RampSet } from "./colorScheme" +import { RampSet } from "./color_scheme" import { ThemeConfigInputColors, ThemeConfigInputColorsKeys, -} from "./themeConfig" +} from "./theme_config" -export function colorRamp(color: Color): Scale { - let endColor = color.desaturate(1).brighten(5) - let startColor = color.desaturate(1).darken(4) - return chroma.scale([startColor, color, endColor]).mode("lab") +export function color_ramp(color: Color): Scale { + const end_color = color.desaturate(1).brighten(5) + const start_color = color.desaturate(1).darken(4) + return chroma.scale([start_color, color, end_color]).mode("lab") } /** @@ -18,29 +18,29 @@ export function colorRamp(color: Color): Scale { theme so that we don't modify the passed in ramps. This combined with an error in the type definitions for chroma js means we have to cast the colors function to any in order to get the colors back out from the original ramps. - * @param isLight - * @param colorRamps - * @returns + * @param is_light + * @param color_ramps + * @returns */ -export function getRamps( - isLight: boolean, - colorRamps: ThemeConfigInputColors +export function get_ramps( + is_light: boolean, + color_ramps: ThemeConfigInputColors ): RampSet { - const ramps: RampSet = {} as any - const colorsKeys = Object.keys(colorRamps) as ThemeConfigInputColorsKeys[] + const ramps: RampSet = {} as any // eslint-disable-line @typescript-eslint/no-explicit-any + const color_keys = Object.keys(color_ramps) as ThemeConfigInputColorsKeys[] - if (isLight) { - for (const rampName of colorsKeys) { - ramps[rampName] = chroma.scale( - colorRamps[rampName].colors(100).reverse() + if (is_light) { + for (const ramp_name of color_keys) { + ramps[ramp_name] = chroma.scale( + color_ramps[ramp_name].colors(100).reverse() ) } - ramps.neutral = chroma.scale(colorRamps.neutral.colors(100).reverse()) + ramps.neutral = chroma.scale(color_ramps.neutral.colors(100).reverse()) } else { - for (const rampName of colorsKeys) { - ramps[rampName] = chroma.scale(colorRamps[rampName].colors(100)) + for (const ramp_name of color_keys) { + ramps[ramp_name] = chroma.scale(color_ramps[ramp_name].colors(100)) } - ramps.neutral = chroma.scale(colorRamps.neutral.colors(100)) + ramps.neutral = chroma.scale(color_ramps.neutral.colors(100)) } return ramps diff --git a/styles/src/theme/syntax.ts b/styles/src/theme/syntax.ts index 369fceb070..c0d68e418e 100644 --- a/styles/src/theme/syntax.ts +++ b/styles/src/theme/syntax.ts @@ -1,6 +1,6 @@ import deepmerge from "deepmerge" -import { FontWeight, fontWeights } from "../common" -import { ColorScheme } from "./colorScheme" +import { FontWeight, font_weights } from "../common" +import { ColorScheme } from "./color_scheme" import chroma from "chroma-js" export interface SyntaxHighlightStyle { @@ -17,13 +17,14 @@ export interface Syntax { "comment.doc": SyntaxHighlightStyle primary: SyntaxHighlightStyle predictive: SyntaxHighlightStyle + hint: SyntaxHighlightStyle // === Formatted Text ====== / emphasis: SyntaxHighlightStyle "emphasis.strong": SyntaxHighlightStyle title: SyntaxHighlightStyle - linkUri: SyntaxHighlightStyle - linkText: SyntaxHighlightStyle + link_uri: SyntaxHighlightStyle + link_text: SyntaxHighlightStyle /** md: indented_code_block, fenced_code_block, code_span */ "text.literal": SyntaxHighlightStyle @@ -56,7 +57,7 @@ export interface Syntax { // == Types ====== / // We allow Function here because all JS objects literals have this property - constructor: SyntaxHighlightStyle | Function + constructor: SyntaxHighlightStyle | Function // eslint-disable-line @typescript-eslint/ban-types variant: SyntaxHighlightStyle type: SyntaxHighlightStyle // js: predefined_type @@ -116,13 +117,13 @@ export interface Syntax { export type ThemeSyntax = Partial -const defaultSyntaxHighlightStyle: Omit = { +const default_syntax_highlight_style: Omit = { weight: "normal", underline: false, italic: false, } -function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { +function build_default_syntax(color_scheme: ColorScheme): Syntax { // Make a temporary object that is allowed to be missing // the "color" property for each style const syntax: { @@ -132,7 +133,7 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { // then spread the default to each style for (const key of Object.keys({} as Syntax)) { syntax[key as keyof Syntax] = { - ...defaultSyntaxHighlightStyle, + ...default_syntax_highlight_style, } } @@ -140,35 +141,46 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { // predictive color distinct from any other color in the theme const predictive = chroma .mix( - colorScheme.ramps.neutral(0.4).hex(), - colorScheme.ramps.blue(0.4).hex(), + color_scheme.ramps.neutral(0.4).hex(), + color_scheme.ramps.blue(0.4).hex(), + 0.45, + "lch" + ) + .hex() + // Mix the neutral and green colors to get a + // hint color distinct from any other color in the theme + const hint = chroma + .mix( + color_scheme.ramps.neutral(0.6).hex(), + color_scheme.ramps.blue(0.4).hex(), 0.45, "lch" ) .hex() const color = { - primary: colorScheme.ramps.neutral(1).hex(), - comment: colorScheme.ramps.neutral(0.71).hex(), - punctuation: colorScheme.ramps.neutral(0.86).hex(), + primary: color_scheme.ramps.neutral(1).hex(), + comment: color_scheme.ramps.neutral(0.71).hex(), + punctuation: color_scheme.ramps.neutral(0.86).hex(), predictive: predictive, - emphasis: colorScheme.ramps.blue(0.5).hex(), - string: colorScheme.ramps.orange(0.5).hex(), - function: colorScheme.ramps.yellow(0.5).hex(), - type: colorScheme.ramps.cyan(0.5).hex(), - constructor: colorScheme.ramps.blue(0.5).hex(), - variant: colorScheme.ramps.blue(0.5).hex(), - property: colorScheme.ramps.blue(0.5).hex(), - enum: colorScheme.ramps.orange(0.5).hex(), - operator: colorScheme.ramps.orange(0.5).hex(), - number: colorScheme.ramps.green(0.5).hex(), - boolean: colorScheme.ramps.green(0.5).hex(), - constant: colorScheme.ramps.green(0.5).hex(), - keyword: colorScheme.ramps.blue(0.5).hex(), + hint: hint, + emphasis: color_scheme.ramps.blue(0.5).hex(), + string: color_scheme.ramps.orange(0.5).hex(), + function: color_scheme.ramps.yellow(0.5).hex(), + type: color_scheme.ramps.cyan(0.5).hex(), + constructor: color_scheme.ramps.blue(0.5).hex(), + variant: color_scheme.ramps.blue(0.5).hex(), + property: color_scheme.ramps.blue(0.5).hex(), + enum: color_scheme.ramps.orange(0.5).hex(), + operator: color_scheme.ramps.orange(0.5).hex(), + number: color_scheme.ramps.green(0.5).hex(), + boolean: color_scheme.ramps.green(0.5).hex(), + constant: color_scheme.ramps.green(0.5).hex(), + keyword: color_scheme.ramps.blue(0.5).hex(), } // Then assign colors and use Syntax to enforce each style getting it's own color - const defaultSyntax: Syntax = { + const default_syntax: Syntax = { ...syntax, comment: { color: color.comment, @@ -183,23 +195,27 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { color: color.predictive, italic: true, }, + hint: { + color: color.hint, + weight: font_weights.bold, + }, emphasis: { color: color.emphasis, }, "emphasis.strong": { color: color.emphasis, - weight: fontWeights.bold, + weight: font_weights.bold, }, title: { color: color.primary, - weight: fontWeights.bold, + weight: font_weights.bold, }, - linkUri: { - color: colorScheme.ramps.green(0.5).hex(), + link_uri: { + color: color_scheme.ramps.green(0.5).hex(), underline: true, }, - linkText: { - color: colorScheme.ramps.orange(0.5).hex(), + link_text: { + color: color_scheme.ramps.orange(0.5).hex(), italic: true, }, "text.literal": { @@ -215,7 +231,7 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { color: color.punctuation, }, "punctuation.special": { - color: colorScheme.ramps.neutral(0.86).hex(), + color: color_scheme.ramps.neutral(0.86).hex(), }, "punctuation.list_marker": { color: color.punctuation, @@ -236,10 +252,10 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { color: color.string, }, constructor: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, variant: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, type: { color: color.type, @@ -248,16 +264,16 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { color: color.primary, }, label: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, tag: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, attribute: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, property: { - color: colorScheme.ramps.blue(0.5).hex(), + color: color_scheme.ramps.blue(0.5).hex(), }, constant: { color: color.constant, @@ -288,17 +304,20 @@ function buildDefaultSyntax(colorScheme: ColorScheme): Syntax { }, } - return defaultSyntax + return default_syntax } -function mergeSyntax(defaultSyntax: Syntax, colorScheme: ColorScheme): Syntax { - if (!colorScheme.syntax) { - return defaultSyntax +function merge_syntax( + default_syntax: Syntax, + color_scheme: ColorScheme +): Syntax { + if (!color_scheme.syntax) { + return default_syntax } return deepmerge>( - defaultSyntax, - colorScheme.syntax, + default_syntax, + color_scheme.syntax, { arrayMerge: (destinationArray, sourceArray) => [ ...destinationArray, @@ -308,10 +327,10 @@ function mergeSyntax(defaultSyntax: Syntax, colorScheme: ColorScheme): Syntax { ) } -export function buildSyntax(colorScheme: ColorScheme): Syntax { - const defaultSyntax: Syntax = buildDefaultSyntax(colorScheme) +export function build_syntax(color_scheme: ColorScheme): Syntax { + const default_syntax: Syntax = build_default_syntax(color_scheme) - const syntax = mergeSyntax(defaultSyntax, colorScheme) + const syntax = merge_syntax(default_syntax, color_scheme) return syntax } diff --git a/styles/src/theme/themeConfig.ts b/styles/src/theme/theme_config.ts similarity index 93% rename from styles/src/theme/themeConfig.ts rename to styles/src/theme/theme_config.ts index 176ae83bb7..26462bee6d 100644 --- a/styles/src/theme/themeConfig.ts +++ b/styles/src/theme/theme_config.ts @@ -17,15 +17,15 @@ interface ThemeMeta { * * Example: `MIT` */ - licenseType?: string | ThemeLicenseType - licenseUrl?: string - licenseFile: string - themeUrl?: string + license_type?: string | ThemeLicenseType + license_url?: string + license_file: string + theme_url?: string } export type ThemeFamilyMeta = Pick< ThemeMeta, - "name" | "author" | "licenseType" | "licenseUrl" + "name" | "author" | "license_type" | "license_url" > export interface ThemeConfigInputColors { @@ -62,7 +62,7 @@ interface ThemeConfigOverrides { } type ThemeConfigProperties = ThemeMeta & { - inputColor: ThemeConfigInputColors + input_color: ThemeConfigInputColors override: ThemeConfigOverrides } diff --git a/styles/src/theme/tokens/colorScheme.ts b/styles/src/theme/tokens/colorScheme.ts deleted file mode 100644 index bc53ca802a..0000000000 --- a/styles/src/theme/tokens/colorScheme.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - SingleBoxShadowToken, - SingleColorToken, - SingleOtherToken, - TokenTypes, -} from "@tokens-studio/types" -import { - ColorScheme, - Shadow, - SyntaxHighlightStyle, - ThemeSyntax, -} from "../colorScheme" -import { LayerToken, layerToken } from "./layer" -import { PlayersToken, playersToken } from "./players" -import { colorToken } from "./token" -import { Syntax } from "../syntax" -import editor from "../../styleTree/editor" - -interface ColorSchemeTokens { - name: SingleOtherToken - appearance: SingleOtherToken - lowest: LayerToken - middle: LayerToken - highest: LayerToken - players: PlayersToken - popoverShadow: SingleBoxShadowToken - modalShadow: SingleBoxShadowToken - syntax?: Partial -} - -const createShadowToken = ( - shadow: Shadow, - tokenName: string -): SingleBoxShadowToken => { - return { - name: tokenName, - type: TokenTypes.BOX_SHADOW, - value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`, - } -} - -const popoverShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => { - const shadow = colorScheme.popoverShadow - return createShadowToken(shadow, "popoverShadow") -} - -const modalShadowToken = (colorScheme: ColorScheme): SingleBoxShadowToken => { - const shadow = colorScheme.modalShadow - return createShadowToken(shadow, "modalShadow") -} - -type ThemeSyntaxColorTokens = Record - -function syntaxHighlightStyleColorTokens( - syntax: Syntax -): ThemeSyntaxColorTokens { - const styleKeys = Object.keys(syntax) as (keyof Syntax)[] - - return styleKeys.reduce((acc, styleKey) => { - // Hack: The type of a style could be "Function" - // This can happen because we have a "constructor" property on the syntax object - // and a "constructor" property on the prototype of the syntax object - // To work around this just assert that the type of the style is not a function - if (!syntax[styleKey] || typeof syntax[styleKey] === "function") - return acc - const { color } = syntax[styleKey] as Required - return { ...acc, [styleKey]: colorToken(styleKey, color) } - }, {} as ThemeSyntaxColorTokens) -} - -const syntaxTokens = ( - colorScheme: ColorScheme -): ColorSchemeTokens["syntax"] => { - const syntax = editor(colorScheme).syntax - - return syntaxHighlightStyleColorTokens(syntax) -} - -export function colorSchemeTokens(colorScheme: ColorScheme): ColorSchemeTokens { - return { - name: { - name: "themeName", - value: colorScheme.name, - type: TokenTypes.OTHER, - }, - appearance: { - name: "themeAppearance", - value: colorScheme.isLight ? "light" : "dark", - type: TokenTypes.OTHER, - }, - lowest: layerToken(colorScheme.lowest, "lowest"), - middle: layerToken(colorScheme.middle, "middle"), - highest: layerToken(colorScheme.highest, "highest"), - popoverShadow: popoverShadowToken(colorScheme), - modalShadow: modalShadowToken(colorScheme), - players: playersToken(colorScheme), - syntax: syntaxTokens(colorScheme), - } -} diff --git a/styles/src/theme/tokens/color_scheme.ts b/styles/src/theme/tokens/color_scheme.ts new file mode 100644 index 0000000000..a8ce4ec2d2 --- /dev/null +++ b/styles/src/theme/tokens/color_scheme.ts @@ -0,0 +1,97 @@ +import { + SingleBoxShadowToken, + SingleColorToken, + SingleOtherToken, + TokenTypes, +} from "@tokens-studio/types" +import { + ColorScheme, + Shadow, + SyntaxHighlightStyle, + ThemeSyntax, +} from "../color_scheme" +import { LayerToken, layer_token } from "./layer" +import { PlayersToken, players_token } from "./players" +import { color_token } from "./token" +import { Syntax } from "../syntax" +import editor from "../../style_tree/editor" + +interface ColorSchemeTokens { + name: SingleOtherToken + appearance: SingleOtherToken + lowest: LayerToken + middle: LayerToken + highest: LayerToken + players: PlayersToken + popover_shadow: SingleBoxShadowToken + modal_shadow: SingleBoxShadowToken + syntax?: Partial +} + +const create_shadow_token = ( + shadow: Shadow, + token_name: string +): SingleBoxShadowToken => { + return { + name: token_name, + type: TokenTypes.BOX_SHADOW, + value: `${shadow.offset[0]}px ${shadow.offset[1]}px ${shadow.blur}px 0px ${shadow.color}`, + } +} + +const popover_shadow_token = (theme: ColorScheme): SingleBoxShadowToken => { + const shadow = theme.popover_shadow + return create_shadow_token(shadow, "popover_shadow") +} + +const modal_shadow_token = (theme: ColorScheme): SingleBoxShadowToken => { + const shadow = theme.modal_shadow + return create_shadow_token(shadow, "modal_shadow") +} + +type ThemeSyntaxColorTokens = Record + +function syntax_highlight_style_color_tokens( + syntax: Syntax +): ThemeSyntaxColorTokens { + const style_keys = Object.keys(syntax) as (keyof Syntax)[] + + return style_keys.reduce((acc, style_key) => { + // Hack: The type of a style could be "Function" + // This can happen because we have a "constructor" property on the syntax object + // and a "constructor" property on the prototype of the syntax object + // To work around this just assert that the type of the style is not a function + if (!syntax[style_key] || typeof syntax[style_key] === "function") + return acc + const { color } = syntax[style_key] as Required + return { ...acc, [style_key]: color_token(style_key, color) } + }, {} as ThemeSyntaxColorTokens) +} + +const syntax_tokens = (theme: ColorScheme): ColorSchemeTokens["syntax"] => { + const syntax = editor(theme).syntax + + return syntax_highlight_style_color_tokens(syntax) +} + +export function theme_tokens(theme: ColorScheme): ColorSchemeTokens { + return { + name: { + name: "themeName", + value: theme.name, + type: TokenTypes.OTHER, + }, + appearance: { + name: "themeAppearance", + value: theme.is_light ? "light" : "dark", + type: TokenTypes.OTHER, + }, + lowest: layer_token(theme.lowest, "lowest"), + middle: layer_token(theme.middle, "middle"), + highest: layer_token(theme.highest, "highest"), + popover_shadow: popover_shadow_token(theme), + modal_shadow: modal_shadow_token(theme), + players: players_token(theme), + syntax: syntax_tokens(theme), + } +} diff --git a/styles/src/theme/tokens/layer.ts b/styles/src/theme/tokens/layer.ts index 42a69b5a52..a2e539092e 100644 --- a/styles/src/theme/tokens/layer.ts +++ b/styles/src/theme/tokens/layer.ts @@ -1,6 +1,6 @@ import { SingleColorToken } from "@tokens-studio/types" -import { Layer, Style, StyleSet } from "../colorScheme" -import { colorToken } from "./token" +import { Layer, Style, StyleSet } from "../color_scheme" +import { color_token } from "./token" interface StyleToken { background: SingleColorToken @@ -27,36 +27,36 @@ export interface LayerToken { negative: StyleSetToken } -export const styleToken = (style: Style, name: string): StyleToken => { +export const style_token = (style: Style, name: string): StyleToken => { const token = { - background: colorToken(`${name}Background`, style.background), - border: colorToken(`${name}Border`, style.border), - foreground: colorToken(`${name}Foreground`, style.foreground), + background: color_token(`${name}Background`, style.background), + border: color_token(`${name}Border`, style.border), + foreground: color_token(`${name}Foreground`, style.foreground), } return token } -export const styleSetToken = ( - styleSet: StyleSet, +export const style_set_token = ( + style_set: StyleSet, name: string ): StyleSetToken => { const token: StyleSetToken = {} as StyleSetToken - for (const style in styleSet) { + for (const style in style_set) { const s = style as keyof StyleSet - token[s] = styleToken(styleSet[s], `${name}${style}`) + token[s] = style_token(style_set[s], `${name}${style}`) } return token } -export const layerToken = (layer: Layer, name: string): LayerToken => { +export const layer_token = (layer: Layer, name: string): LayerToken => { const token: LayerToken = {} as LayerToken - for (const styleSet in layer) { - const s = styleSet as keyof Layer - token[s] = styleSetToken(layer[s], `${name}${styleSet}`) + for (const style_set in layer) { + const s = style_set as keyof Layer + token[s] = style_set_token(layer[s], `${name}${style_set}`) } return token diff --git a/styles/src/theme/tokens/players.ts b/styles/src/theme/tokens/players.ts index 94d05cd827..545a712ff1 100644 --- a/styles/src/theme/tokens/players.ts +++ b/styles/src/theme/tokens/players.ts @@ -1,36 +1,33 @@ import { SingleColorToken } from "@tokens-studio/types" -import { ColorScheme, Players } from "../../common" -import { colorToken } from "./token" +import { color_token } from "./token" +import { ColorScheme, Players } from "../color_scheme" export type PlayerToken = Record<"selection" | "cursor", SingleColorToken> export type PlayersToken = Record -function buildPlayerToken( - colorScheme: ColorScheme, - index: number -): PlayerToken { - const playerNumber = index.toString() as keyof Players +function build_player_token(theme: ColorScheme, index: number): PlayerToken { + const player_number = index.toString() as keyof Players return { - selection: colorToken( + selection: color_token( `player${index}Selection`, - colorScheme.players[playerNumber].selection + theme.players[player_number].selection ), - cursor: colorToken( + cursor: color_token( `player${index}Cursor`, - colorScheme.players[playerNumber].cursor + theme.players[player_number].cursor ), } } -export const playersToken = (colorScheme: ColorScheme): PlayersToken => ({ - "0": buildPlayerToken(colorScheme, 0), - "1": buildPlayerToken(colorScheme, 1), - "2": buildPlayerToken(colorScheme, 2), - "3": buildPlayerToken(colorScheme, 3), - "4": buildPlayerToken(colorScheme, 4), - "5": buildPlayerToken(colorScheme, 5), - "6": buildPlayerToken(colorScheme, 6), - "7": buildPlayerToken(colorScheme, 7), +export const players_token = (theme: ColorScheme): PlayersToken => ({ + "0": build_player_token(theme, 0), + "1": build_player_token(theme, 1), + "2": build_player_token(theme, 2), + "3": build_player_token(theme, 3), + "4": build_player_token(theme, 4), + "5": build_player_token(theme, 5), + "6": build_player_token(theme, 6), + "7": build_player_token(theme, 7), }) diff --git a/styles/src/theme/tokens/token.ts b/styles/src/theme/tokens/token.ts index 2f1760778e..60e846ce94 100644 --- a/styles/src/theme/tokens/token.ts +++ b/styles/src/theme/tokens/token.ts @@ -1,6 +1,6 @@ import { SingleColorToken, TokenTypes } from "@tokens-studio/types" -export function colorToken( +export function color_token( name: string, value: string, description?: string diff --git a/styles/src/themes/andromeda/LICENSE b/styles/src/themes/andromeda/LICENSE index bdd549491f..9422adafa4 100644 --- a/styles/src/themes/andromeda/LICENSE +++ b/styles/src/themes/andromeda/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/andromeda/andromeda.ts b/styles/src/themes/andromeda/andromeda.ts index 52c29bb2ec..18699d21cd 100644 --- a/styles/src/themes/andromeda/andromeda.ts +++ b/styles/src/themes/andromeda/andromeda.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -10,10 +10,10 @@ export const dark: ThemeConfig = { name: "Andromeda", author: "EliverLara", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/EliverLara/Andromeda", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/EliverLara/Andromeda", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#1E2025", @@ -26,14 +26,14 @@ export const dark: ThemeConfig = { "#F7F7F8", ]) .domain([0, 0.15, 0.25, 0.35, 0.7, 0.8, 0.9, 1]), - red: colorRamp(chroma("#F92672")), - orange: colorRamp(chroma("#F39C12")), - yellow: colorRamp(chroma("#FFE66D")), - green: colorRamp(chroma("#96E072")), - cyan: colorRamp(chroma("#00E8C6")), - blue: colorRamp(chroma("#0CA793")), - violet: colorRamp(chroma("#8A3FA6")), - magenta: colorRamp(chroma("#C74DED")), + red: color_ramp(chroma("#F92672")), + orange: color_ramp(chroma("#F39C12")), + yellow: color_ramp(chroma("#FFE66D")), + green: color_ramp(chroma("#96E072")), + cyan: color_ramp(chroma("#00E8C6")), + blue: color_ramp(chroma("#0CA793")), + violet: color_ramp(chroma("#8A3FA6")), + magenta: color_ramp(chroma("#C74DED")), }, override: { syntax: {} }, } diff --git a/styles/src/themes/atelier/LICENSE b/styles/src/themes/atelier/LICENSE index 9f92967a04..47c46d0429 100644 --- a/styles/src/themes/atelier/LICENSE +++ b/styles/src/themes/atelier/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/atelier/atelier-cave-dark.ts b/styles/src/themes/atelier/atelier-cave-dark.ts index ebec67b4c2..faf957b642 100644 --- a/styles/src/themes/atelier/atelier-cave-dark.ts +++ b/styles/src/themes/atelier/atelier-cave-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Cave Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-cave-light.ts b/styles/src/themes/atelier/atelier-cave-light.ts index c1b7a05d47..856cd30043 100644 --- a/styles/src/themes/atelier/atelier-cave-light.ts +++ b/styles/src/themes/atelier/atelier-cave-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Cave Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-dune-dark.ts b/styles/src/themes/atelier/atelier-dune-dark.ts index c2ebc424e7..fb67fd2471 100644 --- a/styles/src/themes/atelier/atelier-dune-dark.ts +++ b/styles/src/themes/atelier/atelier-dune-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Dune Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-dune-light.ts b/styles/src/themes/atelier/atelier-dune-light.ts index 01cb1d67cb..5e9e5b6927 100644 --- a/styles/src/themes/atelier/atelier-dune-light.ts +++ b/styles/src/themes/atelier/atelier-dune-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Dune Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-estuary-dark.ts b/styles/src/themes/atelier/atelier-estuary-dark.ts index 8e32c1f68f..0badf4371e 100644 --- a/styles/src/themes/atelier/atelier-estuary-dark.ts +++ b/styles/src/themes/atelier/atelier-estuary-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Estuary Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-estuary-light.ts b/styles/src/themes/atelier/atelier-estuary-light.ts index 75fcb8e830..adc77e7607 100644 --- a/styles/src/themes/atelier/atelier-estuary-light.ts +++ b/styles/src/themes/atelier/atelier-estuary-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Estuary Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-forest-dark.ts b/styles/src/themes/atelier/atelier-forest-dark.ts index 7ee7ae4ab1..3e89518c0b 100644 --- a/styles/src/themes/atelier/atelier-forest-dark.ts +++ b/styles/src/themes/atelier/atelier-forest-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Forest Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-forest-light.ts b/styles/src/themes/atelier/atelier-forest-light.ts index e12baf9904..68d2c50876 100644 --- a/styles/src/themes/atelier/atelier-forest-light.ts +++ b/styles/src/themes/atelier/atelier-forest-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Forest Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-heath-dark.ts b/styles/src/themes/atelier/atelier-heath-dark.ts index 11751367a3..c185d69e43 100644 --- a/styles/src/themes/atelier/atelier-heath-dark.ts +++ b/styles/src/themes/atelier/atelier-heath-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Heath Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-heath-light.ts b/styles/src/themes/atelier/atelier-heath-light.ts index 07f4a9b3cb..4414987e22 100644 --- a/styles/src/themes/atelier/atelier-heath-light.ts +++ b/styles/src/themes/atelier/atelier-heath-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Heath Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-lakeside-dark.ts b/styles/src/themes/atelier/atelier-lakeside-dark.ts index b1c98ddfdf..7fdc3b4eba 100644 --- a/styles/src/themes/atelier/atelier-lakeside-dark.ts +++ b/styles/src/themes/atelier/atelier-lakeside-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Lakeside Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-lakeside-light.ts b/styles/src/themes/atelier/atelier-lakeside-light.ts index d960444def..bdda48f6c7 100644 --- a/styles/src/themes/atelier/atelier-lakeside-light.ts +++ b/styles/src/themes/atelier/atelier-lakeside-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Lakeside Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-plateau-dark.ts b/styles/src/themes/atelier/atelier-plateau-dark.ts index 74693b24fd..ff287bc80d 100644 --- a/styles/src/themes/atelier/atelier-plateau-dark.ts +++ b/styles/src/themes/atelier/atelier-plateau-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Plateau Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-plateau-light.ts b/styles/src/themes/atelier/atelier-plateau-light.ts index dd3130cea0..8a9fb989ad 100644 --- a/styles/src/themes/atelier/atelier-plateau-light.ts +++ b/styles/src/themes/atelier/atelier-plateau-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Plateau Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-savanna-dark.ts b/styles/src/themes/atelier/atelier-savanna-dark.ts index c387ac5ae9..d94af30334 100644 --- a/styles/src/themes/atelier/atelier-savanna-dark.ts +++ b/styles/src/themes/atelier/atelier-savanna-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Savanna Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-savanna-light.ts b/styles/src/themes/atelier/atelier-savanna-light.ts index 64edd406a8..2426b05400 100644 --- a/styles/src/themes/atelier/atelier-savanna-light.ts +++ b/styles/src/themes/atelier/atelier-savanna-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Savanna Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-seaside-dark.ts b/styles/src/themes/atelier/atelier-seaside-dark.ts index dbccb96013..abb267f5a4 100644 --- a/styles/src/themes/atelier/atelier-seaside-dark.ts +++ b/styles/src/themes/atelier/atelier-seaside-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Seaside Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-seaside-light.ts b/styles/src/themes/atelier/atelier-seaside-light.ts index a9c034ed44..455e7795e1 100644 --- a/styles/src/themes/atelier/atelier-seaside-light.ts +++ b/styles/src/themes/atelier/atelier-seaside-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Seaside Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-sulphurpool-dark.ts b/styles/src/themes/atelier/atelier-sulphurpool-dark.ts index edfc518b8e..3f33647daa 100644 --- a/styles/src/themes/atelier/atelier-sulphurpool-dark.ts +++ b/styles/src/themes/atelier/atelier-sulphurpool-dark.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Sulphurpool Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ colors.base00, colors.base01, @@ -45,17 +45,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base06, colors.base07, ]), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/atelier-sulphurpool-light.ts b/styles/src/themes/atelier/atelier-sulphurpool-light.ts index fbef6683bf..2cb4d04539 100644 --- a/styles/src/themes/atelier/atelier-sulphurpool-light.ts +++ b/styles/src/themes/atelier/atelier-sulphurpool-light.ts @@ -1,5 +1,5 @@ -import { chroma, ThemeAppearance, ThemeConfig, colorRamp } from "../../common" -import { meta, buildSyntax, Variant } from "./common" +import { chroma, ThemeAppearance, ThemeConfig, color_ramp } from "../../common" +import { meta, build_syntax, Variant } from "./common" const variant: Variant = { colors: { @@ -22,19 +22,19 @@ const variant: Variant = { }, } -const syntax = buildSyntax(variant) +const syntax = build_syntax(variant) -const getTheme = (variant: Variant): ThemeConfig => { +const get_theme = (variant: Variant): ThemeConfig => { const { colors } = variant return { name: `${meta.name} Sulphurpool Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale( [ colors.base00, @@ -47,17 +47,17 @@ const getTheme = (variant: Variant): ThemeConfig => { colors.base07, ].reverse() ), - red: colorRamp(chroma(colors.base08)), - orange: colorRamp(chroma(colors.base09)), - yellow: colorRamp(chroma(colors.base0A)), - green: colorRamp(chroma(colors.base0B)), - cyan: colorRamp(chroma(colors.base0C)), - blue: colorRamp(chroma(colors.base0D)), - violet: colorRamp(chroma(colors.base0E)), - magenta: colorRamp(chroma(colors.base0F)), + red: color_ramp(chroma(colors.base08)), + orange: color_ramp(chroma(colors.base09)), + yellow: color_ramp(chroma(colors.base0A)), + green: color_ramp(chroma(colors.base0B)), + cyan: color_ramp(chroma(colors.base0C)), + blue: color_ramp(chroma(colors.base0D)), + violet: color_ramp(chroma(colors.base0E)), + magenta: color_ramp(chroma(colors.base0F)), }, override: { syntax }, } } -export const theme = getTheme(variant) +export const theme = get_theme(variant) diff --git a/styles/src/themes/atelier/common.ts b/styles/src/themes/atelier/common.ts index 2e5be61f52..b76ccc5b60 100644 --- a/styles/src/themes/atelier/common.ts +++ b/styles/src/themes/atelier/common.ts @@ -24,12 +24,12 @@ export interface Variant { export const meta: ThemeFamilyMeta = { name: "Atelier", author: "Bram de Haan (http://atelierbramdehaan.nl)", - licenseType: ThemeLicenseType.MIT, - licenseUrl: + license_type: ThemeLicenseType.MIT, + license_url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/", } -export const buildSyntax = (variant: Variant): ThemeSyntax => { +export const build_syntax = (variant: Variant): ThemeSyntax => { const { colors } = variant return { primary: { color: colors.base06 }, diff --git a/styles/src/themes/ayu/LICENSE b/styles/src/themes/ayu/LICENSE index 6b83ef0582..37a9229268 100644 --- a/styles/src/themes/ayu/LICENSE +++ b/styles/src/themes/ayu/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/ayu/ayu-dark.ts b/styles/src/themes/ayu/ayu-dark.ts index 7feddacd2b..a12ce08e29 100644 --- a/styles/src/themes/ayu/ayu-dark.ts +++ b/styles/src/themes/ayu/ayu-dark.ts @@ -1,16 +1,16 @@ import { ThemeAppearance, ThemeConfig } from "../../common" -import { ayu, meta, buildTheme } from "./common" +import { ayu, meta, build_theme } from "./common" const variant = ayu.dark -const { ramps, syntax } = buildTheme(variant, false) +const { ramps, syntax } = build_theme(variant, false) export const theme: ThemeConfig = { name: `${meta.name} Dark`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } diff --git a/styles/src/themes/ayu/ayu-light.ts b/styles/src/themes/ayu/ayu-light.ts index bf02385247..aceda0d017 100644 --- a/styles/src/themes/ayu/ayu-light.ts +++ b/styles/src/themes/ayu/ayu-light.ts @@ -1,16 +1,16 @@ import { ThemeAppearance, ThemeConfig } from "../../common" -import { ayu, meta, buildTheme } from "./common" +import { ayu, meta, build_theme } from "./common" const variant = ayu.light -const { ramps, syntax } = buildTheme(variant, true) +const { ramps, syntax } = build_theme(variant, true) export const theme: ThemeConfig = { name: `${meta.name} Light`, author: meta.author, appearance: ThemeAppearance.Light, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } diff --git a/styles/src/themes/ayu/ayu-mirage.ts b/styles/src/themes/ayu/ayu-mirage.ts index d2a69e7ab6..9dd3ea7a61 100644 --- a/styles/src/themes/ayu/ayu-mirage.ts +++ b/styles/src/themes/ayu/ayu-mirage.ts @@ -1,16 +1,16 @@ import { ThemeAppearance, ThemeConfig } from "../../common" -import { ayu, meta, buildTheme } from "./common" +import { ayu, meta, build_theme } from "./common" const variant = ayu.mirage -const { ramps, syntax } = buildTheme(variant, false) +const { ramps, syntax } = build_theme(variant, false) export const theme: ThemeConfig = { name: `${meta.name} Mirage`, author: meta.author, appearance: ThemeAppearance.Dark, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } diff --git a/styles/src/themes/ayu/common.ts b/styles/src/themes/ayu/common.ts index 1eb2c91916..2bd0bbf259 100644 --- a/styles/src/themes/ayu/common.ts +++ b/styles/src/themes/ayu/common.ts @@ -1,7 +1,7 @@ import { dark, light, mirage } from "ayu" import { chroma, - colorRamp, + color_ramp, ThemeLicenseType, ThemeSyntax, ThemeFamilyMeta, @@ -13,16 +13,16 @@ export const ayu = { mirage, } -export const buildTheme = (t: typeof dark, light: boolean) => { +export const build_theme = (t: typeof dark, light: boolean) => { const color = { - lightBlue: t.syntax.tag.hex(), + light_blue: t.syntax.tag.hex(), yellow: t.syntax.func.hex(), blue: t.syntax.entity.hex(), green: t.syntax.string.hex(), teal: t.syntax.regexp.hex(), red: t.syntax.markup.hex(), orange: t.syntax.keyword.hex(), - lightYellow: t.syntax.special.hex(), + light_yellow: t.syntax.special.hex(), gray: t.syntax.comment.hex(), purple: t.syntax.constant.hex(), } @@ -48,20 +48,20 @@ export const buildTheme = (t: typeof dark, light: boolean) => { light ? t.editor.fg.hex() : t.editor.bg.hex(), light ? t.editor.bg.hex() : t.editor.fg.hex(), ]), - red: colorRamp(chroma(color.red)), - orange: colorRamp(chroma(color.orange)), - yellow: colorRamp(chroma(color.yellow)), - green: colorRamp(chroma(color.green)), - cyan: colorRamp(chroma(color.teal)), - blue: colorRamp(chroma(color.blue)), - violet: colorRamp(chroma(color.purple)), - magenta: colorRamp(chroma(color.lightBlue)), + red: color_ramp(chroma(color.red)), + orange: color_ramp(chroma(color.orange)), + yellow: color_ramp(chroma(color.yellow)), + green: color_ramp(chroma(color.green)), + cyan: color_ramp(chroma(color.teal)), + blue: color_ramp(chroma(color.blue)), + violet: color_ramp(chroma(color.purple)), + magenta: color_ramp(chroma(color.light_blue)), }, syntax, } } -export const buildSyntax = (t: typeof dark): ThemeSyntax => { +export const build_syntax = (t: typeof dark): ThemeSyntax => { return { constant: { color: t.syntax.constant.hex() }, "string.regex": { color: t.syntax.regexp.hex() }, @@ -80,6 +80,6 @@ export const buildSyntax = (t: typeof dark): ThemeSyntax => { export const meta: ThemeFamilyMeta = { name: "Ayu", author: "dempfi", - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/dempfi/ayu", + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/dempfi/ayu", } diff --git a/styles/src/themes/gruvbox/LICENSE b/styles/src/themes/gruvbox/LICENSE index 2a92306143..0e18d6d7a9 100644 --- a/styles/src/themes/gruvbox/LICENSE +++ b/styles/src/themes/gruvbox/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/gruvbox/gruvbox-common.ts b/styles/src/themes/gruvbox/gruvbox-common.ts index 18e8c5b97e..2fa6b58faa 100644 --- a/styles/src/themes/gruvbox/gruvbox-common.ts +++ b/styles/src/themes/gruvbox/gruvbox-common.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -11,8 +11,8 @@ import { const meta: ThemeFamilyMeta = { name: "Gruvbox", author: "morhetz ", - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/morhetz/gruvbox", + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/morhetz/gruvbox", } const color = { @@ -73,7 +73,7 @@ interface ThemeColors { gray: string } -const darkNeutrals = [ +const dark_neutrals = [ color.dark1, color.dark2, color.dark3, @@ -96,7 +96,7 @@ const dark: ThemeColors = { gray: color.light4, } -const lightNeutrals = [ +const light_neutrals = [ color.light1, color.light2, color.light3, @@ -119,14 +119,6 @@ const light: ThemeColors = { gray: color.dark4, } -const darkHardNeutral = [color.dark0_hard, ...darkNeutrals] -const darkNeutral = [color.dark0, ...darkNeutrals] -const darkSoftNeutral = [color.dark0_soft, ...darkNeutrals] - -const lightHardNeutral = [color.light0_hard, ...lightNeutrals] -const lightNeutral = [color.light0, ...lightNeutrals] -const lightSoftNeutral = [color.light0_soft, ...lightNeutrals] - interface Variant { name: string appearance: "light" | "dark" @@ -167,61 +159,68 @@ const variant: Variant[] = [ }, ] -const buildVariant = (variant: Variant): ThemeConfig => { +const dark_hard_neutral = [color.dark0_hard, ...dark_neutrals] +const dark_neutral = [color.dark0, ...dark_neutrals] +const dark_soft_neutral = [color.dark0_soft, ...dark_neutrals] + +const light_hard_neutral = [color.light0_hard, ...light_neutrals] +const light_neutral = [color.light0, ...light_neutrals] +const light_soft_neutral = [color.light0_soft, ...light_neutrals] + +const build_variant = (variant: Variant): ThemeConfig => { const { colors } = variant const name = `Gruvbox ${variant.name}` - const isLight = variant.appearance === "light" + const is_light = variant.appearance === "light" let neutral: string[] = [] switch (variant.name) { - case "Dark Hard": { - neutral = darkHardNeutral + case "Dark Hard": + neutral = dark_hard_neutral break - } - case "Dark": { - neutral = darkNeutral + + case "Dark": + neutral = dark_neutral break - } - case "Dark Soft": { - neutral = darkSoftNeutral + + case "Dark Soft": + neutral = dark_soft_neutral break - } - case "Light Hard": { - neutral = lightHardNeutral + + case "Light Hard": + neutral = light_hard_neutral break - } - case "Light": { - neutral = lightNeutral + + case "Light": + neutral = light_neutral break - } - case "Light Soft": { - neutral = lightSoftNeutral + + case "Light Soft": + neutral = light_soft_neutral break - } } const ramps = { - neutral: chroma.scale(isLight ? neutral.reverse() : neutral), - red: colorRamp(chroma(variant.colors.red)), - orange: colorRamp(chroma(variant.colors.orange)), - yellow: colorRamp(chroma(variant.colors.yellow)), - green: colorRamp(chroma(variant.colors.green)), - cyan: colorRamp(chroma(variant.colors.aqua)), - blue: colorRamp(chroma(variant.colors.blue)), - violet: colorRamp(chroma(variant.colors.purple)), - magenta: colorRamp(chroma(variant.colors.gray)), + neutral: chroma.scale(is_light ? neutral.reverse() : neutral), + red: color_ramp(chroma(variant.colors.red)), + orange: color_ramp(chroma(variant.colors.orange)), + yellow: color_ramp(chroma(variant.colors.yellow)), + green: color_ramp(chroma(variant.colors.green)), + cyan: color_ramp(chroma(variant.colors.aqua)), + blue: color_ramp(chroma(variant.colors.blue)), + violet: color_ramp(chroma(variant.colors.purple)), + magenta: color_ramp(chroma(variant.colors.gray)), } const syntax: ThemeSyntax = { - primary: { color: neutral[isLight ? 0 : 8] }, + primary: { color: neutral[is_light ? 0 : 8] }, "text.literal": { color: colors.blue }, comment: { color: colors.gray }, - punctuation: { color: neutral[isLight ? 1 : 7] }, - "punctuation.bracket": { color: neutral[isLight ? 3 : 5] }, - "punctuation.list_marker": { color: neutral[isLight ? 0 : 8] }, + punctuation: { color: neutral[is_light ? 1 : 7] }, + "punctuation.bracket": { color: neutral[is_light ? 3 : 5] }, + "punctuation.list_marker": { color: neutral[is_light ? 0 : 8] }, operator: { color: colors.aqua }, boolean: { color: colors.purple }, number: { color: colors.purple }, @@ -237,10 +236,10 @@ const buildVariant = (variant: Variant): ThemeConfig => { function: { color: colors.green }, "function.builtin": { color: colors.red }, variable: { color: colors.blue }, - property: { color: neutral[isLight ? 0 : 8] }, + property: { color: neutral[is_light ? 0 : 8] }, embedded: { color: colors.aqua }, - linkText: { color: colors.aqua }, - linkUri: { color: colors.purple }, + link_text: { color: colors.aqua }, + link_uri: { color: colors.purple }, title: { color: colors.green }, } @@ -248,18 +247,18 @@ const buildVariant = (variant: Variant): ThemeConfig => { name, author: meta.author, appearance: variant.appearance as ThemeAppearance, - licenseType: meta.licenseType, - licenseUrl: meta.licenseUrl, - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: meta.license_type, + license_url: meta.license_url, + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax }, } } // Variants -export const darkHard = buildVariant(variant[0]) -export const darkDefault = buildVariant(variant[1]) -export const darkSoft = buildVariant(variant[2]) -export const lightHard = buildVariant(variant[3]) -export const lightDefault = buildVariant(variant[4]) -export const lightSoft = buildVariant(variant[5]) +export const dark_hard = build_variant(variant[0]) +export const dark_default = build_variant(variant[1]) +export const dark_soft = build_variant(variant[2]) +export const light_hard = build_variant(variant[3]) +export const light_default = build_variant(variant[4]) +export const light_soft = build_variant(variant[5]) diff --git a/styles/src/themes/gruvbox/gruvbox-dark-hard.ts b/styles/src/themes/gruvbox/gruvbox-dark-hard.ts index 4102671189..72757c99f2 100644 --- a/styles/src/themes/gruvbox/gruvbox-dark-hard.ts +++ b/styles/src/themes/gruvbox/gruvbox-dark-hard.ts @@ -1 +1 @@ -export { darkHard } from "./gruvbox-common" +export { dark_hard } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-dark-soft.ts b/styles/src/themes/gruvbox/gruvbox-dark-soft.ts index d550d63768..d8f63ed331 100644 --- a/styles/src/themes/gruvbox/gruvbox-dark-soft.ts +++ b/styles/src/themes/gruvbox/gruvbox-dark-soft.ts @@ -1 +1 @@ -export { darkSoft } from "./gruvbox-common" +export { dark_soft } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-dark.ts b/styles/src/themes/gruvbox/gruvbox-dark.ts index 05850028a4..0582baa0d8 100644 --- a/styles/src/themes/gruvbox/gruvbox-dark.ts +++ b/styles/src/themes/gruvbox/gruvbox-dark.ts @@ -1 +1 @@ -export { darkDefault } from "./gruvbox-common" +export { dark_default } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-light-hard.ts b/styles/src/themes/gruvbox/gruvbox-light-hard.ts index ec3cddec75..bcaea06a41 100644 --- a/styles/src/themes/gruvbox/gruvbox-light-hard.ts +++ b/styles/src/themes/gruvbox/gruvbox-light-hard.ts @@ -1 +1 @@ -export { lightHard } from "./gruvbox-common" +export { light_hard } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-light-soft.ts b/styles/src/themes/gruvbox/gruvbox-light-soft.ts index 0888e847ac..5eb79f647b 100644 --- a/styles/src/themes/gruvbox/gruvbox-light-soft.ts +++ b/styles/src/themes/gruvbox/gruvbox-light-soft.ts @@ -1 +1 @@ -export { lightSoft } from "./gruvbox-common" +export { light_soft } from "./gruvbox-common" diff --git a/styles/src/themes/gruvbox/gruvbox-light.ts b/styles/src/themes/gruvbox/gruvbox-light.ts index 6f53ce5299..dc54a33f26 100644 --- a/styles/src/themes/gruvbox/gruvbox-light.ts +++ b/styles/src/themes/gruvbox/gruvbox-light.ts @@ -1 +1 @@ -export { lightDefault } from "./gruvbox-common" +export { light_default } from "./gruvbox-common" diff --git a/styles/src/themes/index.ts b/styles/src/themes/index.ts index 75853bc042..72bb100e7b 100644 --- a/styles/src/themes/index.ts +++ b/styles/src/themes/index.ts @@ -1,82 +1,82 @@ import { ThemeConfig } from "../theme" -import { darkDefault as gruvboxDark } from "./gruvbox/gruvbox-dark" -import { darkHard as gruvboxDarkHard } from "./gruvbox/gruvbox-dark-hard" -import { darkSoft as gruvboxDarkSoft } from "./gruvbox/gruvbox-dark-soft" -import { lightDefault as gruvboxLight } from "./gruvbox/gruvbox-light" -import { lightHard as gruvboxLightHard } from "./gruvbox/gruvbox-light-hard" -import { lightSoft as gruvboxLightSoft } from "./gruvbox/gruvbox-light-soft" -import { dark as solarizedDark } from "./solarized/solarized" -import { light as solarizedLight } from "./solarized/solarized" -import { dark as andromedaDark } from "./andromeda/andromeda" -import { theme as oneDark } from "./one/one-dark" -import { theme as oneLight } from "./one/one-light" -import { theme as ayuLight } from "./ayu/ayu-light" -import { theme as ayuDark } from "./ayu/ayu-dark" -import { theme as ayuMirage } from "./ayu/ayu-mirage" -import { theme as rosePine } from "./rose-pine/rose-pine" -import { theme as rosePineDawn } from "./rose-pine/rose-pine-dawn" -import { theme as rosePineMoon } from "./rose-pine/rose-pine-moon" +import { dark_default as gruvbox_dark } from "./gruvbox/gruvbox-dark" +import { dark_hard as gruvbox_dark_hard } from "./gruvbox/gruvbox-dark-hard" +import { dark_soft as gruvbox_dark_soft } from "./gruvbox/gruvbox-dark-soft" +import { light_default as gruvbox_light } from "./gruvbox/gruvbox-light" +import { light_hard as gruvbox_light_hard } from "./gruvbox/gruvbox-light-hard" +import { light_soft as gruvbox_light_soft } from "./gruvbox/gruvbox-light-soft" +import { dark as solarized_dark } from "./solarized/solarized" +import { light as solarized_light } from "./solarized/solarized" +import { dark as andromeda_dark } from "./andromeda/andromeda" +import { theme as one_dark } from "./one/one-dark" +import { theme as one_light } from "./one/one-light" +import { theme as ayu_light } from "./ayu/ayu-light" +import { theme as ayu_dark } from "./ayu/ayu-dark" +import { theme as ayu_mirage } from "./ayu/ayu-mirage" +import { theme as rose_pine } from "./rose-pine/rose-pine" +import { theme as rose_pine_dawn } from "./rose-pine/rose-pine-dawn" +import { theme as rose_pine_moon } from "./rose-pine/rose-pine-moon" import { theme as sandcastle } from "./sandcastle/sandcastle" import { theme as summercamp } from "./summercamp/summercamp" -import { theme as atelierCaveDark } from "./atelier/atelier-cave-dark" -import { theme as atelierCaveLight } from "./atelier/atelier-cave-light" -import { theme as atelierDuneDark } from "./atelier/atelier-dune-dark" -import { theme as atelierDuneLight } from "./atelier/atelier-dune-light" -import { theme as atelierEstuaryDark } from "./atelier/atelier-estuary-dark" -import { theme as atelierEstuaryLight } from "./atelier/atelier-estuary-light" -import { theme as atelierForestDark } from "./atelier/atelier-forest-dark" -import { theme as atelierForestLight } from "./atelier/atelier-forest-light" -import { theme as atelierHeathDark } from "./atelier/atelier-heath-dark" -import { theme as atelierHeathLight } from "./atelier/atelier-heath-light" -import { theme as atelierLakesideDark } from "./atelier/atelier-lakeside-dark" -import { theme as atelierLakesideLight } from "./atelier/atelier-lakeside-light" -import { theme as atelierPlateauDark } from "./atelier/atelier-plateau-dark" -import { theme as atelierPlateauLight } from "./atelier/atelier-plateau-light" -import { theme as atelierSavannaDark } from "./atelier/atelier-savanna-dark" -import { theme as atelierSavannaLight } from "./atelier/atelier-savanna-light" -import { theme as atelierSeasideDark } from "./atelier/atelier-seaside-dark" -import { theme as atelierSeasideLight } from "./atelier/atelier-seaside-light" -import { theme as atelierSulphurpoolDark } from "./atelier/atelier-sulphurpool-dark" -import { theme as atelierSulphurpoolLight } from "./atelier/atelier-sulphurpool-light" +import { theme as atelier_cave_dark } from "./atelier/atelier-cave-dark" +import { theme as atelier_cave_light } from "./atelier/atelier-cave-light" +import { theme as atelier_dune_dark } from "./atelier/atelier-dune-dark" +import { theme as atelier_dune_light } from "./atelier/atelier-dune-light" +import { theme as atelier_estuary_dark } from "./atelier/atelier-estuary-dark" +import { theme as atelier_estuary_light } from "./atelier/atelier-estuary-light" +import { theme as atelier_forest_dark } from "./atelier/atelier-forest-dark" +import { theme as atelier_forest_light } from "./atelier/atelier-forest-light" +import { theme as atelier_heath_dark } from "./atelier/atelier-heath-dark" +import { theme as atelier_heath_light } from "./atelier/atelier-heath-light" +import { theme as atelier_lakeside_dark } from "./atelier/atelier-lakeside-dark" +import { theme as atelier_lakeside_light } from "./atelier/atelier-lakeside-light" +import { theme as atelier_plateau_dark } from "./atelier/atelier-plateau-dark" +import { theme as atelier_plateau_light } from "./atelier/atelier-plateau-light" +import { theme as atelier_savanna_dark } from "./atelier/atelier-savanna-dark" +import { theme as atelier_savanna_light } from "./atelier/atelier-savanna-light" +import { theme as atelier_seaside_dark } from "./atelier/atelier-seaside-dark" +import { theme as atelier_seaside_light } from "./atelier/atelier-seaside-light" +import { theme as atelier_sulphurpool_dark } from "./atelier/atelier-sulphurpool-dark" +import { theme as atelier_sulphurpool_light } from "./atelier/atelier-sulphurpool-light" export const themes: ThemeConfig[] = [ - oneDark, - oneLight, - ayuLight, - ayuDark, - ayuMirage, - gruvboxDark, - gruvboxDarkHard, - gruvboxDarkSoft, - gruvboxLight, - gruvboxLightHard, - gruvboxLightSoft, - rosePine, - rosePineDawn, - rosePineMoon, + one_dark, + one_light, + ayu_light, + ayu_dark, + ayu_mirage, + gruvbox_dark, + gruvbox_dark_hard, + gruvbox_dark_soft, + gruvbox_light, + gruvbox_light_hard, + gruvbox_light_soft, + rose_pine, + rose_pine_dawn, + rose_pine_moon, sandcastle, - solarizedDark, - solarizedLight, - andromedaDark, + solarized_dark, + solarized_light, + andromeda_dark, summercamp, - atelierCaveDark, - atelierCaveLight, - atelierDuneDark, - atelierDuneLight, - atelierEstuaryDark, - atelierEstuaryLight, - atelierForestDark, - atelierForestLight, - atelierHeathDark, - atelierHeathLight, - atelierLakesideDark, - atelierLakesideLight, - atelierPlateauDark, - atelierPlateauLight, - atelierSavannaDark, - atelierSavannaLight, - atelierSeasideDark, - atelierSeasideLight, - atelierSulphurpoolDark, - atelierSulphurpoolLight, + atelier_cave_dark, + atelier_cave_light, + atelier_dune_dark, + atelier_dune_light, + atelier_estuary_dark, + atelier_estuary_light, + atelier_forest_dark, + atelier_forest_light, + atelier_heath_dark, + atelier_heath_light, + atelier_lakeside_dark, + atelier_lakeside_light, + atelier_plateau_dark, + atelier_plateau_light, + atelier_savanna_dark, + atelier_savanna_light, + atelier_seaside_dark, + atelier_seaside_light, + atelier_sulphurpool_dark, + atelier_sulphurpool_light, ] diff --git a/styles/src/themes/one/LICENSE b/styles/src/themes/one/LICENSE index dc07dc10ad..f7637d33ea 100644 --- a/styles/src/themes/one/LICENSE +++ b/styles/src/themes/one/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/one/one-dark.ts b/styles/src/themes/one/one-dark.ts index 69a5bd5575..f672b892ee 100644 --- a/styles/src/themes/one/one-dark.ts +++ b/styles/src/themes/one/one-dark.ts @@ -1,7 +1,7 @@ import { chroma, - fontWeights, - colorRamp, + font_weights, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -11,7 +11,7 @@ const color = { white: "#ACB2BE", grey: "#5D636F", red: "#D07277", - darkRed: "#B1574B", + dark_red: "#B1574B", orange: "#C0966B", yellow: "#DFC184", green: "#A1C181", @@ -24,10 +24,11 @@ export const theme: ThemeConfig = { name: "One Dark", author: "simurai", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/atom/atom/tree/master/packages/one-dark-ui", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: + "https://github.com/atom/atom/tree/master/packages/one-dark-ui", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#282c34", @@ -40,14 +41,14 @@ export const theme: ThemeConfig = { "#c8ccd4", ]) .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), - red: colorRamp(chroma(color.red)), - orange: colorRamp(chroma(color.orange)), - yellow: colorRamp(chroma(color.yellow)), - green: colorRamp(chroma(color.green)), - cyan: colorRamp(chroma(color.teal)), - blue: colorRamp(chroma(color.blue)), - violet: colorRamp(chroma(color.purple)), - magenta: colorRamp(chroma("#be5046")), + red: color_ramp(chroma(color.red)), + orange: color_ramp(chroma(color.orange)), + yellow: color_ramp(chroma(color.yellow)), + green: color_ramp(chroma(color.green)), + cyan: color_ramp(chroma(color.teal)), + blue: color_ramp(chroma(color.blue)), + violet: color_ramp(chroma(color.purple)), + magenta: color_ramp(chroma("#be5046")), }, override: { syntax: { @@ -57,8 +58,8 @@ export const theme: ThemeConfig = { "emphasis.strong": { color: color.orange }, function: { color: color.blue }, keyword: { color: color.purple }, - linkText: { color: color.blue, italic: false }, - linkUri: { color: color.teal }, + link_text: { color: color.blue, italic: false }, + link_uri: { color: color.teal }, number: { color: color.orange }, constant: { color: color.yellow }, operator: { color: color.teal }, @@ -66,9 +67,9 @@ export const theme: ThemeConfig = { property: { color: color.red }, punctuation: { color: color.white }, "punctuation.list_marker": { color: color.red }, - "punctuation.special": { color: color.darkRed }, + "punctuation.special": { color: color.dark_red }, string: { color: color.green }, - title: { color: color.red, weight: fontWeights.normal }, + title: { color: color.red, weight: font_weights.normal }, "text.literal": { color: color.green }, type: { color: color.teal }, "variable.special": { color: color.orange }, diff --git a/styles/src/themes/one/one-light.ts b/styles/src/themes/one/one-light.ts index 9123c8879d..c3de7826c9 100644 --- a/styles/src/themes/one/one-light.ts +++ b/styles/src/themes/one/one-light.ts @@ -1,7 +1,7 @@ import { chroma, - fontWeights, - colorRamp, + font_weights, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -11,7 +11,7 @@ const color = { black: "#383A41", grey: "#A2A3A7", red: "#D36050", - darkRed: "#B92C46", + dark_red: "#B92C46", orange: "#AD6F26", yellow: "#DFC184", green: "#659F58", @@ -25,11 +25,11 @@ export const theme: ThemeConfig = { name: "One Light", author: "simurai", appearance: ThemeAppearance.Light, - licenseType: ThemeLicenseType.MIT, - licenseUrl: + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/atom/atom/tree/master/packages/one-light-ui", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#383A41", @@ -42,14 +42,14 @@ export const theme: ThemeConfig = { "#FAFAFA", ]) .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]), - red: colorRamp(chroma(color.red)), - orange: colorRamp(chroma(color.orange)), - yellow: colorRamp(chroma(color.yellow)), - green: colorRamp(chroma(color.green)), - cyan: colorRamp(chroma(color.teal)), - blue: colorRamp(chroma(color.blue)), - violet: colorRamp(chroma(color.purple)), - magenta: colorRamp(chroma(color.magenta)), + red: color_ramp(chroma(color.red)), + orange: color_ramp(chroma(color.orange)), + yellow: color_ramp(chroma(color.yellow)), + green: color_ramp(chroma(color.green)), + cyan: color_ramp(chroma(color.teal)), + blue: color_ramp(chroma(color.blue)), + violet: color_ramp(chroma(color.purple)), + magenta: color_ramp(chroma(color.magenta)), }, override: { syntax: { @@ -59,17 +59,17 @@ export const theme: ThemeConfig = { "emphasis.strong": { color: color.orange }, function: { color: color.blue }, keyword: { color: color.purple }, - linkText: { color: color.blue }, - linkUri: { color: color.teal }, + link_text: { color: color.blue }, + link_uri: { color: color.teal }, number: { color: color.orange }, operator: { color: color.teal }, primary: { color: color.black }, property: { color: color.red }, punctuation: { color: color.black }, "punctuation.list_marker": { color: color.red }, - "punctuation.special": { color: color.darkRed }, + "punctuation.special": { color: color.dark_red }, string: { color: color.green }, - title: { color: color.red, weight: fontWeights.normal }, + title: { color: color.red, weight: font_weights.normal }, "text.literal": { color: color.green }, type: { color: color.teal }, "variable.special": { color: color.orange }, diff --git a/styles/src/themes/rose-pine/LICENSE b/styles/src/themes/rose-pine/LICENSE index dfd60136f9..1276733453 100644 --- a/styles/src/themes/rose-pine/LICENSE +++ b/styles/src/themes/rose-pine/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/rose-pine/common.ts b/styles/src/themes/rose-pine/common.ts index 146305890b..5c5482a754 100644 --- a/styles/src/themes/rose-pine/common.ts +++ b/styles/src/themes/rose-pine/common.ts @@ -14,9 +14,9 @@ export const color = { pine: "#31748f", foam: "#9ccfd8", iris: "#c4a7e7", - highlightLow: "#21202e", - highlightMed: "#403d52", - highlightHigh: "#524f67", + highlight_low: "#21202e", + highlight_med: "#403d52", + highlight_high: "#524f67", }, moon: { base: "#232136", @@ -31,9 +31,9 @@ export const color = { pine: "#3e8fb0", foam: "#9ccfd8", iris: "#c4a7e7", - highlightLow: "#2a283e", - highlightMed: "#44415a", - highlightHigh: "#56526e", + highlight_low: "#2a283e", + highlight_med: "#44415a", + highlight_high: "#56526e", }, dawn: { base: "#faf4ed", @@ -48,9 +48,9 @@ export const color = { pine: "#286983", foam: "#56949f", iris: "#907aa9", - highlightLow: "#f4ede8", - highlightMed: "#dfdad9", - highlightHigh: "#cecacd", + highlight_low: "#f4ede8", + highlight_med: "#dfdad9", + highlight_high: "#cecacd", }, } @@ -69,7 +69,7 @@ export const syntax = (c: typeof color.default): Partial => { tag: { color: c.foam }, "function.method": { color: c.rose }, title: { color: c.gold }, - linkText: { color: c.foam, italic: false }, - linkUri: { color: c.rose }, + link_text: { color: c.foam, italic: false }, + link_uri: { color: c.rose }, } } diff --git a/styles/src/themes/rose-pine/rose-pine-dawn.ts b/styles/src/themes/rose-pine/rose-pine-dawn.ts index 15d7b5de2d..c78f1132dd 100644 --- a/styles/src/themes/rose-pine/rose-pine-dawn.ts +++ b/styles/src/themes/rose-pine/rose-pine-dawn.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -17,16 +17,16 @@ export const theme: ThemeConfig = { name: "Rosé Pine Dawn", author: "edunfelt", appearance: ThemeAppearance.Light, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/edunfelt/base16-rose-pine-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale( [ color.base, color.surface, - color.highlightHigh, + color.highlight_high, color.overlay, color.muted, color.subtle, @@ -34,14 +34,14 @@ export const theme: ThemeConfig = { ].reverse() ) .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]), - red: colorRamp(chroma(color.love)), - orange: colorRamp(chroma(color.iris)), - yellow: colorRamp(chroma(color.gold)), - green: colorRamp(chroma(green)), - cyan: colorRamp(chroma(color.pine)), - blue: colorRamp(chroma(color.foam)), - violet: colorRamp(chroma(color.iris)), - magenta: colorRamp(chroma(magenta)), + red: color_ramp(chroma(color.love)), + orange: color_ramp(chroma(color.iris)), + yellow: color_ramp(chroma(color.gold)), + green: color_ramp(chroma(green)), + cyan: color_ramp(chroma(color.pine)), + blue: color_ramp(chroma(color.foam)), + violet: color_ramp(chroma(color.iris)), + magenta: color_ramp(chroma(magenta)), }, override: { syntax: syntax(color), diff --git a/styles/src/themes/rose-pine/rose-pine-moon.ts b/styles/src/themes/rose-pine/rose-pine-moon.ts index c5ef0c997f..450d6865e7 100644 --- a/styles/src/themes/rose-pine/rose-pine-moon.ts +++ b/styles/src/themes/rose-pine/rose-pine-moon.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -17,29 +17,29 @@ export const theme: ThemeConfig = { name: "Rosé Pine Moon", author: "edunfelt", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/edunfelt/base16-rose-pine-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ color.base, color.surface, - color.highlightHigh, + color.highlight_high, color.overlay, color.muted, color.subtle, color.text, ]) .domain([0, 0.3, 0.55, 1]), - red: colorRamp(chroma(color.love)), - orange: colorRamp(chroma(color.iris)), - yellow: colorRamp(chroma(color.gold)), - green: colorRamp(chroma(green)), - cyan: colorRamp(chroma(color.pine)), - blue: colorRamp(chroma(color.foam)), - violet: colorRamp(chroma(color.iris)), - magenta: colorRamp(chroma(magenta)), + red: color_ramp(chroma(color.love)), + orange: color_ramp(chroma(color.iris)), + yellow: color_ramp(chroma(color.gold)), + green: color_ramp(chroma(green)), + cyan: color_ramp(chroma(color.pine)), + blue: color_ramp(chroma(color.foam)), + violet: color_ramp(chroma(color.iris)), + magenta: color_ramp(chroma(magenta)), }, override: { syntax: syntax(color), diff --git a/styles/src/themes/rose-pine/rose-pine.ts b/styles/src/themes/rose-pine/rose-pine.ts index 0f3b439338..b305b5b577 100644 --- a/styles/src/themes/rose-pine/rose-pine.ts +++ b/styles/src/themes/rose-pine/rose-pine.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -16,27 +16,27 @@ export const theme: ThemeConfig = { name: "Rosé Pine", author: "edunfelt", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/edunfelt/base16-rose-pine-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/edunfelt/base16-rose-pine-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ color.base, color.surface, - color.highlightHigh, + color.highlight_high, color.overlay, color.muted, color.subtle, color.text, ]), - red: colorRamp(chroma(color.love)), - orange: colorRamp(chroma(color.iris)), - yellow: colorRamp(chroma(color.gold)), - green: colorRamp(chroma(green)), - cyan: colorRamp(chroma(color.pine)), - blue: colorRamp(chroma(color.foam)), - violet: colorRamp(chroma(color.iris)), - magenta: colorRamp(chroma(magenta)), + red: color_ramp(chroma(color.love)), + orange: color_ramp(chroma(color.iris)), + yellow: color_ramp(chroma(color.gold)), + green: color_ramp(chroma(green)), + cyan: color_ramp(chroma(color.pine)), + blue: color_ramp(chroma(color.foam)), + violet: color_ramp(chroma(color.iris)), + magenta: color_ramp(chroma(magenta)), }, override: { syntax: syntax(color), diff --git a/styles/src/themes/sandcastle/LICENSE b/styles/src/themes/sandcastle/LICENSE index c66a06c51b..ba6559d810 100644 --- a/styles/src/themes/sandcastle/LICENSE +++ b/styles/src/themes/sandcastle/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/sandcastle/sandcastle.ts b/styles/src/themes/sandcastle/sandcastle.ts index 753828c665..b54c402e47 100644 --- a/styles/src/themes/sandcastle/sandcastle.ts +++ b/styles/src/themes/sandcastle/sandcastle.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -10,10 +10,10 @@ export const theme: ThemeConfig = { name: "Sandcastle", author: "gessig", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/gessig/base16-sandcastle-scheme", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/gessig/base16-sandcastle-scheme", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma.scale([ "#282c34", "#2c323b", @@ -24,14 +24,14 @@ export const theme: ThemeConfig = { "#d5c4a1", "#fdf4c1", ]), - red: colorRamp(chroma("#B4637A")), - orange: colorRamp(chroma("#a07e3b")), - yellow: colorRamp(chroma("#a07e3b")), - green: colorRamp(chroma("#83a598")), - cyan: colorRamp(chroma("#83a598")), - blue: colorRamp(chroma("#528b8b")), - violet: colorRamp(chroma("#d75f5f")), - magenta: colorRamp(chroma("#a87322")), + red: color_ramp(chroma("#B4637A")), + orange: color_ramp(chroma("#a07e3b")), + yellow: color_ramp(chroma("#a07e3b")), + green: color_ramp(chroma("#83a598")), + cyan: color_ramp(chroma("#83a598")), + blue: color_ramp(chroma("#528b8b")), + violet: color_ramp(chroma("#d75f5f")), + magenta: color_ramp(chroma("#a87322")), }, override: { syntax: {} }, } diff --git a/styles/src/themes/solarized/LICENSE b/styles/src/themes/solarized/LICENSE index 221eee6f15..2b5ddc4158 100644 --- a/styles/src/themes/solarized/LICENSE +++ b/styles/src/themes/solarized/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/solarized/solarized.ts b/styles/src/themes/solarized/solarized.ts index 4084757525..05e6f018ab 100644 --- a/styles/src/themes/solarized/solarized.ts +++ b/styles/src/themes/solarized/solarized.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -19,24 +19,24 @@ const ramps = { "#fdf6e3", ]) .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]), - red: colorRamp(chroma("#dc322f")), - orange: colorRamp(chroma("#cb4b16")), - yellow: colorRamp(chroma("#b58900")), - green: colorRamp(chroma("#859900")), - cyan: colorRamp(chroma("#2aa198")), - blue: colorRamp(chroma("#268bd2")), - violet: colorRamp(chroma("#6c71c4")), - magenta: colorRamp(chroma("#d33682")), + red: color_ramp(chroma("#dc322f")), + orange: color_ramp(chroma("#cb4b16")), + yellow: color_ramp(chroma("#b58900")), + green: color_ramp(chroma("#859900")), + cyan: color_ramp(chroma("#2aa198")), + blue: color_ramp(chroma("#268bd2")), + violet: color_ramp(chroma("#6c71c4")), + magenta: color_ramp(chroma("#d33682")), } export const dark: ThemeConfig = { name: "Solarized Dark", author: "Ethan Schoonover", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/altercation/solarized", - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/altercation/solarized", + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax: {} }, } @@ -44,9 +44,9 @@ export const light: ThemeConfig = { name: "Solarized Light", author: "Ethan Schoonover", appearance: ThemeAppearance.Light, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/altercation/solarized", - licenseFile: `${__dirname}/LICENSE`, - inputColor: ramps, + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/altercation/solarized", + license_file: `${__dirname}/LICENSE`, + input_color: ramps, override: { syntax: {} }, } diff --git a/styles/src/themes/summercamp/LICENSE b/styles/src/themes/summercamp/LICENSE index d7525414ad..dd49a64536 100644 --- a/styles/src/themes/summercamp/LICENSE +++ b/styles/src/themes/summercamp/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/styles/src/themes/summercamp/summercamp.ts b/styles/src/themes/summercamp/summercamp.ts index 08098d2e2f..f9037feae4 100644 --- a/styles/src/themes/summercamp/summercamp.ts +++ b/styles/src/themes/summercamp/summercamp.ts @@ -1,6 +1,6 @@ import { chroma, - colorRamp, + color_ramp, ThemeAppearance, ThemeLicenseType, ThemeConfig, @@ -10,10 +10,10 @@ export const theme: ThemeConfig = { name: "Summercamp", author: "zoefiri", appearance: ThemeAppearance.Dark, - licenseType: ThemeLicenseType.MIT, - licenseUrl: "https://github.com/zoefiri/base16-sc", - licenseFile: `${__dirname}/LICENSE`, - inputColor: { + license_type: ThemeLicenseType.MIT, + license_url: "https://github.com/zoefiri/base16-sc", + license_file: `${__dirname}/LICENSE`, + input_color: { neutral: chroma .scale([ "#1c1810", @@ -26,14 +26,14 @@ export const theme: ThemeConfig = { "#f8f5de", ]) .domain([0, 0.2, 0.38, 0.4, 0.65, 0.7, 0.85, 1]), - red: colorRamp(chroma("#e35142")), - orange: colorRamp(chroma("#fba11b")), - yellow: colorRamp(chroma("#f2ff27")), - green: colorRamp(chroma("#5ceb5a")), - cyan: colorRamp(chroma("#5aebbc")), - blue: colorRamp(chroma("#489bf0")), - violet: colorRamp(chroma("#FF8080")), - magenta: colorRamp(chroma("#F69BE7")), + red: color_ramp(chroma("#e35142")), + orange: color_ramp(chroma("#fba11b")), + yellow: color_ramp(chroma("#f2ff27")), + green: color_ramp(chroma("#5ceb5a")), + cyan: color_ramp(chroma("#5aebbc")), + blue: color_ramp(chroma("#489bf0")), + violet: color_ramp(chroma("#FF8080")), + magenta: color_ramp(chroma("#F69BE7")), }, override: { syntax: {} }, } diff --git a/styles/src/utils/slugify.ts b/styles/src/utils/slugify.ts index b5c3b3c519..04fd4d53bb 100644 --- a/styles/src/utils/slugify.ts +++ b/styles/src/utils/slugify.ts @@ -3,8 +3,8 @@ export function slugify(t: string): string { .toString() .toLowerCase() .replace(/\s+/g, "-") - .replace(/[^\w\-]+/g, "") - .replace(/\-\-+/g, "-") + .replace(/[^\w-]+/g, "") + .replace(/--+/g, "-") .replace(/^-+/, "") .replace(/-+$/, "") } diff --git a/styles/src/utils/snakeCase.ts b/styles/src/utils/snakeCase.ts deleted file mode 100644 index 5191064707..0000000000 --- a/styles/src/utils/snakeCase.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { snakeCase } from "case-anything" - -// https://stackoverflow.com/questions/60269936/typescript-convert-generic-object-from-snake-to-camel-case - -// Typescript magic to convert any string from camelCase to snake_case at compile time -type SnakeCase = S extends string - ? S extends `${infer T}${infer U}` - ? `${T extends Capitalize ? "_" : ""}${Lowercase}${SnakeCase}` - : S - : S - -type SnakeCased = { - [Property in keyof Type as SnakeCase]: SnakeCased -} - -export default function snakeCaseTree(object: T): SnakeCased { - const snakeObject: any = {} - for (const key in object) { - snakeObject[snakeCase(key, { keepSpecialCharacters: true })] = - snakeCaseValue(object[key]) - } - return snakeObject -} - -function snakeCaseValue(value: any): any { - if (typeof value === "object") { - if (Array.isArray(value)) { - return value.map(snakeCaseValue) - } else { - return snakeCaseTree(value) - } - } else { - return value - } -} diff --git a/styles/tsconfig.json b/styles/tsconfig.json index 7a8ec69927..925935ebb5 100644 --- a/styles/tsconfig.json +++ b/styles/tsconfig.json @@ -21,6 +21,7 @@ "experimentalDecorators": true, "strictPropertyInitialization": false, "skipLibCheck": true, + "useUnknownInCatchVariables": false, "baseUrl": ".", "paths": { "@/*": ["./*"], @@ -28,6 +29,7 @@ "@component/*": ["./src/component/*"], "@styleTree/*": ["./src/styleTree/*"], "@theme/*": ["./src/theme/*"], + "@types/*": ["./src/util/*"], "@themes/*": ["./src/themes/*"], "@util/*": ["./src/util/*"] }