diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 67a7ed31c9b5a4cfbf3ab6881c627b1a9db126c5..2cd2050013f7639ff3d3a4ea10379584f0e5f387 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,12 +2,11 @@ Release Notes: -Use `N/A` in this section if this item should be skipped in the release notes. +- N/A -Add release note lines here: +or -* (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/community/issues/)). -* ... +- (Added|Fixed|Improved) ... ([#](https://github.com/zed-industries/community/issues/)). If the release notes are only intended for a specific release channel only, add `(-only)` to the end of the release note line. These will be removed by the person making the release. diff --git a/Cargo.lock b/Cargo.lock index 35b3176749af29e02e8541602a65de989ed445fe..9634cd2c8eb8956e9066b270385035fcc75677a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1251,7 +1251,7 @@ dependencies = [ [[package]] name = "collab" -version = "0.12.4" +version = "0.12.5" dependencies = [ "anyhow", "async-tungstenite", @@ -2241,6 +2241,7 @@ dependencies = [ "log", "postage", "project", + "regex", "search", "serde", "serde_derive", @@ -8781,7 +8782,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.89.0" +version = "0.90.0" dependencies = [ "activity_indicator", "ai", diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index c6964b49e27dbff4d369656d88cd0a000abc5c13..f58e3c7cce790124cfaef66816899086182e3633 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -375,42 +375,9 @@ "workspace::ActivatePane", 8 ], - "cmd-b": [ - "workspace::ToggleLeftDock", - { - "focus": true - } - ], - "cmd-shift-b": [ - "workspace::ToggleLeftDock", - { - "focus": false - } - ], - "cmd-r": [ - "workspace::ToggleRightDock", - { - "focus": true - } - ], - "cmd-shift-r": [ - "workspace::ToggleRightDock", - { - "focus": false - } - ], - "cmd-j": [ - "workspace::ToggleBottomDock", - { - "focus": true - } - ], - "cmd-shift-j": [ - "workspace::ToggleBottomDock", - { - "focus": false - } - ], + "cmd-b": "workspace::ToggleLeftDock", + "cmd-r": "workspace::ToggleRightDock", + "cmd-j": "workspace::ToggleBottomDock", "cmd-shift-f": "workspace::NewSearch", "cmd-k cmd-t": "theme_selector::Toggle", "cmd-k cmd-s": "zed::OpenKeymap", diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index cd06b9a70a253eb9668d2704880638c1eeabaaba..07d2cdc60a6439a96a1a93bd9d212e0207bb59ac 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] default-run = "collab" edition = "2021" name = "collab" -version = "0.12.4" +version = "0.12.5" publish = false [[bin]] diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 7c6a49f179c2258cc2f18a834e8d63c9c70a6df8..b0b2a684d9501bf135f4280022437ef1c39f1230 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -112,6 +112,16 @@ CREATE INDEX "index_worktree_repository_statuses_on_project_id" ON "worktree_rep CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id" ON "worktree_repository_statuses" ("project_id", "worktree_id"); CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id_and_work_directory_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id"); +CREATE TABLE "worktree_settings_files" ( + "project_id" INTEGER NOT NULL, + "worktree_id" INTEGER NOT NULL, + "path" VARCHAR NOT NULL, + "content" TEXT, + PRIMARY KEY(project_id, worktree_id, path), + FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE +); +CREATE INDEX "index_worktree_settings_files_on_project_id" ON "worktree_settings_files" ("project_id"); +CREATE INDEX "index_worktree_settings_files_on_project_id_and_worktree_id" ON "worktree_settings_files" ("project_id", "worktree_id"); CREATE TABLE "worktree_diagnostic_summaries" ( "project_id" INTEGER NOT NULL, diff --git a/crates/collab/migrations/20230529164700_add_worktree_settings_files.sql b/crates/collab/migrations/20230529164700_add_worktree_settings_files.sql new file mode 100644 index 0000000000000000000000000000000000000000..973a40af0f21908e5dbe0d5a30373629f24b7f1e --- /dev/null +++ b/crates/collab/migrations/20230529164700_add_worktree_settings_files.sql @@ -0,0 +1,10 @@ +CREATE TABLE "worktree_settings_files" ( + "project_id" INTEGER NOT NULL, + "worktree_id" INT8 NOT NULL, + "path" VARCHAR NOT NULL, + "content" TEXT NOT NULL, + PRIMARY KEY(project_id, worktree_id, path), + FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE +); +CREATE INDEX "index_settings_files_on_project_id" ON "worktree_settings_files" ("project_id"); +CREATE INDEX "index_settings_files_on_project_id_and_wt_id" ON "worktree_settings_files" ("project_id", "worktree_id"); diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index fd28fb910177099d0cf7d639cfd802aa4f3dab25..1deca1baa8036113c4ad2c4f0535a18085cb5db1 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -16,6 +16,7 @@ mod worktree_diagnostic_summary; mod worktree_entry; mod worktree_repository; mod worktree_repository_statuses; +mod worktree_settings_file; use crate::executor::Executor; use crate::{Error, Result}; @@ -1494,6 +1495,7 @@ impl Database { updated_repositories: Default::default(), removed_repositories: Default::default(), diagnostic_summaries: Default::default(), + settings_files: Default::default(), scan_id: db_worktree.scan_id as u64, completed_scan_id: db_worktree.completed_scan_id as u64, }; @@ -1638,6 +1640,25 @@ impl Database { }) .collect::>(); + { + let mut db_settings_files = worktree_settings_file::Entity::find() + .filter(worktree_settings_file::Column::ProjectId.eq(project_id)) + .stream(&*tx) + .await?; + while let Some(db_settings_file) = db_settings_files.next().await { + let db_settings_file = db_settings_file?; + if let Some(worktree) = worktrees + .iter_mut() + .find(|w| w.id == db_settings_file.worktree_id as u64) + { + worktree.settings_files.push(WorktreeSettingsFile { + path: db_settings_file.path, + content: db_settings_file.content, + }); + } + } + } + let mut collaborators = project .find_related(project_collaborator::Entity) .all(&*tx) @@ -2637,6 +2658,58 @@ impl Database { .await } + pub async fn update_worktree_settings( + &self, + update: &proto::UpdateWorktreeSettings, + connection: ConnectionId, + ) -> Result>> { + let project_id = ProjectId::from_proto(update.project_id); + let room_id = self.room_id_for_project(project_id).await?; + self.room_transaction(room_id, |tx| async move { + // Ensure the update comes from the host. + let project = project::Entity::find_by_id(project_id) + .one(&*tx) + .await? + .ok_or_else(|| anyhow!("no such project"))?; + if project.host_connection()? != connection { + return Err(anyhow!("can't update a project hosted by someone else"))?; + } + + if let Some(content) = &update.content { + worktree_settings_file::Entity::insert(worktree_settings_file::ActiveModel { + project_id: ActiveValue::Set(project_id), + worktree_id: ActiveValue::Set(update.worktree_id as i64), + path: ActiveValue::Set(update.path.clone()), + content: ActiveValue::Set(content.clone()), + }) + .on_conflict( + OnConflict::columns([ + worktree_settings_file::Column::ProjectId, + worktree_settings_file::Column::WorktreeId, + worktree_settings_file::Column::Path, + ]) + .update_column(worktree_settings_file::Column::Content) + .to_owned(), + ) + .exec(&*tx) + .await?; + } else { + worktree_settings_file::Entity::delete(worktree_settings_file::ActiveModel { + project_id: ActiveValue::Set(project_id), + worktree_id: ActiveValue::Set(update.worktree_id as i64), + path: ActiveValue::Set(update.path.clone()), + ..Default::default() + }) + .exec(&*tx) + .await?; + } + + let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?; + Ok(connection_ids) + }) + .await + } + pub async fn join_project( &self, project_id: ProjectId, @@ -2707,6 +2780,7 @@ impl Database { entries: Default::default(), repository_entries: Default::default(), diagnostic_summaries: Default::default(), + settings_files: Default::default(), scan_id: db_worktree.scan_id as u64, completed_scan_id: db_worktree.completed_scan_id as u64, }, @@ -2819,6 +2893,25 @@ impl Database { } } + // Populate worktree settings files + { + let mut db_settings_files = worktree_settings_file::Entity::find() + .filter(worktree_settings_file::Column::ProjectId.eq(project_id)) + .stream(&*tx) + .await?; + while let Some(db_settings_file) = db_settings_files.next().await { + let db_settings_file = db_settings_file?; + if let Some(worktree) = + worktrees.get_mut(&(db_settings_file.worktree_id as u64)) + { + worktree.settings_files.push(WorktreeSettingsFile { + path: db_settings_file.path, + content: db_settings_file.content, + }); + } + } + } + // Populate language servers. let language_servers = project .find_related(language_server::Entity) @@ -3482,6 +3575,7 @@ pub struct RejoinedWorktree { pub updated_repositories: Vec, pub removed_repositories: Vec, pub diagnostic_summaries: Vec, + pub settings_files: Vec, pub scan_id: u64, pub completed_scan_id: u64, } @@ -3537,10 +3631,17 @@ pub struct Worktree { pub entries: Vec, pub repository_entries: BTreeMap, pub diagnostic_summaries: Vec, + pub settings_files: Vec, pub scan_id: u64, pub completed_scan_id: u64, } +#[derive(Debug)] +pub struct WorktreeSettingsFile { + pub path: String, + pub content: String, +} + #[cfg(test)] pub use test::*; diff --git a/crates/collab/src/db/worktree_settings_file.rs b/crates/collab/src/db/worktree_settings_file.rs new file mode 100644 index 0000000000000000000000000000000000000000..f8e87f6e599cbaaeb698bdb28cb1316c7097dc77 --- /dev/null +++ b/crates/collab/src/db/worktree_settings_file.rs @@ -0,0 +1,19 @@ +use super::ProjectId; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "worktree_settings_files")] +pub struct Model { + #[sea_orm(primary_key)] + pub project_id: ProjectId, + #[sea_orm(primary_key)] + pub worktree_id: i64, + #[sea_orm(primary_key)] + pub path: String, + pub content: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 4c117b613da90ce6ac54c792833aa94364715758..61c6123a827e897d9bf8c5db15e9e79fdbd4b951 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -200,6 +200,7 @@ impl Server { .add_message_handler(start_language_server) .add_message_handler(update_language_server) .add_message_handler(update_diagnostic_summary) + .add_message_handler(update_worktree_settings) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) .add_request_handler(forward_project_request::) @@ -1088,6 +1089,18 @@ async fn rejoin_room( }, )?; } + + for settings_file in worktree.settings_files { + session.peer.send( + session.connection_id, + proto::UpdateWorktreeSettings { + project_id: project.id.to_proto(), + worktree_id: worktree.id, + path: settings_file.path, + content: Some(settings_file.content), + }, + )?; + } } for language_server in &project.language_servers { @@ -1410,6 +1423,18 @@ async fn join_project( }, )?; } + + for settings_file in dbg!(worktree.settings_files) { + session.peer.send( + session.connection_id, + proto::UpdateWorktreeSettings { + project_id: project_id.to_proto(), + worktree_id: worktree.id, + path: settings_file.path, + content: Some(settings_file.content), + }, + )?; + } } for language_server in &project.language_servers { @@ -1525,6 +1550,31 @@ async fn update_diagnostic_summary( Ok(()) } +async fn update_worktree_settings( + message: proto::UpdateWorktreeSettings, + session: Session, +) -> Result<()> { + dbg!(&message); + + let guest_connection_ids = session + .db() + .await + .update_worktree_settings(&message, session.connection_id) + .await?; + + broadcast( + Some(session.connection_id), + guest_connection_ids.iter().copied(), + |connection_id| { + session + .peer + .forward_send(session.connection_id, connection_id, message.clone()) + }, + ); + + Ok(()) +} + async fn start_language_server( request: proto::StartLanguageServer, session: Session, diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 53726113db7d23ce0aa6f7ab04be2ba7c75aa2d2..5bc1a4e414d393a346af706b961f59236eb656bc 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -3114,6 +3114,135 @@ async fn test_fs_operations( }); } +#[gpui::test(iterations = 10)] +async fn test_local_settings( + 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); + + // As client A, open a project that contains some local settings files + client_a + .fs + .insert_tree( + "/dir", + json!({ + ".zed": { + "settings.json": r#"{ "tab_size": 2 }"# + }, + "a": { + ".zed": { + "settings.json": r#"{ "tab_size": 8 }"# + }, + "a.txt": "a-contents", + }, + "b": { + "b.txt": "b-contents", + } + }), + ) + .await; + let (project_a, _) = client_a.build_local_project("/dir", cx_a).await; + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + + // As client B, join that project and observe the local settings. + let project_b = client_b.build_remote_project(project_id, cx_b).await; + let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap()); + deterministic.run_until_parked(); + cx_b.read(|cx| { + let store = cx.global::(); + assert_eq!( + store.local_settings(worktree_b.id()).collect::>(), + &[ + (Path::new("").into(), r#"{"tab_size":2}"#.to_string()), + (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), + ] + ) + }); + + // As client A, update a settings file. As Client B, see the changed settings. + client_a + .fs + .insert_file("/dir/.zed/settings.json", r#"{}"#.into()) + .await; + deterministic.run_until_parked(); + cx_b.read(|cx| { + let store = cx.global::(); + assert_eq!( + store.local_settings(worktree_b.id()).collect::>(), + &[ + (Path::new("").into(), r#"{}"#.to_string()), + (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), + ] + ) + }); + + // As client A, create and remove some settings files. As client B, see the changed settings. + client_a + .fs + .remove_file("/dir/.zed/settings.json".as_ref(), Default::default()) + .await + .unwrap(); + client_a + .fs + .create_dir("/dir/b/.zed".as_ref()) + .await + .unwrap(); + client_a + .fs + .insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into()) + .await; + deterministic.run_until_parked(); + cx_b.read(|cx| { + let store = cx.global::(); + assert_eq!( + store.local_settings(worktree_b.id()).collect::>(), + &[ + (Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), + (Path::new("b").into(), r#"{"tab_size":4}"#.to_string()), + ] + ) + }); + + // As client B, disconnect. + server.forbid_connections(); + server.disconnect_client(client_b.peer_id().unwrap()); + + // As client A, change and remove settings files while client B is disconnected. + client_a + .fs + .insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into()) + .await; + client_a + .fs + .remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default()) + .await + .unwrap(); + deterministic.run_until_parked(); + + // As client B, reconnect and see the changed settings. + server.allow_connections(); + deterministic.advance_clock(RECEIVE_TIMEOUT); + cx_b.read(|cx| { + let store = cx.global::(); + assert_eq!( + store.local_settings(worktree_b.id()).collect::>(), + &[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),] + ) + }); +} + #[gpui::test(iterations = 10)] async fn test_buffer_conflict_after_save( deterministic: Arc, diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index de9104a6848d504eb78d28ab45da896d771dca29..7834603552fa14a36d81109c2a8030a8fef4efee 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -318,7 +318,7 @@ impl Copilot { fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext) { let http = self.http.clone(); let node_runtime = self.node_runtime.clone(); - if all_language_settings(cx).copilot_enabled(None, None) { + if all_language_settings(None, cx).copilot_enabled(None, None) { if matches!(self.server, CopilotServer::Disabled) { let start_task = cx .spawn({ @@ -785,10 +785,7 @@ impl Copilot { let buffer = buffer.read(cx); let uri = registered_buffer.uri.clone(); let position = position.to_point_utf16(buffer); - let settings = language_settings( - buffer.language_at(position).map(|l| l.name()).as_deref(), - cx, - ); + let settings = language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx); let tab_size = settings.tab_size; let hard_tabs = settings.hard_tabs; let relative_path = buffer @@ -1175,6 +1172,10 @@ mod tests { fn to_proto(&self) -> rpc::proto::File { unimplemented!() } + + fn worktree_id(&self) -> usize { + 0 + } } impl language::LocalFile for File { diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index 17d27ca41f66caf08117518d29b7f6f627febe4c..e34fddd9b9c5f200bf5bb24120089208bba05a3e 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -9,7 +9,10 @@ use gpui::{ AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; -use language::language_settings::{self, all_language_settings, AllLanguageSettings}; +use language::{ + language_settings::{self, all_language_settings, AllLanguageSettings}, + File, Language, +}; use settings::{update_settings_file, SettingsStore}; use std::{path::Path, sync::Arc}; use util::{paths, ResultExt}; @@ -26,8 +29,8 @@ pub struct CopilotButton { popup_menu: ViewHandle, editor_subscription: Option<(Subscription, usize)>, editor_enabled: Option, - language: Option>, - path: Option>, + language: Option>, + file: Option>, fs: Arc, } @@ -41,7 +44,7 @@ impl View for CopilotButton { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - let all_language_settings = &all_language_settings(cx); + let all_language_settings = all_language_settings(None, cx); if !all_language_settings.copilot.feature_enabled { return Empty::new().into_any(); } @@ -165,7 +168,7 @@ impl CopilotButton { editor_subscription: None, editor_enabled: None, language: None, - path: None, + file: None, fs, } } @@ -197,14 +200,13 @@ impl CopilotButton { if let Some(language) = self.language.clone() { let fs = fs.clone(); - let language_enabled = - language_settings::language_settings(Some(language.as_ref()), cx) - .show_copilot_suggestions; + let language_enabled = language_settings::language_settings(Some(&language), None, cx) + .show_copilot_suggestions; menu_options.push(ContextMenuItem::handler( format!( "{} Suggestions for {}", if language_enabled { "Hide" } else { "Show" }, - language + language.name() ), move |cx| toggle_copilot_for_language(language.clone(), fs.clone(), cx), )); @@ -212,9 +214,9 @@ impl CopilotButton { let settings = settings::get::(cx); - if let Some(path) = self.path.as_ref() { - let path_enabled = settings.copilot_enabled_for_path(path); - let path = path.clone(); + if let Some(file) = &self.file { + let path = file.path().clone(); + let path_enabled = settings.copilot_enabled_for_path(&path); menu_options.push(ContextMenuItem::handler( format!( "{} Suggestions for This Path", @@ -276,17 +278,15 @@ impl CopilotButton { let editor = editor.read(cx); let snapshot = editor.buffer().read(cx).snapshot(cx); let suggestion_anchor = editor.selections.newest_anchor().start; - let language_name = snapshot - .language_at(suggestion_anchor) - .map(|language| language.name()); - let path = snapshot.file_at(suggestion_anchor).map(|file| file.path()); + let language = snapshot.language_at(suggestion_anchor); + let file = snapshot.file_at(suggestion_anchor).cloned(); self.editor_enabled = Some( - all_language_settings(cx) - .copilot_enabled(language_name.as_deref(), path.map(|p| p.as_ref())), + all_language_settings(self.file.as_ref(), cx) + .copilot_enabled(language, file.as_ref().map(|file| file.path().as_ref())), ); - self.language = language_name; - self.path = path.cloned(); + self.language = language.cloned(); + self.file = file; cx.notify() } @@ -363,17 +363,18 @@ async fn configure_disabled_globs( } fn toggle_copilot_globally(fs: Arc, cx: &mut AppContext) { - let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(None, None); + let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None); update_settings_file::(fs, cx, move |file| { file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into()) }); } -fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut AppContext) { - let show_copilot_suggestions = all_language_settings(cx).copilot_enabled(Some(&language), None); +fn toggle_copilot_for_language(language: Arc, fs: Arc, cx: &mut AppContext) { + let show_copilot_suggestions = + all_language_settings(None, cx).copilot_enabled(Some(&language), None); update_settings_file::(fs, cx, move |file| { file.languages - .entry(language) + .entry(language.name()) .or_default() .show_copilot_suggestions = Some(!show_copilot_suggestions); }); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 366e47ddc640fa6c9ee205debdc7b837b205ebe8..b0483db68dcb781619188791b3f9b3630e902584 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -272,12 +272,11 @@ impl DisplayMap { } fn tab_size(buffer: &ModelHandle, cx: &mut ModelContext) -> NonZeroU32 { - let language_name = buffer + let language = buffer .read(cx) .as_singleton() - .and_then(|buffer| buffer.read(cx).language()) - .map(|language| language.name()); - language_settings(language_name.as_deref(), cx).tab_size + .and_then(|buffer| buffer.read(cx).language()); + language_settings(language.as_deref(), None, cx).tab_size } #[cfg(test)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 16fce156a4ba99c760e81f93b932fe511de4043a..a3601f3c589877dd44ff997e40e92c2d8e2510c2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -31,7 +31,9 @@ use copilot::Copilot; pub use display_map::DisplayPoint; use display_map::*; pub use editor_settings::EditorSettings; -pub use element::*; +pub use element::{ + Cursor, EditorElement, HighlightedRange, HighlightedRangeLine, LineWithInvisibles, +}; use futures::FutureExt; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ @@ -3214,12 +3216,10 @@ impl Editor { snapshot: &MultiBufferSnapshot, cx: &mut ViewContext, ) -> bool { - let path = snapshot.file_at(location).map(|file| file.path().as_ref()); - let language_name = snapshot - .language_at(location) - .map(|language| language.name()); - let settings = all_language_settings(cx); - settings.copilot_enabled(language_name.as_deref(), path) + let file = snapshot.file_at(location); + let language = snapshot.language_at(location); + let settings = all_language_settings(file, cx); + settings.copilot_enabled(language, file.map(|f| f.path().as_ref())) } fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool { @@ -7102,11 +7102,13 @@ impl Editor { }; // If None, we are in a file without an extension - let file_extension = file_extension.or(self + let file = self .buffer .read(cx) .as_singleton() - .and_then(|b| b.read(cx).file()) + .and_then(|b| b.read(cx).file()); + let file_extension = file_extension.or(file + .as_ref() .and_then(|file| Path::new(file.file_name(cx)).extension()) .and_then(|e| e.to_str()) .map(|a| a.to_string())); @@ -7117,7 +7119,7 @@ impl Editor { .get("vim_mode") == Some(&serde_json::Value::Bool(true)); let telemetry_settings = *settings::get::(cx); - let copilot_enabled = all_language_settings(cx).copilot_enabled(None, None); + let copilot_enabled = all_language_settings(file, cx).copilot_enabled(None, None); let copilot_enabled_for_language = self .buffer .read(cx) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 40e7c89cb298e34510002550349f7faff6f0fa19..8da746075e25f7130b739b82d40e33ceb2ef8130 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1231,6 +1231,10 @@ mod tests { unimplemented!() } + fn worktree_id(&self) -> usize { + 0 + } + fn is_deleted(&self) -> bool { unimplemented!() } diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 4650dff38f58088eab5fc1638080680dc86dd84e..b7b2b89c8ca312667bfb3b5988da8292fd33c6bc 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1377,8 +1377,14 @@ impl MultiBuffer { point: T, cx: &'a AppContext, ) -> &'a LanguageSettings { - let language = self.language_at(point, cx); - language_settings(language.map(|l| l.name()).as_deref(), cx) + let mut language = None; + let mut file = None; + if let Some((buffer, offset)) = self.point_to_buffer_offset(point, cx) { + let buffer = buffer.read(cx); + language = buffer.language_at(offset); + file = buffer.file(); + } + language_settings(language.as_ref(), file, cx) } pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle)) { @@ -2785,9 +2791,13 @@ impl MultiBufferSnapshot { point: T, cx: &'a AppContext, ) -> &'a LanguageSettings { - self.point_to_buffer_offset(point) - .map(|(buffer, offset)| buffer.settings_at(offset, cx)) - .unwrap_or_else(|| language_settings(None, cx)) + let mut language = None; + let mut file = None; + if let Some((buffer, offset)) = self.point_to_buffer_offset(point) { + language = buffer.language_at(offset); + file = buffer.file(); + } + language_settings(language, file, cx) } pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { diff --git a/crates/feedback/Cargo.toml b/crates/feedback/Cargo.toml index cd35afbda8ce36e7890f09fd6c02dceeeda51b4b..07b6ad790c8dcd553b8edb193f7d37179e212634 100644 --- a/crates/feedback/Cargo.toml +++ b/crates/feedback/Cargo.toml @@ -16,6 +16,7 @@ editor = { path = "../editor" } language = { path = "../language" } gpui = { path = "../gpui" } project = { path = "../project" } +regex.workspace = true search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } diff --git a/crates/feedback/src/feedback_editor.rs b/crates/feedback/src/feedback_editor.rs index d5d20b069ac108c70208b0230fe53b394143ffb2..5a4f912e3a063068adba31d787a21d1df42368b2 100644 --- a/crates/feedback/src/feedback_editor.rs +++ b/crates/feedback/src/feedback_editor.rs @@ -14,6 +14,7 @@ use isahc::Request; use language::Buffer; use postage::prelude::Stream; use project::Project; +use regex::Regex; use serde::Serialize; use smallvec::SmallVec; use std::{ @@ -46,6 +47,7 @@ pub fn init(cx: &mut AppContext) { #[derive(Serialize)] struct FeedbackRequestBody<'a> { feedback_text: &'a str, + email: Option, metrics_id: Option>, installation_id: Option>, system_specs: SystemSpecs, @@ -157,8 +159,18 @@ impl FeedbackEditor { let is_staff = telemetry.is_staff(); let http_client = zed_client.http_client(); + let re = Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap(); + + let emails: Vec<&str> = re + .captures_iter(feedback_text) + .map(|capture| capture.get(0).unwrap().as_str()) + .collect(); + + let email = emails.first().map(|e| e.to_string()); + let request = FeedbackRequestBody { feedback_text: &feedback_text, + email, metrics_id, installation_id, system_specs, diff --git a/crates/feedback/src/feedback_info_text.rs b/crates/feedback/src/feedback_info_text.rs index 5852cd9a6184449b08eb7844bf7ac6d11b3ca6ee..6c55b7a7139076b4b34410e063628d71f38765b2 100644 --- a/crates/feedback/src/feedback_info_text.rs +++ b/crates/feedback/src/feedback_info_text.rs @@ -34,7 +34,7 @@ impl View for FeedbackInfoText { Flex::row() .with_child( Text::new( - "We read whatever you submit here. For issues and discussions, visit the ", + "Share your feedback. Include your email for replies. For issues and discussions, visit the ", theme.feedback.info_text_default.text.clone(), ) .with_soft_wrap(false) @@ -60,7 +60,7 @@ impl View for FeedbackInfoText { }), ) .with_child( - Text::new(" on GitHub.", theme.feedback.info_text_default.text.clone()) + Text::new(".", theme.feedback.info_text_default.text.clone()) .with_soft_wrap(false) .aligned(), ) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 9ccd57b2919727ec1c60e8af525d8b6de73e8f77..8344914da0d8f0124fd6ddc5cc1cbc29ab2e4d1d 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -36,7 +36,7 @@ struct StateInner { scroll_to: Option, } -pub struct LayoutState { +pub struct UniformListLayoutState { scroll_max: f32, item_height: f32, items: Vec>, @@ -152,7 +152,7 @@ impl UniformList { } impl Element for UniformList { - type LayoutState = LayoutState; + type LayoutState = UniformListLayoutState; type PaintState = (); fn layout( @@ -169,7 +169,7 @@ impl Element for UniformList { let no_items = ( constraint.min, - LayoutState { + UniformListLayoutState { item_height: 0., scroll_max: 0., items: Default::default(), @@ -263,7 +263,7 @@ impl Element for UniformList { ( size, - LayoutState { + UniformListLayoutState { item_height, scroll_max, items, diff --git a/crates/gpui/src/font_cache.rs b/crates/gpui/src/font_cache.rs index 4388ad4bcbd2bea66149c05258bfe39d5682af03..57dad48e341005ff267dabe2af41951b1d41d657 100644 --- a/crates/gpui/src/font_cache.rs +++ b/crates/gpui/src/font_cache.rs @@ -25,8 +25,9 @@ struct Family { pub struct FontCache(RwLock); pub struct FontCacheState { - fonts: Arc, + font_system: Arc, families: Vec, + default_family: Option, font_selections: HashMap>, metrics: HashMap, wrapper_pool: HashMap<(FontId, OrderedFloat), Vec>, @@ -42,8 +43,9 @@ unsafe impl Send for FontCache {} impl FontCache { pub fn new(fonts: Arc) -> Self { Self(RwLock::new(FontCacheState { - fonts, + font_system: fonts, families: Default::default(), + default_family: None, font_selections: Default::default(), metrics: Default::default(), wrapper_pool: Default::default(), @@ -73,14 +75,14 @@ impl FontCache { let mut state = RwLockUpgradableReadGuard::upgrade(state); - if let Ok(font_ids) = state.fonts.load_family(name, features) { + if let Ok(font_ids) = state.font_system.load_family(name, features) { if font_ids.is_empty() { continue; } let family_id = FamilyId(state.families.len()); for font_id in &font_ids { - if state.fonts.glyph_for_char(*font_id, 'm').is_none() { + if state.font_system.glyph_for_char(*font_id, 'm').is_none() { return Err(anyhow!("font must contain a glyph for the 'm' character")); } } @@ -99,6 +101,31 @@ impl FontCache { )) } + /// Returns an arbitrary font family that is available on the system. + pub fn known_existing_family(&self) -> FamilyId { + if let Some(family_id) = self.0.read().default_family { + return family_id; + } + + let default_family = self + .load_family( + &["Courier", "Helvetica", "Arial", "Verdana"], + &Default::default(), + ) + .unwrap_or_else(|_| { + let all_family_names = self.0.read().font_system.all_families(); + let all_family_names: Vec<_> = all_family_names + .iter() + .map(|string| string.as_str()) + .collect(); + self.load_family(&all_family_names, &Default::default()) + .expect("could not load any default font family") + }); + + self.0.write().default_family = Some(default_family); + default_family + } + pub fn default_font(&self, family_id: FamilyId) -> FontId { self.select_font(family_id, &Properties::default()).unwrap() } @@ -115,7 +142,7 @@ impl FontCache { let mut inner = RwLockUpgradableReadGuard::upgrade(inner); let family = &inner.families[family_id.0]; let font_id = inner - .fonts + .font_system .select_font(&family.font_ids, properties) .unwrap_or(family.font_ids[0]); @@ -137,7 +164,7 @@ impl FontCache { if let Some(metrics) = state.metrics.get(&font_id) { f(metrics) } else { - let metrics = state.fonts.font_metrics(font_id); + let metrics = state.font_system.font_metrics(font_id); let metric = f(&metrics); let mut state = RwLockUpgradableReadGuard::upgrade(state); state.metrics.insert(font_id, metrics); @@ -157,8 +184,11 @@ impl FontCache { let bounds; { let state = self.0.read(); - glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap(); - bounds = state.fonts.typographic_bounds(font_id, glyph_id).unwrap(); + glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap(); + bounds = state + .font_system + .typographic_bounds(font_id, glyph_id) + .unwrap(); } bounds.width() * self.em_scale(font_id, font_size) } @@ -168,8 +198,8 @@ impl FontCache { let advance; { let state = self.0.read(); - glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap(); - advance = state.fonts.advance(font_id, glyph_id).unwrap(); + glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap(); + advance = state.font_system.advance(font_id, glyph_id).unwrap(); } advance.x() * self.em_scale(font_id, font_size) } @@ -214,7 +244,7 @@ impl FontCache { .or_default(); let wrapper = wrappers .pop() - .unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.fonts.clone())); + .unwrap_or_else(|| LineWrapper::new(font_id, font_size, state.font_system.clone())); LineWrapperHandle { wrapper: Some(wrapper), font_cache: self.clone(), diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index e0f037acb8fe1ec5b954b049930fbaac0e69313f..5e77593d05298b232f83b0f53d0c947e79501b67 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -295,13 +295,14 @@ impl Default for TextStyle { .as_ref() .expect("TextStyle::default can only be called within a call to with_font_cache"); - let font_family_name = Arc::from("Courier"); - let font_family_id = font_cache - .load_family(&[&font_family_name], &Default::default()) - .unwrap(); + let font_family_id = font_cache.known_existing_family(); let font_id = font_cache .select_font(font_family_id, &Default::default()) - .unwrap(); + .expect("did not have any font in system-provided family"); + let font_family_name = font_cache + .family_name(font_family_id) + .expect("we loaded this family from the font cache, so this should work"); + Self { color: Default::default(), font_family_name, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 9b4fd7ca510a49a12b439da29a6df8002f9dada0..7fc02b054808786d412b45d532d72156c1ffefa5 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -343,6 +343,7 @@ pub enum RasterizationOptions { pub trait FontSystem: Send + Sync { fn add_fonts(&self, fonts: &[Arc>]) -> anyhow::Result<()>; + fn all_families(&self) -> Vec; fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result>; fn select_font( &self, diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index 96916698fcd126653ac9f9cd9748212f7ef41203..0414cb8a6ce051419fa94e1bddda7a1c23d6cc17 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -66,6 +66,14 @@ impl platform::FontSystem for FontSystem { self.0.write().add_fonts(fonts) } + fn all_families(&self) -> Vec { + self.0 + .read() + .system_source + .all_families() + .expect("core text should never return an error") + } + fn load_family(&self, name: &str, features: &Features) -> anyhow::Result> { self.0.write().load_family(name, features) } diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 93b50cf597cfd3e0ed18ac8e317fa5847740f1b1..7acb36a92f8f87ed7f0d18b093a5cefe7068f6b1 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -216,6 +216,11 @@ pub trait File: Send + Sync { /// of its worktree, then this method will return the name of the worktree itself. fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr; + /// Returns the id of the worktree to which this file belongs. + /// + /// This is needed for looking up project-specific settings. + fn worktree_id(&self) -> usize; + fn is_deleted(&self) -> bool; fn as_any(&self) -> &dyn Any; @@ -1802,8 +1807,7 @@ impl BufferSnapshot { } pub fn language_indent_size_at(&self, position: T, cx: &AppContext) -> IndentSize { - let language_name = self.language_at(position).map(|language| language.name()); - let settings = language_settings(language_name.as_deref(), cx); + let settings = language_settings(self.language_at(position), self.file(), cx); if settings.hard_tabs { IndentSize::tab() } else { @@ -2127,8 +2131,7 @@ impl BufferSnapshot { position: D, cx: &'a AppContext, ) -> &'a LanguageSettings { - let language = self.language_at(position); - language_settings(language.map(|l| l.name()).as_deref(), cx) + language_settings(self.language_at(position), self.file.as_ref(), cx) } pub fn language_scope_at(&self, position: D) -> Option { diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index c98297c03648f7db9c307a592b4f7bf2dcfe279d..332e789b4d7e364ab24c382a4daa9928291967cd 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,3 +1,4 @@ +use crate::{File, Language}; use anyhow::Result; use collections::HashMap; use globset::GlobMatcher; @@ -13,12 +14,21 @@ pub fn init(cx: &mut AppContext) { settings::register::(cx); } -pub fn language_settings<'a>(language: Option<&str>, cx: &'a AppContext) -> &'a LanguageSettings { - settings::get::(cx).language(language) +pub fn language_settings<'a>( + language: Option<&Arc>, + file: Option<&Arc>, + cx: &'a AppContext, +) -> &'a LanguageSettings { + let language_name = language.map(|l| l.name()); + all_language_settings(file, cx).language(language_name.as_deref()) } -pub fn all_language_settings<'a>(cx: &'a AppContext) -> &'a AllLanguageSettings { - settings::get::(cx) +pub fn all_language_settings<'a>( + file: Option<&Arc>, + cx: &'a AppContext, +) -> &'a AllLanguageSettings { + let location = file.map(|f| (f.worktree_id(), f.path().as_ref())); + settings::get_local(location, cx) } #[derive(Debug, Clone)] @@ -155,7 +165,7 @@ impl AllLanguageSettings { .any(|glob| glob.is_match(path)) } - pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool { + pub fn copilot_enabled(&self, language: Option<&Arc>, path: Option<&Path>) -> bool { if !self.copilot.feature_enabled { return false; } @@ -166,7 +176,8 @@ impl AllLanguageSettings { } } - self.language(language_name).show_copilot_suggestions + self.language(language.map(|l| l.name()).as_deref()) + .show_copilot_suggestions } } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index ce7004dd317361a6cf751f9d860edce50a0d4311..490f3bde17e58ea0844d49bdec0003da6dcee02e 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1717,8 +1717,7 @@ impl LspCommand for OnTypeFormatting { .await?; let tab_size = buffer.read_with(&cx, |buffer, cx| { - let language_name = buffer.language().map(|language| language.name()); - language_settings(language_name.as_deref(), cx).tab_size + language_settings(buffer.language(), buffer.file(), cx).tab_size }); Ok(Self { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index fd522c5061fc37b1688a13aa11e12eaff1d54e12..7ce07959d7972c76f32cb43dce5bf832d5df23d7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -28,7 +28,7 @@ use gpui::{ ModelHandle, Task, WeakModelHandle, }; use language::{ - language_settings::{all_language_settings, language_settings, FormatOnSave, Formatter}, + language_settings::{language_settings, FormatOnSave, Formatter}, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -72,7 +72,10 @@ use std::{ time::{Duration, Instant, SystemTime}, }; use terminals::Terminals; -use util::{debug_panic, defer, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _}; +use util::{ + debug_panic, defer, merge_json_value_into, paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, + ResultExt, TryFutureExt as _, +}; pub use fs::*; pub use worktree::*; @@ -460,6 +463,7 @@ impl Project { client.add_model_request_handler(Self::handle_update_buffer); client.add_model_message_handler(Self::handle_update_diagnostic_summary); client.add_model_message_handler(Self::handle_update_worktree); + client.add_model_message_handler(Self::handle_update_worktree_settings); client.add_model_request_handler(Self::handle_create_project_entry); client.add_model_request_handler(Self::handle_rename_project_entry); client.add_model_request_handler(Self::handle_copy_project_entry); @@ -686,42 +690,37 @@ impl Project { } fn on_settings_changed(&mut self, cx: &mut ModelContext) { - let settings = all_language_settings(cx); - let mut language_servers_to_start = Vec::new(); for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language()) - { - if settings - .language(Some(&language.name())) - .enable_language_server - { - let worktree = file.worktree.read(cx); - language_servers_to_start.push(( - worktree.id(), - worktree.as_local().unwrap().abs_path().clone(), - language.clone(), - )); + if let Some((file, language)) = buffer.file().zip(buffer.language()) { + let settings = language_settings(Some(language), Some(file), cx); + if settings.enable_language_server { + if let Some(file) = File::from_dyn(Some(file)) { + language_servers_to_start + .push((file.worktree.clone(), language.clone())); + } } } } } let mut language_servers_to_stop = Vec::new(); - for language in self.languages.to_vec() { - for lsp_adapter in language.lsp_adapters() { - if !settings - .language(Some(&language.name())) - .enable_language_server - { - let lsp_name = &lsp_adapter.name; - for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { - if lsp_name == started_lsp_name { - language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); - } - } + let languages = self.languages.to_vec(); + for (worktree_id, started_lsp_name) in self.language_server_ids.keys() { + let language = languages.iter().find(|l| { + l.lsp_adapters() + .iter() + .any(|adapter| &adapter.name == started_lsp_name) + }); + if let Some(language) = language { + let worktree = self.worktree_for_id(*worktree_id, cx); + let file = worktree.and_then(|tree| { + tree.update(cx, |tree, cx| tree.root_file(cx).map(|f| f as _)) + }); + if !language_settings(Some(language), file.as_ref(), cx).enable_language_server { + language_servers_to_stop.push((*worktree_id, started_lsp_name.clone())); } } } @@ -733,8 +732,9 @@ impl Project { } // Start all the newly-enabled language servers. - for (worktree_id, worktree_path, language) in language_servers_to_start { - self.start_language_servers(worktree_id, worktree_path, language, cx); + for (worktree, language) in language_servers_to_start { + let worktree_path = worktree.read(cx).abs_path(); + self.start_language_servers(&worktree, worktree_path, language, cx); } if !self.copilot_enabled && Copilot::global(cx).is_some() { @@ -1107,6 +1107,21 @@ impl Project { .log_err(); } + let store = cx.global::(); + for worktree in self.worktrees(cx) { + let worktree_id = worktree.read(cx).id().to_proto(); + for (path, content) in store.local_settings(worktree.id()) { + self.client + .send(proto::UpdateWorktreeSettings { + project_id, + worktree_id, + path: path.to_string_lossy().into(), + content: Some(content), + }) + .log_err(); + } + } + let (updates_tx, mut updates_rx) = mpsc::unbounded(); let client = self.client.clone(); self.client_state = Some(ProjectClientState::Local { @@ -1219,6 +1234,14 @@ impl Project { message_id: u32, cx: &mut ModelContext, ) -> Result<()> { + cx.update_global::(|store, cx| { + for worktree in &self.worktrees { + store + .clear_local_settings(worktree.handle_id(), cx) + .log_err(); + } + }); + self.join_project_response_message_id = message_id; self.set_worktrees_from_proto(message.worktrees, cx)?; self.set_collaborators_from_proto(message.collaborators, cx)?; @@ -2321,25 +2344,34 @@ impl Project { }); if let Some(file) = File::from_dyn(buffer.read(cx).file()) { - if let Some(worktree) = file.worktree.read(cx).as_local() { - let worktree_id = worktree.id(); - let worktree_abs_path = worktree.abs_path().clone(); - self.start_language_servers(worktree_id, worktree_abs_path, new_language, cx); + let worktree = file.worktree.clone(); + if let Some(tree) = worktree.read(cx).as_local() { + self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); } } } fn start_language_servers( &mut self, - worktree_id: WorktreeId, + worktree: &ModelHandle, worktree_path: Arc, language: Arc, cx: &mut ModelContext, ) { - if !language_settings(Some(&language.name()), cx).enable_language_server { + if !language_settings( + Some(&language), + worktree + .update(cx, |tree, cx| tree.root_file(cx)) + .map(|f| f as _) + .as_ref(), + cx, + ) + .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) { @@ -2748,23 +2780,22 @@ impl Project { buffers: impl IntoIterator>, cx: &mut ModelContext, ) -> Option<()> { - let language_server_lookup_info: HashSet<(WorktreeId, Arc, Arc)> = buffers + let language_server_lookup_info: HashSet<(ModelHandle, Arc)> = buffers .into_iter() .filter_map(|buffer| { let buffer = buffer.read(cx); let file = File::from_dyn(buffer.file())?; - let worktree = file.worktree.read(cx).as_local()?; let full_path = file.full_path(cx); let language = self .languages .language_for_file(&full_path, Some(buffer.as_rope())) .now_or_never()? .ok()?; - Some((worktree.id(), worktree.abs_path().clone(), language)) + Some((file.worktree.clone(), language)) }) .collect(); - for (worktree_id, worktree_abs_path, language) in language_server_lookup_info { - self.restart_language_servers(worktree_id, worktree_abs_path, language, cx); + for (worktree, language) in language_server_lookup_info { + self.restart_language_servers(worktree, language, cx); } None @@ -2773,11 +2804,13 @@ impl Project { // TODO This will break in the case where the adapter's root paths and worktrees are not equal fn restart_language_servers( &mut self, - worktree_id: WorktreeId, - fallback_path: Arc, + worktree: ModelHandle, language: Arc, cx: &mut ModelContext, ) { + let worktree_id = worktree.read(cx).id(); + let fallback_path = worktree.read(cx).abs_path(); + let mut stops = Vec::new(); for adapter in language.lsp_adapters() { stops.push(self.stop_language_server(worktree_id, adapter.name.clone(), cx)); @@ -2807,7 +2840,7 @@ impl Project { .map(|path_buf| Arc::from(path_buf.as_path())) .unwrap_or(fallback_path); - this.start_language_servers(worktree_id, root_path, language.clone(), cx); + this.start_language_servers(&worktree, root_path, language.clone(), cx); // Lookup new server ids and set them for each of the orphaned worktrees for adapter in language.lsp_adapters() { @@ -3432,8 +3465,7 @@ impl Project { let mut project_transaction = ProjectTransaction::default(); for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers { let settings = buffer.read_with(&cx, |buffer, cx| { - let language_name = buffer.language().map(|language| language.name()); - language_settings(language_name.as_deref(), cx).clone() + language_settings(buffer.language(), buffer.file(), cx).clone() }); let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save; @@ -4463,11 +4495,14 @@ impl Project { push_to_history: bool, cx: &mut ModelContext, ) -> Task>> { - let tab_size = buffer.read_with(cx, |buffer, cx| { - let language_name = buffer.language().map(|language| language.name()); - language_settings(language_name.as_deref(), cx).tab_size + let (position, tab_size) = buffer.read_with(cx, |buffer, cx| { + let position = position.to_point_utf16(buffer); + ( + position, + language_settings(buffer.language_at(position).as_ref(), buffer.file(), cx) + .tab_size, + ) }); - let position = position.to_point_utf16(buffer.read(cx)); self.request_lsp( buffer.clone(), OnTypeFormatting { @@ -4873,6 +4908,7 @@ impl Project { worktree::Event::UpdatedEntries(changes) => { this.update_local_worktree_buffers(&worktree, changes, cx); this.update_local_worktree_language_servers(&worktree, changes, cx); + this.update_local_worktree_settings(&worktree, changes, cx); } worktree::Event::UpdatedGitRepositories(updated_repos) => { this.update_local_worktree_buffers_git_repos(worktree, updated_repos, cx) @@ -4893,8 +4929,12 @@ impl Project { .push(WorktreeHandle::Weak(worktree.downgrade())); } - cx.observe_release(worktree, |this, worktree, cx| { + let handle_id = worktree.id(); + cx.observe_release(worktree, move |this, worktree, cx| { let _ = this.remove_worktree(worktree.id(), cx); + cx.update_global::(|store, cx| { + store.clear_local_settings(handle_id, cx).log_err() + }); }) .detach(); @@ -5179,6 +5219,71 @@ impl Project { .detach(); } + fn update_local_worktree_settings( + &mut self, + worktree: &ModelHandle, + changes: &UpdatedEntriesSet, + cx: &mut ModelContext, + ) { + let project_id = self.remote_id(); + let worktree_id = worktree.id(); + let worktree = worktree.read(cx).as_local().unwrap(); + let remote_worktree_id = worktree.id(); + + let mut settings_contents = Vec::new(); + for (path, _, change) in changes.iter() { + if path.ends_with(&*LOCAL_SETTINGS_RELATIVE_PATH) { + let settings_dir = Arc::from( + path.ancestors() + .nth(LOCAL_SETTINGS_RELATIVE_PATH.components().count()) + .unwrap(), + ); + let fs = self.fs.clone(); + let removed = *change == PathChange::Removed; + let abs_path = worktree.absolutize(path); + settings_contents.push(async move { + (settings_dir, (!removed).then_some(fs.load(&abs_path).await)) + }); + } + } + + if settings_contents.is_empty() { + return; + } + + let client = self.client.clone(); + cx.spawn_weak(move |_, mut cx| async move { + let settings_contents: Vec<(Arc, _)> = + futures::future::join_all(settings_contents).await; + cx.update(|cx| { + cx.update_global::(|store, cx| { + for (directory, file_content) in settings_contents { + let file_content = file_content.and_then(|content| content.log_err()); + store + .set_local_settings( + worktree_id, + directory.clone(), + file_content.as_ref().map(String::as_str), + cx, + ) + .log_err(); + if let Some(remote_id) = project_id { + client + .send(proto::UpdateWorktreeSettings { + project_id: remote_id, + worktree_id: remote_worktree_id.to_proto(), + path: directory.to_string_lossy().into_owned(), + content: file_content, + }) + .log_err(); + } + } + }); + }); + }) + .detach(); + } + pub fn set_active_path(&mut self, entry: Option, cx: &mut ModelContext) { let new_active_entry = entry.and_then(|project_path| { let worktree = self.worktree_for_id(project_path.worktree_id, cx)?; @@ -5431,6 +5536,30 @@ impl Project { }) } + async fn handle_update_worktree_settings( + this: ModelHandle, + envelope: TypedEnvelope, + _: Arc, + mut cx: AsyncAppContext, + ) -> Result<()> { + this.update(&mut cx, |this, cx| { + let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); + if let Some(worktree) = this.worktree_for_id(worktree_id, cx) { + cx.update_global::(|store, cx| { + store + .set_local_settings( + worktree.id(), + PathBuf::from(&envelope.payload.path).into(), + envelope.payload.content.as_ref().map(String::as_str), + cx, + ) + .log_err(); + }); + } + Ok(()) + }) + } + async fn handle_create_project_entry( this: ModelHandle, envelope: TypedEnvelope, @@ -6521,8 +6650,8 @@ impl Project { } self.metadata_changed(cx); - for (id, _) in old_worktrees_by_id { - cx.emit(Event::WorktreeRemoved(id)); + for id in old_worktrees_by_id.keys() { + cx.emit(Event::WorktreeRemoved(*id)); } Ok(()) @@ -6892,6 +7021,13 @@ impl WorktreeHandle { WorktreeHandle::Weak(handle) => handle.upgrade(cx), } } + + pub fn handle_id(&self) -> usize { + match self { + WorktreeHandle::Strong(handle) => handle.id(), + WorktreeHandle::Weak(handle) => handle.id(), + } + } } impl OpenBuffer { diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 656fdaf25d17979de0338bc1bbe7508057455997..a67e38893b2457bcec814834c5f4d0142ecd6b12 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -63,6 +63,66 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_managing_project_specific_settings( + deterministic: Arc, + cx: &mut gpui::TestAppContext, +) { + init_test(cx); + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/the-root", + json!({ + ".zed": { + "settings.json": r#"{ "tab_size": 8 }"# + }, + "a": { + "a.rs": "fn a() {\n A\n}" + }, + "b": { + ".zed": { + "settings.json": r#"{ "tab_size": 2 }"# + }, + "b.rs": "fn b() {\n B\n}" + } + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/the-root".as_ref()], cx).await; + let worktree = project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap()); + + deterministic.run_until_parked(); + cx.read(|cx| { + let tree = worktree.read(cx); + + let settings_a = language_settings( + None, + Some( + &(File::for_entry( + tree.entry_for_path("a/a.rs").unwrap().clone(), + worktree.clone(), + ) as _), + ), + cx, + ); + let settings_b = language_settings( + None, + Some( + &(File::for_entry( + tree.entry_for_path("b/b.rs").unwrap().clone(), + worktree.clone(), + ) as _), + ), + cx, + ); + + assert_eq!(settings_a.tab_size.get(), 8); + assert_eq!(settings_b.tab_size.get(), 2); + }); +} + #[gpui::test] async fn test_managing_language_servers( deterministic: Arc, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index dc3c172775721c5142aedab6fc4f0f5d66a1c913..a33a0fc050177db96dfbb56a901331bb6928e5ff 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -677,6 +677,11 @@ impl Worktree { Worktree::Remote(worktree) => worktree.abs_path.clone(), } } + + pub fn root_file(&self, cx: &mut ModelContext) -> Option> { + let entry = self.root_entry()?; + Some(File::for_entry(entry.clone(), cx.handle())) + } } impl LocalWorktree { @@ -684,14 +689,6 @@ impl LocalWorktree { path.starts_with(&self.abs_path) } - fn absolutize(&self, path: &Path) -> PathBuf { - if path.file_name().is_some() { - self.abs_path.join(path) - } else { - self.abs_path.to_path_buf() - } - } - pub(crate) fn load_buffer( &mut self, id: u64, @@ -1544,6 +1541,14 @@ impl Snapshot { &self.abs_path } + pub fn absolutize(&self, path: &Path) -> PathBuf { + if path.file_name().is_some() { + self.abs_path.join(path) + } else { + self.abs_path.to_path_buf() + } + } + pub fn contains_entry(&self, entry_id: ProjectEntryId) -> bool { self.entries_by_id.get(&entry_id, &()).is_some() } @@ -2383,6 +2388,10 @@ impl language::File for File { .unwrap_or_else(|| OsStr::new(&self.worktree.read(cx).root_name)) } + fn worktree_id(&self) -> usize { + self.worktree.id() + } + fn is_deleted(&self) -> bool { self.is_deleted } @@ -2447,6 +2456,17 @@ impl language::LocalFile for File { } impl File { + pub fn for_entry(entry: Entry, worktree: ModelHandle) -> Arc { + Arc::new(Self { + worktree, + path: entry.path.clone(), + mtime: entry.mtime, + entry_id: entry.id, + is_local: true, + is_deleted: false, + }) + } + pub fn from_proto( proto: rpc::proto::File, worktree: ModelHandle, @@ -2507,7 +2527,7 @@ pub enum EntryKind { File(CharBag), } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub enum PathChange { /// A filesystem entry was was created. Added, diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 848cc1c2fa3213ce55f317e63b7a6d7db293671f..8351cdfd2dfe6a61bcccaad4d7a8dc40ff00bfc2 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -132,6 +132,8 @@ message Envelope { OnTypeFormatting on_type_formatting = 111; OnTypeFormattingResponse on_type_formatting_response = 112; + + UpdateWorktreeSettings update_worktree_settings = 113; } } @@ -339,6 +341,13 @@ message UpdateWorktree { string abs_path = 10; } +message UpdateWorktreeSettings { + uint64 project_id = 1; + uint64 worktree_id = 2; + string path = 3; + optional string content = 4; +} + message CreateProjectEntry { uint64 project_id = 1; uint64 worktree_id = 2; diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 07925a0486e6d210ceac44a7c5a5428b3f48a58e..13794ea64dad1446e579dd00806ccb4afda4758b 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -236,6 +236,7 @@ messages!( (UpdateProject, Foreground), (UpdateProjectCollaborator, Foreground), (UpdateWorktree, Foreground), + (UpdateWorktreeSettings, Foreground), (UpdateDiffBase, Foreground), (GetPrivateUserInfo, Foreground), (GetPrivateUserInfoResponse, Foreground), @@ -345,6 +346,7 @@ entity_messages!( UpdateProject, UpdateProjectCollaborator, UpdateWorktree, + UpdateWorktreeSettings, UpdateDiffBase ); diff --git a/crates/rpc/src/rpc.rs b/crates/rpc/src/rpc.rs index b929de95961e756d369f9b1549dc693d6536bbe0..bef6efa529fd514b24789a5d294dbc33409974ab 100644 --- a/crates/rpc/src/rpc.rs +++ b/crates/rpc/src/rpc.rs @@ -6,4 +6,4 @@ pub use conn::Connection; pub use peer::*; mod macros; -pub const PROTOCOL_VERSION: u32 = 56; +pub const PROTOCOL_VERSION: u32 = 57; diff --git a/crates/settings/src/settings_file.rs b/crates/settings/src/settings_file.rs index cca2909da22dba93842304b87c600e2b05a99ae4..3505330eda6fb94cc4ded9d90f313c40a11e7866 100644 --- a/crates/settings/src/settings_file.rs +++ b/crates/settings/src/settings_file.rs @@ -4,7 +4,14 @@ use assets::Assets; use fs::Fs; use futures::{channel::mpsc, StreamExt}; use gpui::{executor::Background, AppContext, AssetSource}; -use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration}; +use std::{ + borrow::Cow, + io::ErrorKind, + path::{Path, PathBuf}, + str, + sync::Arc, + time::Duration, +}; use util::{paths, ResultExt}; pub fn register(cx: &mut AppContext) { @@ -17,6 +24,10 @@ pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T { cx.global::().get(None) } +pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a T { + cx.global::().get(location) +} + pub fn default_settings() -> Cow<'static, str> { match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() { Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), @@ -55,15 +66,22 @@ pub fn watch_config_file( .spawn(async move { let events = fs.watch(&path, Duration::from_millis(100)).await; futures::pin_mut!(events); + + let contents = fs.load(&path).await.unwrap_or_default(); + if tx.unbounded_send(contents).is_err() { + return; + } + loop { + if events.next().await.is_none() { + break; + } + if let Ok(contents) = fs.load(&path).await { if !tx.unbounded_send(contents).is_ok() { break; } } - if events.next().await.is_none() { - break; - } } }) .detach(); diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 71b3cc635f4e03465d94cb498567c21bd36bd76a..3fdb81dd45cfee2671314ecc902cb38958841579 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{anyhow, Result}; use collections::{btree_map, hash_map, BTreeMap, HashMap}; use gpui::AppContext; use lazy_static::lazy_static; @@ -84,19 +84,30 @@ pub struct SettingsJsonSchemaParams<'a> { } /// A set of strongly-typed setting values defined via multiple JSON files. -#[derive(Default)] pub struct SettingsStore { setting_values: HashMap>, - default_deserialized_settings: Option, - user_deserialized_settings: Option, - local_deserialized_settings: BTreeMap, serde_json::Value>, + default_deserialized_settings: serde_json::Value, + user_deserialized_settings: serde_json::Value, + local_deserialized_settings: BTreeMap<(usize, Arc), serde_json::Value>, tab_size_callback: Option<(TypeId, Box Option>)>, } +impl Default for SettingsStore { + fn default() -> Self { + SettingsStore { + setting_values: Default::default(), + default_deserialized_settings: serde_json::json!({}), + user_deserialized_settings: serde_json::json!({}), + local_deserialized_settings: Default::default(), + tab_size_callback: Default::default(), + } + } +} + #[derive(Debug)] struct SettingValue { global_value: Option, - local_values: Vec<(Arc, T)>, + local_values: Vec<(usize, Arc, T)>, } trait AnySettingValue { @@ -109,9 +120,9 @@ trait AnySettingValue { custom: &[DeserializedSetting], cx: &AppContext, ) -> Result>; - fn value_for_path(&self, path: Option<&Path>) -> &dyn Any; + fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any; fn set_global_value(&mut self, value: Box); - fn set_local_value(&mut self, path: Arc, value: Box); + fn set_local_value(&mut self, root_id: usize, path: Arc, value: Box); fn json_schema( &self, generator: &mut SchemaGenerator, @@ -136,27 +147,24 @@ impl SettingsStore { local_values: Vec::new(), })); - if let Some(default_settings) = &self.default_deserialized_settings { - if let Some(default_settings) = setting_value - .deserialize_setting(default_settings) + if let Some(default_settings) = setting_value + .deserialize_setting(&self.default_deserialized_settings) + .log_err() + { + let mut user_values_stack = Vec::new(); + + if let Some(user_settings) = setting_value + .deserialize_setting(&self.user_deserialized_settings) .log_err() { - let mut user_values_stack = Vec::new(); - - if let Some(user_settings) = &self.user_deserialized_settings { - if let Some(user_settings) = - setting_value.deserialize_setting(user_settings).log_err() - { - user_values_stack = vec![user_settings]; - } - } + user_values_stack = vec![user_settings]; + } - if let Some(setting) = setting_value - .load_setting(&default_settings, &user_values_stack, cx) - .log_err() - { - setting_value.set_global_value(setting); - } + if let Some(setting) = setting_value + .load_setting(&default_settings, &user_values_stack, cx) + .log_err() + { + setting_value.set_global_value(setting); } } } @@ -165,7 +173,7 @@ impl SettingsStore { /// /// Panics if the given setting type has not been registered, or if there is no /// value for this setting. - pub fn get(&self, path: Option<&Path>) -> &T { + pub fn get(&self, path: Option<(usize, &Path)>) -> &T { self.setting_values .get(&TypeId::of::()) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) @@ -189,9 +197,7 @@ impl SettingsStore { /// This is only for debugging and reporting. For user-facing functionality, /// use the typed setting interface. pub fn untyped_user_settings(&self) -> &serde_json::Value { - self.user_deserialized_settings - .as_ref() - .unwrap_or(&serde_json::Value::Null) + &self.user_deserialized_settings } #[cfg(any(test, feature = "test-support"))] @@ -213,11 +219,7 @@ impl SettingsStore { cx: &AppContext, update: impl FnOnce(&mut T::FileContent), ) { - if self.user_deserialized_settings.is_none() { - self.set_user_settings("{}", cx).unwrap(); - } - let old_text = - serde_json::to_string(self.user_deserialized_settings.as_ref().unwrap()).unwrap(); + let old_text = serde_json::to_string(&self.user_deserialized_settings).unwrap(); let new_text = self.new_text_for_update::(old_text, update); self.set_user_settings(&new_text, cx).unwrap(); } @@ -250,11 +252,7 @@ impl SettingsStore { .setting_values .get(&setting_type_id) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::())) - .deserialize_setting( - self.user_deserialized_settings - .as_ref() - .expect("no user settings loaded"), - ) + .deserialize_setting(&self.user_deserialized_settings) .unwrap_or_else(|e| { panic!( "could not deserialize setting type {} from user settings: {}", @@ -323,10 +321,14 @@ impl SettingsStore { default_settings_content: &str, cx: &AppContext, ) -> Result<()> { - self.default_deserialized_settings = - Some(parse_json_with_comments(default_settings_content)?); - self.recompute_values(None, cx)?; - Ok(()) + let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?; + if settings.is_object() { + self.default_deserialized_settings = settings; + self.recompute_values(None, cx)?; + Ok(()) + } else { + Err(anyhow!("settings must be an object")) + } } /// Set the user settings via a JSON string. @@ -335,28 +337,49 @@ impl SettingsStore { user_settings_content: &str, cx: &AppContext, ) -> Result<()> { - self.user_deserialized_settings = Some(parse_json_with_comments(user_settings_content)?); - self.recompute_values(None, cx)?; - Ok(()) + let settings: serde_json::Value = parse_json_with_comments(user_settings_content)?; + if settings.is_object() { + self.user_deserialized_settings = settings; + self.recompute_values(None, cx)?; + Ok(()) + } else { + Err(anyhow!("settings must be an object")) + } } /// Add or remove a set of local settings via a JSON string. pub fn set_local_settings( &mut self, + root_id: usize, path: Arc, settings_content: Option<&str>, cx: &AppContext, ) -> Result<()> { if let Some(content) = settings_content { self.local_deserialized_settings - .insert(path.clone(), parse_json_with_comments(content)?); + .insert((root_id, path.clone()), parse_json_with_comments(content)?); } else { - self.local_deserialized_settings.remove(&path); + self.local_deserialized_settings + .remove(&(root_id, path.clone())); } - self.recompute_values(Some(&path), cx)?; + self.recompute_values(Some((root_id, &path)), cx)?; Ok(()) } + /// Add or remove a set of local settings via a JSON string. + pub fn clear_local_settings(&mut self, root_id: usize, cx: &AppContext) -> Result<()> { + self.local_deserialized_settings + .retain(|k, _| k.0 != root_id); + self.recompute_values(Some((root_id, "".as_ref())), cx)?; + Ok(()) + } + + pub fn local_settings(&self, root_id: usize) -> impl '_ + Iterator, String)> { + self.local_deserialized_settings + .range((root_id, Path::new("").into())..(root_id + 1, Path::new("").into())) + .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap())) + } + pub fn json_schema( &self, schema_params: &SettingsJsonSchemaParams, @@ -436,72 +459,70 @@ impl SettingsStore { fn recompute_values( &mut self, - changed_local_path: Option<&Path>, + changed_local_path: Option<(usize, &Path)>, cx: &AppContext, ) -> Result<()> { // Reload the global and local values for every setting. let mut user_settings_stack = Vec::::new(); - let mut paths_stack = Vec::>::new(); + let mut paths_stack = Vec::>::new(); for setting_value in self.setting_values.values_mut() { - if let Some(default_settings) = &self.default_deserialized_settings { - let default_settings = setting_value.deserialize_setting(default_settings)?; + let default_settings = + setting_value.deserialize_setting(&self.default_deserialized_settings)?; - user_settings_stack.clear(); - paths_stack.clear(); + user_settings_stack.clear(); + paths_stack.clear(); - if let Some(user_settings) = &self.user_deserialized_settings { - if let Some(user_settings) = - setting_value.deserialize_setting(user_settings).log_err() - { - user_settings_stack.push(user_settings); - paths_stack.push(None); - } + if let Some(user_settings) = setting_value + .deserialize_setting(&self.user_deserialized_settings) + .log_err() + { + user_settings_stack.push(user_settings); + paths_stack.push(None); + } + + // If the global settings file changed, reload the global value for the field. + if changed_local_path.is_none() { + if let Some(value) = setting_value + .load_setting(&default_settings, &user_settings_stack, cx) + .log_err() + { + setting_value.set_global_value(value); } + } - // If the global settings file changed, reload the global value for the field. - if changed_local_path.is_none() { - if let Some(value) = setting_value - .load_setting(&default_settings, &user_settings_stack, cx) - .log_err() - { - setting_value.set_global_value(value); + // Reload the local values for the setting. + for ((root_id, path), local_settings) in &self.local_deserialized_settings { + // Build a stack of all of the local values for that setting. + while let Some(prev_entry) = paths_stack.last() { + if let Some((prev_root_id, prev_path)) = prev_entry { + if root_id != prev_root_id || !path.starts_with(prev_path) { + paths_stack.pop(); + user_settings_stack.pop(); + continue; + } } + break; } - // Reload the local values for the setting. - for (path, local_settings) in &self.local_deserialized_settings { - // Build a stack of all of the local values for that setting. - while let Some(prev_path) = paths_stack.last() { - if let Some(prev_path) = prev_path { - if !path.starts_with(prev_path) { - paths_stack.pop(); - user_settings_stack.pop(); - continue; - } - } - break; + if let Some(local_settings) = + setting_value.deserialize_setting(&local_settings).log_err() + { + paths_stack.push(Some((*root_id, path.as_ref()))); + user_settings_stack.push(local_settings); + + // If a local settings file changed, then avoid recomputing local + // settings for any path outside of that directory. + if changed_local_path.map_or(false, |(changed_root_id, changed_local_path)| { + *root_id != changed_root_id || !path.starts_with(changed_local_path) + }) { + continue; } - if let Some(local_settings) = - setting_value.deserialize_setting(&local_settings).log_err() + if let Some(value) = setting_value + .load_setting(&default_settings, &user_settings_stack, cx) + .log_err() { - paths_stack.push(Some(path.as_ref())); - user_settings_stack.push(local_settings); - - // If a local settings file changed, then avoid recomputing local - // settings for any path outside of that directory. - if changed_local_path.map_or(false, |changed_local_path| { - !path.starts_with(changed_local_path) - }) { - continue; - } - - if let Some(value) = setting_value - .load_setting(&default_settings, &user_settings_stack, cx) - .log_err() - { - setting_value.set_local_value(path.clone(), value); - } + setting_value.set_local_value(*root_id, path.clone(), value); } } } @@ -510,6 +531,24 @@ impl SettingsStore { } } +impl Debug for SettingsStore { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SettingsStore") + .field( + "types", + &self + .setting_values + .values() + .map(|value| value.setting_type_name()) + .collect::>(), + ) + .field("default_settings", &self.default_deserialized_settings) + .field("user_settings", &self.user_deserialized_settings) + .field("local_settings", &self.local_deserialized_settings) + .finish_non_exhaustive() + } +} + impl AnySettingValue for SettingValue { fn key(&self) -> Option<&'static str> { T::KEY @@ -546,10 +585,10 @@ impl AnySettingValue for SettingValue { Ok(DeserializedSetting(Box::new(value))) } - fn value_for_path(&self, path: Option<&Path>) -> &dyn Any { - if let Some(path) = path { - for (settings_path, value) in self.local_values.iter().rev() { - if path.starts_with(&settings_path) { + fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any { + if let Some((root_id, path)) = path { + for (settings_root_id, settings_path, value) in self.local_values.iter().rev() { + if root_id == *settings_root_id && path.starts_with(&settings_path) { return value; } } @@ -563,11 +602,14 @@ impl AnySettingValue for SettingValue { self.global_value = Some(*value.downcast().unwrap()); } - fn set_local_value(&mut self, path: Arc, value: Box) { + fn set_local_value(&mut self, root_id: usize, path: Arc, value: Box) { let value = *value.downcast().unwrap(); - match self.local_values.binary_search_by_key(&&path, |e| &e.0) { - Ok(ix) => self.local_values[ix].1 = value, - Err(ix) => self.local_values.insert(ix, (path, value)), + match self + .local_values + .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1)) + { + Ok(ix) => self.local_values[ix].2 = value, + Err(ix) => self.local_values.insert(ix, (root_id, path, value)), } } @@ -884,6 +926,7 @@ mod tests { store .set_local_settings( + 1, Path::new("/root1").into(), Some(r#"{ "user": { "staff": true } }"#), cx, @@ -891,6 +934,7 @@ mod tests { .unwrap(); store .set_local_settings( + 1, Path::new("/root1/subdir").into(), Some(r#"{ "user": { "name": "Jane Doe" } }"#), cx, @@ -899,6 +943,7 @@ mod tests { store .set_local_settings( + 1, Path::new("/root2").into(), Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#), cx, @@ -906,7 +951,7 @@ mod tests { .unwrap(); assert_eq!( - store.get::(Some(Path::new("/root1/something"))), + store.get::(Some((1, Path::new("/root1/something")))), &UserSettings { name: "John Doe".to_string(), age: 31, @@ -914,7 +959,7 @@ mod tests { } ); assert_eq!( - store.get::(Some(Path::new("/root1/subdir/something"))), + store.get::(Some((1, Path::new("/root1/subdir/something")))), &UserSettings { name: "Jane Doe".to_string(), age: 31, @@ -922,7 +967,7 @@ mod tests { } ); assert_eq!( - store.get::(Some(Path::new("/root2/something"))), + store.get::(Some((1, Path::new("/root2/something")))), &UserSettings { name: "John Doe".to_string(), age: 42, @@ -930,7 +975,7 @@ mod tests { } ); assert_eq!( - store.get::(Some(Path::new("/root2/something"))), + store.get::(Some((1, Path::new("/root2/something")))), &MultiKeySettings { key1: "a".to_string(), key2: "b".to_string(), diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index d34a261bfecf32bcdedbaaffa8dfa49e4dd0d003..ac3875af9e10704be06a7a2a047f86ec1fe992b6 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -22,7 +22,7 @@ const TERMINAL_PANEL_KEY: &'static str = "TerminalPanel"; actions!(terminal_panel, [ToggleFocus]); pub fn init(cx: &mut AppContext) { - cx.add_action(TerminalPanel::add_terminal); + cx.add_action(TerminalPanel::new_terminal); } pub enum Event { @@ -70,6 +70,7 @@ impl TerminalPanel { .with_child(Pane::render_tab_bar_button( 0, "icons/plus_12.svg", + false, Some(( "New Terminal".into(), Some(Box::new(workspace::NewTerminal)), @@ -80,7 +81,7 @@ impl TerminalPanel { cx.window_context().defer(move |cx| { if let Some(this) = this.upgrade(cx) { this.update(cx, |this, cx| { - this.add_terminal(&Default::default(), cx); + this.add_terminal(cx); }); } }) @@ -94,6 +95,7 @@ impl TerminalPanel { } else { "icons/maximize_8.svg" }, + pane.is_zoomed(), Some(("Toggle Zoom".into(), Some(Box::new(workspace::ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), @@ -220,7 +222,19 @@ impl TerminalPanel { } } - fn add_terminal(&mut self, _: &workspace::NewTerminal, cx: &mut ViewContext) { + fn new_terminal( + workspace: &mut Workspace, + _: &workspace::NewTerminal, + cx: &mut ViewContext, + ) { + let Some(this) = workspace.focus_panel::(cx) else { + return; + }; + + this.update(cx, |this, cx| this.add_terminal(cx)) + } + + fn add_terminal(&mut self, cx: &mut ViewContext) { let workspace = self.workspace.clone(); cx.spawn(|this, mut cx| async move { let pane = this.read_with(&cx, |this, _| this.pane.clone())?; @@ -363,7 +377,7 @@ impl Panel for TerminalPanel { fn set_active(&mut self, active: bool, cx: &mut ViewContext) { if active && self.pane.read(cx).items_len() == 0 { - self.add_terminal(&Default::default(), cx) + self.add_terminal(cx) } } diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 767e3bf4dbb3847064c7a32d217596840457c080..9478b3eef14221782050ca9ddf269878a91fef28 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -38,7 +38,7 @@ use workspace::{ notifications::NotifyResultExt, pane, register_deserializable_item, searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}, - Pane, ToolbarItemLocation, Workspace, WorkspaceId, + NewCenterTerminal, Pane, ToolbarItemLocation, Workspace, WorkspaceId, }; pub use terminal::TerminalSettings; @@ -66,10 +66,10 @@ pub fn init(cx: &mut AppContext) { terminal_panel::init(cx); terminal::init(cx); - cx.add_action(TerminalView::deploy); - register_deserializable_item::(cx); + cx.add_action(TerminalView::deploy); + //Useful terminal views cx.add_action(TerminalView::send_text); cx.add_action(TerminalView::send_keystroke); @@ -101,7 +101,7 @@ impl TerminalView { ///Create a new Terminal in the current working directory or the user's home directory pub fn deploy( workspace: &mut Workspace, - _: &workspace::NewTerminal, + _: &NewCenterTerminal, cx: &mut ViewContext, ) { let strategy = settings::get::(cx); @@ -133,8 +133,8 @@ impl TerminalView { Event::Wakeup => { if !cx.is_self_focused() { this.has_new_content = true; - cx.notify(); } + cx.notify(); cx.emit(Event::Wakeup); } Event::Bell => { @@ -905,7 +905,10 @@ mod tests { cx: &mut TestAppContext, ) -> (ModelHandle, ViewHandle) { let params = cx.update(AppState::test); - cx.update(|cx| theme::init((), cx)); + cx.update(|cx| { + theme::init((), cx); + language::init(cx); + }); let project = Project::test(params.fs.clone(), [], cx).await; let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1f99742cfe20cd4770e65457f2462c3f11829cd3..f1e752b763e07f3449099b85d45961d80bca7dd8 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -90,7 +90,8 @@ pub struct Workspace { pub breadcrumbs: Interactive, pub disconnected_overlay: ContainedText, pub modal: ContainerStyle, - pub zoomed_foreground: ContainerStyle, + pub zoomed_panel_foreground: ContainerStyle, + pub zoomed_pane_foreground: ContainerStyle, pub zoomed_background: ContainerStyle, pub notification: ContainerStyle, pub notifications: Notifications, diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index f998fc319fcb7bcdfc6be70c66b482ec247a93b0..e3397a1557cfeed520e4c56fa38c40e4539d71a9 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -15,6 +15,7 @@ lazy_static::lazy_static! { pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt"); pub static ref LOG: PathBuf = LOGS_DIR.join("Zed.log"); pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old"); + pub static ref LOCAL_SETTINGS_RELATIVE_PATH: &'static Path = Path::new(".zed/settings.json"); } pub mod legacy { diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index cef6f53a6ecf50b376b93b460fbba82c96eb51de..b7460c4c461f6a707bf77ee193145770418eeb50 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -32,7 +32,7 @@ pub fn init(cx: &mut AppContext) { pub fn show_welcome_experience(app_state: &Arc, cx: &mut AppContext) { open_new(&app_state, cx, |workspace, cx| { - workspace.toggle_dock(DockPosition::Left, false, cx); + workspace.toggle_dock(DockPosition::Left, cx); let welcome_page = cx.add_view(|cx| WelcomePage::new(workspace, cx)); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); cx.focus(&welcome_page); diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 49651486db3b12526d9e40cc92d01a1a13c3ca19..48f486381dd223340becb30eb1c594e5a1f55bb4 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -175,12 +175,16 @@ impl Dock { } } + pub fn position(&self) -> DockPosition { + self.position + } + pub fn is_open(&self) -> bool { self.is_open } pub fn has_focus(&self, cx: &WindowContext) -> bool { - self.active_panel() + self.visible_panel() .map_or(false, |panel| panel.has_focus(cx)) } @@ -207,7 +211,7 @@ impl Dock { self.active_panel_index } - pub fn set_open(&mut self, open: bool, cx: &mut ViewContext) { + pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext) { if open != self.is_open { self.is_open = open; if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) { @@ -218,11 +222,6 @@ impl Dock { } } - pub fn toggle_open(&mut self, cx: &mut ViewContext) { - self.set_open(!self.is_open, cx); - cx.notify(); - } - pub fn set_panel_zoomed( &mut self, panel: &AnyViewHandle, @@ -265,7 +264,7 @@ impl Dock { cx.focus(&panel); } } else if T::should_close_on_event(event) - && this.active_panel().map_or(false, |p| p.id() == panel.id()) + && this.visible_panel().map_or(false, |p| p.id() == panel.id()) { this.set_open(false, cx); } @@ -321,12 +320,16 @@ impl Dock { } } - pub fn active_panel(&self) -> Option<&Rc> { - let entry = self.active_entry()?; + pub fn visible_panel(&self) -> Option<&Rc> { + let entry = self.visible_entry()?; Some(&entry.panel) } - fn active_entry(&self) -> Option<&PanelEntry> { + pub fn active_panel(&self) -> Option<&Rc> { + Some(&self.panel_entries.get(self.active_panel_index)?.panel) + } + + fn visible_entry(&self) -> Option<&PanelEntry> { if self.is_open { self.panel_entries.get(self.active_panel_index) } else { @@ -335,7 +338,7 @@ impl Dock { } pub fn zoomed_panel(&self, cx: &WindowContext) -> Option> { - let entry = self.active_entry()?; + let entry = self.visible_entry()?; if entry.panel.is_zoomed(cx) { Some(entry.panel.clone()) } else { @@ -368,7 +371,7 @@ impl Dock { } pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { - if let Some(active_entry) = self.active_entry() { + if let Some(active_entry) = self.visible_entry() { Empty::new() .into_any() .contained() @@ -405,7 +408,7 @@ impl View for Dock { } fn render(&mut self, cx: &mut ViewContext) -> AnyElement { - if let Some(active_entry) = self.active_entry() { + if let Some(active_entry) = self.visible_entry() { let style = self.style(cx); ChildView::new(active_entry.panel.as_any(), cx) .contained() @@ -423,7 +426,7 @@ impl View for Dock { fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { if cx.is_self_focused() { - if let Some(active_entry) = self.active_entry() { + if let Some(active_entry) = self.visible_entry() { cx.focus(active_entry.panel.as_any()); } else { cx.focus_parent(); @@ -479,11 +482,22 @@ impl View for PanelButtons { Flex::row() .with_children(panels.into_iter().enumerate().map( |(panel_ix, (view, context_menu))| { - let (tooltip, tooltip_action) = view.icon_tooltip(cx); + let is_active = is_open && panel_ix == active_ix; + let (tooltip, tooltip_action) = if is_active { + ( + format!("Close {} dock", dock_position.to_label()), + Some(match dock_position { + DockPosition::Left => crate::ToggleLeftDock.boxed_clone(), + DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(), + DockPosition::Right => crate::ToggleRightDock.boxed_clone(), + }), + ) + } else { + view.icon_tooltip(cx) + }; Stack::new() .with_child( MouseEventHandler::::new(panel_ix, cx, |state, cx| { - let is_active = is_open && panel_ix == active_ix; let style = button_style.style_for(state, is_active); Flex::row() .with_child( @@ -510,13 +524,22 @@ impl View for PanelButtons { }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, { + let tooltip_action = + tooltip_action.as_ref().map(|action| action.boxed_clone()); move |_, this, cx| { - if let Some(workspace) = this.workspace.upgrade(cx) { - cx.window_context().defer(move |cx| { - workspace.update(cx, |workspace, cx| { - workspace.toggle_panel(dock_position, panel_ix, cx) - }); - }); + if let Some(tooltip_action) = &tooltip_action { + let window_id = cx.window_id(); + let view_id = this.workspace.id(); + let tooltip_action = tooltip_action.boxed_clone(); + cx.spawn(|_, mut cx| async move { + cx.dispatch_action( + window_id, + view_id, + &*tooltip_action, + ) + .ok(); + }) + .detach(); } } }) diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 21b3be09d06ea6781e70dd12fa91a1c2ace2e152..1e3c6044a1651101939453d2ee2e5c6b90df3564 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -1,5 +1,5 @@ use crate::{Toast, Workspace}; -use collections::HashSet; +use collections::HashMap; use gpui::{AnyViewHandle, AppContext, Entity, View, ViewContext, ViewHandle}; use std::{any::TypeId, ops::DerefMut}; @@ -33,12 +33,12 @@ impl From<&dyn NotificationHandle> for AnyViewHandle { } } -struct NotificationTracker { - notifications_sent: HashSet, +pub(crate) struct NotificationTracker { + notifications_sent: HashMap>, } impl std::ops::Deref for NotificationTracker { - type Target = HashSet; + type Target = HashMap>; fn deref(&self) -> &Self::Target { &self.notifications_sent @@ -54,24 +54,33 @@ impl DerefMut for NotificationTracker { impl NotificationTracker { fn new() -> Self { Self { - notifications_sent: HashSet::default(), + notifications_sent: Default::default(), } } } impl Workspace { + pub fn has_shown_notification_once( + &self, + id: usize, + cx: &ViewContext, + ) -> bool { + cx.global::() + .get(&TypeId::of::()) + .map(|ids| ids.contains(&id)) + .unwrap_or(false) + } + pub fn show_notification_once( &mut self, id: usize, cx: &mut ViewContext, build_notification: impl FnOnce(&mut ViewContext) -> ViewHandle, ) { - if !cx - .global::() - .contains(&TypeId::of::()) - { + if !self.has_shown_notification_once::(id, cx) { cx.update_global::(|tracker, _| { - tracker.insert(TypeId::of::()) + let entry = tracker.entry(TypeId::of::()).or_default(); + entry.push(id); }); self.show_notification::(id, cx, build_notification) @@ -154,9 +163,10 @@ pub mod simple_message_notification { use gpui::{ actions, elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text}, + fonts::TextStyle, impl_actions, platform::{CursorStyle, MouseButton}, - AppContext, Element, Entity, View, ViewContext, + AnyElement, AppContext, Element, Entity, View, ViewContext, }; use menu::Cancel; use serde::Deserialize; @@ -184,8 +194,13 @@ pub mod simple_message_notification { ) } + enum NotificationMessage { + Text(Cow<'static, str>), + Element(fn(TextStyle, &AppContext) -> AnyElement), + } + pub struct MessageNotification { - message: Cow<'static, str>, + message: NotificationMessage, on_click: Option)>>, click_message: Option>, } @@ -204,7 +219,17 @@ pub mod simple_message_notification { S: Into>, { Self { - message: message.into(), + message: NotificationMessage::Text(message.into()), + on_click: None, + click_message: None, + } + } + + pub fn new_element( + message: fn(TextStyle, &AppContext) -> AnyElement, + ) -> MessageNotification { + Self { + message: NotificationMessage::Element(message), on_click: None, click_message: None, } @@ -243,84 +268,90 @@ pub mod simple_message_notification { enum MessageNotificationTag {} let click_message = self.click_message.clone(); - let message = self.message.clone(); + let message = match &self.message { + NotificationMessage::Text(text) => { + Text::new(text.to_owned(), theme.message.text.clone()).into_any() + } + NotificationMessage::Element(e) => e(theme.message.text.clone(), cx), + }; let on_click = self.on_click.clone(); let has_click_action = on_click.is_some(); - MouseEventHandler::::new(0, cx, |state, cx| { - Flex::column() - .with_child( - Flex::row() - .with_child( - Text::new(message, theme.message.text.clone()) - .contained() - .with_style(theme.message.container) - .aligned() - .top() - .left() - .flex(1., true), - ) - .with_child( - MouseEventHandler::::new(0, cx, |state, _| { - let style = theme.dismiss_button.style_for(state, false); - Svg::new("icons/x_mark_8.svg") - .with_color(style.color) - .constrained() - .with_width(style.icon_width) - .aligned() - .contained() - .with_style(style.container) - .constrained() - .with_width(style.button_width) - .with_height(style.button_width) - }) - .with_padding(Padding::uniform(5.)) - .on_click(MouseButton::Left, move |_, this, cx| { - this.dismiss(&Default::default(), cx); - }) - .with_cursor_style(CursorStyle::PointingHand) - .aligned() - .constrained() - .with_height( - cx.font_cache().line_height(theme.message.text.font_size), - ) + Flex::column() + .with_child( + Flex::row() + .with_child( + message + .contained() + .with_style(theme.message.container) .aligned() .top() - .flex_float(), - ), - ) - .with_children({ - let style = theme.action_message.style_for(state, false); - if let Some(click_message) = click_message { - Some( - Flex::row().with_child( - Text::new(click_message, style.text.clone()) + .left() + .flex(1., true), + ) + .with_child( + MouseEventHandler::::new(0, cx, |state, _| { + let style = theme.dismiss_button.style_for(state, false); + Svg::new("icons/x_mark_8.svg") + .with_color(style.color) + .constrained() + .with_width(style.icon_width) + .aligned() + .contained() + .with_style(style.container) + .constrained() + .with_width(style.button_width) + .with_height(style.button_width) + }) + .with_padding(Padding::uniform(5.)) + .on_click(MouseButton::Left, move |_, this, cx| { + this.dismiss(&Default::default(), cx); + }) + .with_cursor_style(CursorStyle::PointingHand) + .aligned() + .constrained() + .with_height(cx.font_cache().line_height(theme.message.text.font_size)) + .aligned() + .top() + .flex_float(), + ), + ) + .with_children({ + click_message + .map(|click_message| { + MouseEventHandler::::new( + 0, + cx, + |state, _| { + let style = theme.action_message.style_for(state, false); + + Flex::row() + .with_child( + Text::new(click_message, style.text.clone()) + .contained() + .with_style(style.container), + ) .contained() - .with_style(style.container), - ), + }, ) - } else { - None - } + .on_click(MouseButton::Left, move |_, this, cx| { + if let Some(on_click) = on_click.as_ref() { + on_click(cx); + this.dismiss(&Default::default(), cx); + } + }) + // Since we're not using a proper overlay, we have to capture these extra events + .on_down(MouseButton::Left, |_, _, _| {}) + .on_up(MouseButton::Left, |_, _, _| {}) + .with_cursor_style(if has_click_action { + CursorStyle::PointingHand + } else { + CursorStyle::Arrow + }) + }) .into_iter() - }) - .contained() - }) - // Since we're not using a proper overlay, we have to capture these extra events - .on_down(MouseButton::Left, |_, _, _| {}) - .on_up(MouseButton::Left, |_, _, _| {}) - .on_click(MouseButton::Left, move |_, this, cx| { - if let Some(on_click) = on_click.as_ref() { - on_click(cx); - this.dismiss(&Default::default(), cx); - } - }) - .with_cursor_style(if has_click_action { - CursorStyle::PointingHand - } else { - CursorStyle::Arrow - }) - .into_any() + }) + .into_any() } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2919bb7cd447d1fe50e2afe972c31217d23e9e00..1fa8c15f9bc1f5399fbcda51cb68d2b594543c46 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -2,8 +2,8 @@ mod dragged_item_receiver; use super::{ItemHandle, SplitDirection}; use crate::{ - item::WeakItemHandle, toolbar::Toolbar, AutosaveSetting, Item, NewFile, NewSearch, NewTerminal, - ToggleZoom, Workspace, WorkspaceSettings, + item::WeakItemHandle, notify_of_new_dock, toolbar::Toolbar, AutosaveSetting, Item, + NewCenterTerminal, NewFile, NewSearch, ToggleZoom, Workspace, WorkspaceSettings, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; @@ -131,7 +131,6 @@ pub enum Event { pub struct Pane { items: Vec>, activation_history: Vec, - is_active: bool, zoomed: bool, active_item_index: usize, last_focused_view_by_item: HashMap, @@ -238,7 +237,6 @@ impl Pane { Self { items: Vec::new(), activation_history: Vec::new(), - is_active: true, zoomed: false, active_item_index: 0, last_focused_view_by_item: Default::default(), @@ -270,6 +268,7 @@ impl Pane { .with_child(Self::render_tab_bar_button( 0, "icons/plus_12.svg", + false, Some(("New...".into(), None)), cx, |pane, cx| pane.deploy_new_menu(cx), @@ -279,6 +278,7 @@ impl Pane { .with_child(Self::render_tab_bar_button( 1, "icons/split_12.svg", + false, Some(("Split Pane".into(), None)), cx, |pane, cx| pane.deploy_split_menu(cx), @@ -292,6 +292,7 @@ impl Pane { } else { "icons/maximize_8.svg" }, + pane.is_zoomed(), Some(("Toggle Zoom".into(), Some(Box::new(ToggleZoom)))), cx, move |pane, cx| pane.toggle_zoom(&Default::default(), cx), @@ -306,15 +307,6 @@ impl Pane { &self.workspace } - pub fn is_active(&self) -> bool { - self.is_active - } - - pub fn set_active(&mut self, is_active: bool, cx: &mut ViewContext) { - self.is_active = is_active; - cx.notify(); - } - pub fn has_focus(&self) -> bool { self.has_focus } @@ -547,6 +539,11 @@ impl Pane { } pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext) { + // Potentially warn the user of the new keybinding + let workspace_handle = self.workspace().clone(); + cx.spawn(|_, mut cx| async move { notify_of_new_dock(&workspace_handle, &mut cx) }) + .detach(); + if self.zoomed { cx.emit(Event::ZoomOut); } else if !self.items.is_empty() { @@ -1005,7 +1002,7 @@ impl Pane { AnchorCorner::TopRight, vec![ ContextMenuItem::action("New File", NewFile), - ContextMenuItem::action("New Terminal", NewTerminal), + ContextMenuItem::action("New Terminal", NewCenterTerminal), ContextMenuItem::action("New Search", NewSearch), ], cx, @@ -1129,7 +1126,7 @@ impl Pane { None }; - let pane_active = self.is_active; + let pane_active = self.has_focus; enum Tabs {} let mut row = Flex::row().scrollable::(1, autoscroll, cx); @@ -1412,6 +1409,7 @@ impl Pane { pub fn render_tab_bar_button)>( index: usize, icon: &'static str, + active: bool, tooltip: Option<(String, Option>)>, cx: &mut ViewContext, on_click: F, @@ -1421,7 +1419,7 @@ impl Pane { let mut button = MouseEventHandler::::new(index, cx, |mouse_state, cx| { let theme = &settings::get::(cx).theme.workspace.tab_bar; - let style = theme.pane_button.style_for(mouse_state, false); + let style = theme.pane_button.style_for(mouse_state, active); Svg::new(icon) .with_color(style.color) .constrained() @@ -1508,7 +1506,7 @@ impl View for Pane { let mut tab_row = Flex::row() .with_child(self.render_tabs(cx).flex(1., true).into_any_named("tabs")); - if self.is_active { + if self.has_focus { let render_tab_bar_buttons = self.render_tab_bar_buttons.clone(); tab_row.add_child( (render_tab_bar_buttons)(self, cx) @@ -1599,6 +1597,7 @@ impl View for Pane { if !self.has_focus { self.has_focus = true; cx.emit(Event::Focus); + cx.notify(); } self.toolbar.update(cx, |toolbar, cx| { @@ -1633,6 +1632,7 @@ impl View for Pane { self.toolbar.update(cx, |toolbar, cx| { toolbar.pane_focus_update(false, cx); }); + cx.notify(); } fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 2aa937c9a932ddbc2e2cb9364607693cb2ab5621..9cc175ab4c5f910d404606625b2559d0e9da8584 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -53,13 +53,14 @@ use std::{ cmp, env, future::Future, path::{Path, PathBuf}, + rc::Rc, str, sync::{atomic::AtomicUsize, Arc}, time::Duration, }; use crate::{ - notifications::simple_message_notification::MessageNotification, + notifications::{simple_message_notification::MessageNotification, NotificationTracker}, persistence::model::{ DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace, }, @@ -80,7 +81,7 @@ use serde::Deserialize; use shared_screen::SharedScreen; use status_bar::StatusBar; pub use status_bar::StatusItemView; -use theme::Theme; +use theme::{Theme, ThemeSettings}; pub use toolbar::{ToolbarItemLocation, ToolbarItemView}; use util::{async_iife, paths, ResultExt}; pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings}; @@ -103,24 +104,6 @@ pub trait Modal: View { #[derive(Clone, PartialEq)] pub struct RemoveWorktreeFromProject(pub WorktreeId); -#[derive(Copy, Clone, Default, Deserialize, PartialEq)] -pub struct ToggleLeftDock { - #[serde(default = "default_true")] - pub focus: bool, -} - -#[derive(Copy, Clone, Default, Deserialize, PartialEq)] -pub struct ToggleBottomDock { - #[serde(default = "default_true")] - pub focus: bool, -} - -#[derive(Copy, Clone, Default, Deserialize, PartialEq)] -pub struct ToggleRightDock { - #[serde(default = "default_true")] - pub focus: bool, -} - actions!( workspace, [ @@ -137,22 +120,21 @@ actions!( ActivateNextPane, FollowNextCollaborator, NewTerminal, + NewCenterTerminal, ToggleTerminalFocus, NewSearch, Feedback, Restart, Welcome, ToggleZoom, + ToggleLeftDock, + ToggleRightDock, + ToggleBottomDock, ] ); actions!(zed, [OpenSettings]); -impl_actions!( - workspace, - [ToggleLeftDock, ToggleBottomDock, ToggleRightDock] -); - #[derive(Clone, PartialEq)] pub struct OpenPaths { pub paths: Vec, @@ -268,14 +250,14 @@ pub fn init(app_state: Arc, cx: &mut AppContext) { cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| { workspace.activate_next_pane(cx) }); - cx.add_action(|workspace: &mut Workspace, action: &ToggleLeftDock, cx| { - workspace.toggle_dock(DockPosition::Left, action.focus, cx); + cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| { + workspace.toggle_dock(DockPosition::Left, cx); }); - cx.add_action(|workspace: &mut Workspace, action: &ToggleRightDock, cx| { - workspace.toggle_dock(DockPosition::Right, action.focus, cx); + cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| { + workspace.toggle_dock(DockPosition::Right, cx); }); - cx.add_action(|workspace: &mut Workspace, action: &ToggleBottomDock, cx| { - workspace.toggle_dock(DockPosition::Bottom, action.focus, cx); + cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| { + workspace.toggle_dock(DockPosition::Bottom, cx); }); cx.add_action(Workspace::activate_pane_at_index); cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { @@ -498,6 +480,7 @@ pub struct Workspace { remote_entity_subscription: Option, modal: Option, zoomed: Option, + zoomed_position: Option, center: PaneGroup, left_dock: ViewHandle, bottom_dock: ViewHandle, @@ -703,6 +686,7 @@ impl Workspace { weak_self: weak_handle.clone(), modal: None, zoomed: None, + zoomed_position: None, center: PaneGroup::new(center_pane.clone()), panes: vec![center_pane.clone()], panes_by_item: Default::default(), @@ -901,10 +885,15 @@ impl Workspace { was_visible = dock.is_open() && dock - .active_panel() + .visible_panel() .map_or(false, |active_panel| active_panel.id() == panel.id()); dock.remove_panel(&panel, cx); }); + + if panel.is_zoomed(cx) { + this.zoomed_position = Some(new_position); + } + dock = match panel.read(cx).position(cx) { DockPosition::Left => &this.left_dock, DockPosition::Bottom => &this.bottom_dock, @@ -919,18 +908,27 @@ impl Workspace { } }); } else if T::should_zoom_in_on_event(event) { - this.zoom_out(cx); dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); if panel.has_focus(cx) { this.zoomed = Some(panel.downgrade().into_any()); + this.zoomed_position = Some(panel.read(cx).position(cx)); } } else if T::should_zoom_out_on_event(event) { - this.zoom_out(cx); + dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, false, cx)); + if this.zoomed_position == Some(prev_position) { + this.zoomed = None; + this.zoomed_position = None; + } + cx.notify(); } else if T::is_focus_event(event) { + let position = panel.read(cx).position(cx); + this.dismiss_zoomed_items_to_reveal(Some(position), cx); if panel.is_zoomed(cx) { this.zoomed = Some(panel.downgrade().into_any()); + this.zoomed_position = Some(position); } else { this.zoomed = None; + this.zoomed_position = None; } cx.notify(); } @@ -976,9 +974,8 @@ impl Workspace { let timestamp = entry.timestamp; match history.entry(project_path) { hash_map::Entry::Occupied(mut entry) => { - let (old_fs_path, old_timestamp) = entry.get(); + let (_, old_timestamp) = entry.get(); if ×tamp > old_timestamp { - assert_eq!(&fs_path, old_fs_path, "Inconsistent nav history"); entry.insert((fs_path, timestamp)); } } @@ -1593,89 +1590,98 @@ impl Workspace { } } - pub fn toggle_dock( - &mut self, - dock_side: DockPosition, - focus: bool, - cx: &mut ViewContext, - ) { + pub fn toggle_dock(&mut self, dock_side: DockPosition, cx: &mut ViewContext) { let dock = match dock_side { DockPosition::Left => &self.left_dock, DockPosition::Bottom => &self.bottom_dock, DockPosition::Right => &self.right_dock, }; + let mut focus_center = false; + let mut reveal_dock = false; dock.update(cx, |dock, cx| { - let open = !dock.is_open(); - dock.set_open(open, cx); + let other_is_zoomed = self.zoomed.is_some() && self.zoomed_position != Some(dock_side); + let was_visible = dock.is_open() && !other_is_zoomed; + dock.set_open(!was_visible, cx); + + if let Some(active_panel) = dock.active_panel() { + if was_visible { + if active_panel.has_focus(cx) { + focus_center = true; + } + } else { + if active_panel.is_zoomed(cx) { + cx.focus(active_panel.as_any()); + } + reveal_dock = true; + } + } }); - if dock.read(cx).is_open() && focus { - cx.focus(dock); - } else { - cx.focus_self(); + if reveal_dock { + self.dismiss_zoomed_items_to_reveal(Some(dock_side), cx); } - cx.notify(); - self.serialize_workspace(cx); - } - - pub fn toggle_panel( - &mut self, - position: DockPosition, - panel_index: usize, - cx: &mut ViewContext, - ) { - let dock = match position { - DockPosition::Left => &mut self.left_dock, - DockPosition::Bottom => &mut self.bottom_dock, - DockPosition::Right => &mut self.right_dock, - }; - let active_item = dock.update(cx, move |dock, cx| { - if dock.is_open() && dock.active_panel_index() == panel_index { - dock.set_open(false, cx); - None - } else { - dock.set_open(true, cx); - dock.activate_panel(panel_index, cx); - dock.active_panel().cloned() - } - }); - if let Some(active_item) = active_item { - if active_item.has_focus(cx) { - cx.focus_self(); - } else { - cx.focus(active_item.as_any()); - } - } else { + if focus_center { cx.focus_self(); } + cx.notify(); self.serialize_workspace(cx); + } - cx.notify(); + /// Transfer focus to the panel of the given type. + pub fn focus_panel(&mut self, cx: &mut ViewContext) -> Option> { + self.focus_or_unfocus_panel::(cx, |_, _| true)? + .as_any() + .clone() + .downcast() } + /// Focus the panel of the given type if it isn't already focused. If it is + /// already focused, then transfer focus back to the workspace center. pub fn toggle_panel_focus(&mut self, cx: &mut ViewContext) { + self.focus_or_unfocus_panel::(cx, |panel, cx| !panel.has_focus(cx)); + } + + /// Focus or unfocus the given panel type, depending on the given callback. + fn focus_or_unfocus_panel( + &mut self, + cx: &mut ViewContext, + should_focus: impl Fn(&dyn PanelHandle, &mut ViewContext) -> bool, + ) -> Option> { for dock in [&self.left_dock, &self.bottom_dock, &self.right_dock] { if let Some(panel_index) = dock.read(cx).panel_index_for_type::() { - let active_item = dock.update(cx, |dock, cx| { - dock.set_open(true, cx); + let mut focus_center = false; + let mut reveal_dock = false; + let panel = dock.update(cx, |dock, cx| { dock.activate_panel(panel_index, cx); - dock.active_panel().cloned() - }); - if let Some(active_item) = active_item { - if active_item.has_focus(cx) { - cx.focus_self(); - } else { - cx.focus(active_item.as_any()); + + let panel = dock.active_panel().cloned(); + if let Some(panel) = panel.as_ref() { + if should_focus(&**panel, cx) { + dock.set_open(true, cx); + cx.focus(panel.as_any()); + reveal_dock = true; + } else { + // if panel.is_zoomed(cx) { + // dock.set_open(false, cx); + // } + focus_center = true; + } } + panel + }); + + if focus_center { + cx.focus_self(); } self.serialize_workspace(cx); cx.notify(); - break; + return panel; } } + None } pub fn panel(&self, cx: &WindowContext) -> Option> { @@ -1697,6 +1703,46 @@ impl Workspace { self.bottom_dock.update(cx, |dock, cx| dock.zoom_out(cx)); self.right_dock.update(cx, |dock, cx| dock.zoom_out(cx)); self.zoomed = None; + self.zoomed_position = None; + + cx.notify(); + } + + fn dismiss_zoomed_items_to_reveal( + &mut self, + dock_to_reveal: Option, + cx: &mut ViewContext, + ) { + // If a center pane is zoomed, unzoom it. + for pane in &self.panes { + if pane != &self.active_pane || dock_to_reveal.is_some() { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + } + } + + // If another dock is zoomed, hide it. + let mut focus_center = false; + for dock in [&self.left_dock, &self.right_dock, &self.bottom_dock] { + dock.update(cx, |dock, cx| { + if Some(dock.position()) != dock_to_reveal { + if let Some(panel) = dock.active_panel() { + if panel.is_zoomed(cx) { + focus_center |= panel.has_focus(cx); + dock.set_open(false, cx); + } + } + } + }); + } + + if focus_center { + cx.focus_self(); + } + + if self.zoomed_position != dock_to_reveal { + self.zoomed = None; + self.zoomed_position = None; + } cx.notify(); } @@ -1896,11 +1942,7 @@ impl Workspace { fn handle_pane_focused(&mut self, pane: ViewHandle, cx: &mut ViewContext) { if self.active_pane != pane { - self.active_pane - .update(cx, |pane, cx| pane.set_active(false, cx)); self.active_pane = pane.clone(); - self.active_pane - .update(cx, |pane, cx| pane.set_active(true, cx)); self.status_bar.update(cx, |status_bar, cx| { status_bar.set_active_pane(&self.active_pane, cx); }); @@ -1908,11 +1950,13 @@ impl Workspace { self.last_active_center_pane = Some(pane.downgrade()); } + self.dismiss_zoomed_items_to_reveal(None, cx); if pane.read(cx).is_zoomed() { self.zoomed = Some(pane.downgrade().into_any()); } else { self.zoomed = None; } + self.zoomed_position = None; self.update_followers( proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView { @@ -1968,15 +2012,21 @@ impl Workspace { } pane::Event::ZoomIn => { if pane == self.active_pane { - self.zoom_out(cx); pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); if pane.read(cx).has_focus() { self.zoomed = Some(pane.downgrade().into_any()); + self.zoomed_position = None; } cx.notify(); } } - pane::Event::ZoomOut => self.zoom_out(cx), + pane::Event::ZoomOut => { + pane.update(cx, |pane, cx| pane.set_zoomed(false, cx)); + if self.zoomed_position.is_none() { + self.zoomed = None; + } + cx.notify(); + } } self.serialize_workspace(cx); @@ -2817,7 +2867,7 @@ impl Workspace { }) }) .collect::>(), - pane.is_active(), + pane.has_focus(), ) }; @@ -2845,7 +2895,7 @@ impl Workspace { fn build_serialized_docks(this: &Workspace, cx: &AppContext) -> DockStructure { let left_dock = this.left_dock.read(cx); let left_visible = left_dock.is_open(); - let left_active_panel = left_dock.active_panel().and_then(|panel| { + let left_active_panel = left_dock.visible_panel().and_then(|panel| { Some( cx.view_ui_name(panel.as_any().window_id(), panel.id())? .to_string(), @@ -2854,7 +2904,7 @@ impl Workspace { let right_dock = this.right_dock.read(cx); let right_visible = right_dock.is_open(); - let right_active_panel = right_dock.active_panel().and_then(|panel| { + let right_active_panel = right_dock.visible_panel().and_then(|panel| { Some( cx.view_ui_name(panel.as_any().window_id(), panel.id())? .to_string(), @@ -2863,7 +2913,7 @@ impl Workspace { let bottom_dock = this.bottom_dock.read(cx); let bottom_visible = bottom_dock.is_open(); - let bottom_active_panel = bottom_dock.active_panel().and_then(|panel| { + let bottom_active_panel = bottom_dock.visible_panel().and_then(|panel| { Some( cx.view_ui_name(panel.as_any().window_id(), panel.id())? .to_string(), @@ -3045,7 +3095,7 @@ impl Workspace { DockPosition::Right => &self.right_dock, DockPosition::Bottom => &self.bottom_dock, }; - let active_panel = dock.read(cx).active_panel()?; + let active_panel = dock.read(cx).visible_panel()?; let element = if Some(active_panel.id()) == self.zoomed.as_ref().map(|zoomed| zoomed.id()) { dock.read(cx).render_placeholder(cx) } else { @@ -3159,6 +3209,87 @@ async fn open_items( opened_items } +fn notify_of_new_dock(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { + const NEW_PANEL_BLOG_POST: &str = "https://zed.dev/blog/new-panel-system"; + const NEW_DOCK_HINT_KEY: &str = "show_new_dock_key"; + const MESSAGE_ID: usize = 2; + + if workspace + .read_with(cx, |workspace, cx| { + workspace.has_shown_notification_once::(MESSAGE_ID, cx) + }) + .unwrap_or(false) + { + return; + } + + if db::kvp::KEY_VALUE_STORE + .read_kvp(NEW_DOCK_HINT_KEY) + .ok() + .flatten() + .is_some() + { + if !workspace + .read_with(cx, |workspace, cx| { + workspace.has_shown_notification_once::(MESSAGE_ID, cx) + }) + .unwrap_or(false) + { + cx.update(|cx| { + cx.update_global::(|tracker, _| { + let entry = tracker + .entry(TypeId::of::()) + .or_default(); + if !entry.contains(&MESSAGE_ID) { + entry.push(MESSAGE_ID); + } + }); + }); + } + + return; + } + + cx.spawn(|_| async move { + db::kvp::KEY_VALUE_STORE + .write_kvp(NEW_DOCK_HINT_KEY.to_string(), "seen".to_string()) + .await + .ok(); + }) + .detach(); + + workspace + .update(cx, |workspace, cx| { + workspace.show_notification_once(2, cx, |cx| { + cx.add_view(|_| { + MessageNotification::new_element(|text, _| { + Text::new( + "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.", + text, + ) + .with_custom_runs(vec![26..32, 34..46], |_, bounds, scene, cx| { + let code_span_background_color = settings::get::(cx) + .theme + .editor + .document_highlight_read_background; + + scene.push_quad(gpui::Quad { + bounds, + background: Some(code_span_background_color), + border: Default::default(), + corner_radius: 2.0, + }) + }) + .into_any() + }) + .with_click_message("Read more about the new panel system") + .on_click(|cx| cx.platform().open_url(NEW_PANEL_BLOG_POST)) + }) + }) + }) + .ok(); +} + fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { const REPORT_ISSUE_URL: &str ="https://github.com/zed-industries/community/issues/new?assignees=&labels=defect%2Ctriage&template=2_bug_report.yml"; @@ -3175,7 +3306,7 @@ fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut Asy } else { let backup_path = (*db::BACKUP_DB_PATH).read(); if let Some(backup_path) = backup_path.clone() { - workspace.show_notification_once(0, cx, move |cx| { + workspace.show_notification_once(1, cx, move |cx| { cx.add_view(move |_| { MessageNotification::new(format!( "Database file was corrupted. Old database backed up to {}", @@ -3246,10 +3377,44 @@ impl View for Workspace { .with_children(self.zoomed.as_ref().and_then(|zoomed| { enum ZoomBackground {} let zoomed = zoomed.upgrade(cx)?; + + let mut foreground_style = + theme.workspace.zoomed_pane_foreground; + if let Some(zoomed_dock_position) = self.zoomed_position { + foreground_style = + theme.workspace.zoomed_panel_foreground; + let margin = foreground_style.margin.top; + let border = foreground_style.border.top; + + // Only include a margin and border on the opposite side. + foreground_style.margin.top = 0.; + foreground_style.margin.left = 0.; + foreground_style.margin.bottom = 0.; + foreground_style.margin.right = 0.; + foreground_style.border.top = false; + foreground_style.border.left = false; + foreground_style.border.bottom = false; + foreground_style.border.right = false; + match zoomed_dock_position { + DockPosition::Left => { + foreground_style.margin.right = margin; + foreground_style.border.right = border; + } + DockPosition::Right => { + foreground_style.margin.left = margin; + foreground_style.border.left = border; + } + DockPosition::Bottom => { + foreground_style.margin.top = margin; + foreground_style.border.top = border; + } + } + } + Some( ChildView::new(&zoomed, cx) .contained() - .with_style(theme.workspace.zoomed_foreground) + .with_style(foreground_style) .aligned() .contained() .with_style(theme.workspace.zoomed_background) @@ -3599,10 +3764,6 @@ fn parse_pixel_position_env_var(value: &str) -> Option { Some(vec2f(width as f32, height as f32)) } -fn default_true() -> bool { - true -} - #[cfg(test)] mod tests { use super::*; @@ -4181,6 +4342,153 @@ mod tests { }); } + #[gpui::test] + async fn test_toggle_docks_and_panels(cx: &mut gpui::TestAppContext) { + init_test(cx); + let fs = FakeFs::new(cx.background()); + + let project = Project::test(fs, [], cx).await; + let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project, cx)); + + let panel = workspace.update(cx, |workspace, cx| { + let panel = cx.add_view(|_| TestPanel::new(DockPosition::Right)); + workspace.add_panel(panel.clone(), cx); + + workspace + .right_dock() + .update(cx, |right_dock, cx| right_dock.set_open(true, cx)); + + panel + }); + + let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); + pane.update(cx, |pane, cx| { + let item = cx.add_view(|_| TestItem::new()); + pane.add_item(Box::new(item), true, true, None, cx); + }); + + // Transfer focus from center to panel + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(panel.has_focus(cx)); + }); + + // Transfer focus from panel to center + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(!panel.has_focus(cx)); + }); + + // Close the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx); + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(!panel.has_focus(cx)); + }); + + // Open the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx); + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(!panel.is_zoomed(cx)); + assert!(!panel.has_focus(cx)); + }); + + // Focus and zoom panel + panel.update(cx, |panel, cx| { + cx.focus_self(); + panel.set_zoomed(true, cx) + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(panel.has_focus(cx)); + }); + + // Transfer focus to the center closes the dock + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(!panel.has_focus(cx)); + }); + + // Transfering focus back to the panel keeps it zoomed + workspace.update(cx, |workspace, cx| { + workspace.toggle_panel_focus::(cx); + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(panel.has_focus(cx)); + }); + + // Close the dock while it is zoomed + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(!workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(workspace.zoomed.is_none()); + assert!(!panel.has_focus(cx)); + }); + + // Opening the dock, when it's zoomed, retains focus + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + + workspace.read_with(cx, |workspace, cx| { + assert!(workspace.right_dock().read(cx).is_open()); + assert!(panel.is_zoomed(cx)); + assert!(workspace.zoomed.is_some()); + assert!(panel.has_focus(cx)); + }); + + // Unzoom and close the panel, zoom the active pane. + panel.update(cx, |panel, cx| panel.set_zoomed(false, cx)); + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + pane.update(cx, |pane, cx| pane.toggle_zoom(&Default::default(), cx)); + + // Opening a dock unzooms the pane. + workspace.update(cx, |workspace, cx| { + workspace.toggle_dock(DockPosition::Right, cx) + }); + workspace.read_with(cx, |workspace, cx| { + let pane = pane.read(cx); + assert!(!pane.is_zoomed()); + assert!(pane.has_focus()); + assert!(workspace.right_dock().read(cx).is_open()); + assert!(workspace.zoomed.is_none()); + }); + } + #[gpui::test] async fn test_panels(cx: &mut gpui::TestAppContext) { init_test(cx); @@ -4204,7 +4512,7 @@ mod tests { let left_dock = workspace.left_dock(); assert_eq!( - left_dock.read(cx).active_panel().unwrap().id(), + left_dock.read(cx).visible_panel().unwrap().id(), panel_1.id() ); assert_eq!( @@ -4214,7 +4522,12 @@ mod tests { left_dock.update(cx, |left_dock, cx| left_dock.resize_active_panel(1337., cx)); assert_eq!( - workspace.right_dock().read(cx).active_panel().unwrap().id(), + workspace + .right_dock() + .read(cx) + .visible_panel() + .unwrap() + .id(), panel_2.id() ); @@ -4230,10 +4543,10 @@ mod tests { // Since panel_1 was visible on the left, it should now be visible now that it's been moved to the right. // Since it was the only panel on the left, the left dock should now be closed. assert!(!workspace.left_dock().read(cx).is_open()); - assert!(workspace.left_dock().read(cx).active_panel().is_none()); + assert!(workspace.left_dock().read(cx).visible_panel().is_none()); let right_dock = workspace.right_dock(); assert_eq!( - right_dock.read(cx).active_panel().unwrap().id(), + right_dock.read(cx).visible_panel().unwrap().id(), panel_1.id() ); assert_eq!(right_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); @@ -4248,7 +4561,12 @@ mod tests { // And the right dock is unaffected in it's displaying of panel_1 assert!(workspace.right_dock().read(cx).is_open()); assert_eq!( - workspace.right_dock().read(cx).active_panel().unwrap().id(), + workspace + .right_dock() + .read(cx) + .visible_panel() + .unwrap() + .id(), panel_1.id() ); }); @@ -4263,7 +4581,7 @@ mod tests { let left_dock = workspace.left_dock(); assert!(left_dock.read(cx).is_open()); assert_eq!( - left_dock.read(cx).active_panel().unwrap().id(), + left_dock.read(cx).visible_panel().unwrap().id(), panel_1.id() ); assert_eq!(left_dock.read(cx).active_panel_size(cx).unwrap(), 1337.); @@ -4297,7 +4615,7 @@ mod tests { let left_dock = workspace.left_dock(); assert!(left_dock.read(cx).is_open()); assert_eq!( - left_dock.read(cx).active_panel().unwrap().id(), + left_dock.read(cx).visible_panel().unwrap().id(), panel_1.id() ); assert!(panel_1.is_focused(cx)); @@ -4311,7 +4629,7 @@ mod tests { let left_dock = workspace.left_dock(); assert!(left_dock.read(cx).is_open()); assert_eq!( - left_dock.read(cx).active_panel().unwrap().id(), + left_dock.read(cx).visible_panel().unwrap().id(), panel_1.id() ); }); @@ -4320,6 +4638,14 @@ mod tests { panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomIn)); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Left)); + }); + + // Move panel to another dock while it is zoomed + panel_1.update(cx, |panel, cx| panel.set_position(DockPosition::Right, cx)); + workspace.read_with(cx, |workspace, _| { + assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); }); // If focus is transferred to another view that's not a panel or another pane, we still show @@ -4328,12 +4654,14 @@ mod tests { focus_receiver.update(cx, |_, cx| cx.focus_self()); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); }); // If focus is transferred elsewhere in the workspace, the panel is no longer zoomed. workspace.update(cx, |_, cx| cx.focus_self()); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); }); // If focus is transferred again to another view that's not a panel or a pane, we won't @@ -4341,18 +4669,21 @@ mod tests { focus_receiver.update(cx, |_, cx| cx.focus_self()); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); }); // When focus is transferred back to the panel, it is zoomed again. panel_1.update(cx, |_, cx| cx.focus_self()); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.zoomed, Some(panel_1.downgrade().into_any())); + assert_eq!(workspace.zoomed_position, Some(DockPosition::Right)); }); // Emitting a ZoomOut event unzooms the panel. panel_1.update(cx, |_, cx| cx.emit(TestPanelEvent::ZoomOut)); workspace.read_with(cx, |workspace, _| { assert_eq!(workspace.zoomed, None); + assert_eq!(workspace.zoomed_position, None); }); // Emit closed event on panel 1, which is active @@ -4360,8 +4691,8 @@ mod tests { // Now the left dock is closed, because panel_1 was the active panel workspace.read_with(cx, |workspace, cx| { - let left_dock = workspace.left_dock(); - assert!(!left_dock.read(cx).is_open()); + let right_dock = workspace.right_dock(); + assert!(!right_dock.read(cx).is_open()); }); } diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e96dff122186cb07e17eff54ff909d36c1454731..c45991cf11fc7a8ed9e654781d6d144a1d6795d5 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.89.0" +version = "0.90.0" publish = false [lib] diff --git a/crates/zed/src/languages/json.rs b/crates/zed/src/languages/json.rs index 406d54cc03907e94d2b76c4e87e97941458ac694..3947c62a6d96d11ba37444b34930f75bb8fbaec1 100644 --- a/crates/zed/src/languages/json.rs +++ b/crates/zed/src/languages/json.rs @@ -135,7 +135,10 @@ impl LspAdapter for JsonLspAdapter { }, "schemas": [ { - "fileMatch": [schema_file_match(&paths::SETTINGS)], + "fileMatch": [ + schema_file_match(&paths::SETTINGS), + &*paths::LOCAL_SETTINGS_RELATIVE_PATH, + ], "schema": settings_schema, }, { diff --git a/crates/zed/src/languages/yaml.rs b/crates/zed/src/languages/yaml.rs index bd5f2b4c021e7d8239d76ea29d8ef88ddcf8015b..7f87a7caedb764588a698b5e863fbd418c3859ed 100644 --- a/crates/zed/src/languages/yaml.rs +++ b/crates/zed/src/languages/yaml.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use futures::{future::BoxFuture, FutureExt, StreamExt}; use gpui::AppContext; use language::{ - language_settings::language_settings, LanguageServerBinary, LanguageServerName, LspAdapter, + language_settings::all_language_settings, LanguageServerBinary, LanguageServerName, LspAdapter, }; use node_runtime::NodeRuntime; use serde_json::Value; @@ -101,13 +101,16 @@ impl LspAdapter for YamlLspAdapter { } fn workspace_configuration(&self, cx: &mut AppContext) -> Option> { + let tab_size = all_language_settings(None, cx) + .language(Some("YAML")) + .tab_size; Some( future::ready(serde_json::json!({ "yaml": { "keyOrdering": false }, "[yaml]": { - "editor.tabSize": language_settings(Some("YAML"), cx).tab_size, + "editor.tabSize": tab_size, } })) .boxed(), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 31f331ef93ef17eecb4870f3ed23c9f963a5b3aa..9ec3e3d3f6b5c23c33e0a46ec683ad0bd2f98022 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -41,7 +41,7 @@ use std::{ Arc, Weak, }, thread, - time::Duration, + time::{Duration, SystemTime, UNIX_EPOCH}, }; use sum_tree::Bias; use terminal_view::{get_working_directory, TerminalSettings, TerminalView}; @@ -376,6 +376,7 @@ struct Panic { backtrace: Vec, // TODO // stripped_backtrace: String, + time: u128, } #[derive(Serialize)] @@ -413,6 +414,10 @@ fn init_panic_hook(app_version: String) { .map(|line| line.to_string()) .collect(), // modified_backtrace: None, + time: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), }; if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() { diff --git a/crates/zed/src/menus.rs b/crates/zed/src/menus.rs index adc1f81589fbe7cf4e062a225fb1ac439cd60966..76e88325f5cc858091cd3fcc367340337d554a9c 100644 --- a/crates/zed/src/menus.rs +++ b/crates/zed/src/menus.rs @@ -89,18 +89,9 @@ pub fn menus() -> Vec> { MenuItem::action("Zoom Out", super::DecreaseBufferFontSize), MenuItem::action("Reset Zoom", super::ResetBufferFontSize), MenuItem::separator(), - MenuItem::action( - "Toggle Left Dock", - workspace::ToggleLeftDock { focus: false }, - ), - MenuItem::action( - "Toggle Right Dock", - workspace::ToggleRightDock { focus: false }, - ), - MenuItem::action( - "Toggle Bottom Dock", - workspace::ToggleBottomDock { focus: false }, - ), + MenuItem::action("Toggle Left Dock", workspace::ToggleLeftDock), + MenuItem::action("Toggle Right Dock", workspace::ToggleRightDock), + MenuItem::action("Toggle Bottom Dock", workspace::ToggleBottomDock), MenuItem::submenu(Menu { name: "Editor Layout", items: vec![ diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index dffdccdfe5e5d517af05abb4af7a346ac93ef67e..83792514852fe30fb50766e8aca80b1d5e5ae140 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -357,7 +357,7 @@ pub fn initialize_workspace( .map_or(false, |entry| entry.is_dir()) }) { - workspace.toggle_dock(project_panel_position, false, cx); + workspace.toggle_dock(project_panel_position, cx); } workspace.add_panel(terminal_panel, cx); diff --git a/styles/src/buildLicenses.ts b/styles/src/buildLicenses.ts index 3367e50ff027601db2b68ead242dc3a266b21e1e..9a053e9c0c9ca7732a71b149a884df4a11b9391c 100644 --- a/styles/src/buildLicenses.ts +++ b/styles/src/buildLicenses.ts @@ -1,11 +1,9 @@ import * as fs from "fs" import toml from "toml" import { schemeMeta } from "./colorSchemes" -import { Meta, Verification } from "./themes/common/colorScheme" -import https from "https" -import crypto from "crypto" +import { MetaAndLicense } from "./themes/common/colorScheme" -const accepted_licenses_file = `${__dirname}/../../script/licenses/zed-licenses.toml` +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[] { @@ -20,8 +18,11 @@ function parseAcceptedToml(file: string): string[] { return obj.accepted } -function checkLicenses(schemeMeta: Meta[], licenses: string[]) { - for (let meta of schemeMeta) { +function checkLicenses( + schemeMetaWithLicense: MetaAndLicense[], + licenses: string[] +) { + for (const { meta } of schemeMetaWithLicense) { // FIXME: Add support for conjuctions and conditions if (licenses.indexOf(meta.license.SPDX) < 0) { throw Error( @@ -31,62 +32,23 @@ function checkLicenses(schemeMeta: Meta[], licenses: string[]) { } } -function getLicenseText( - schemeMeta: Meta[], - callback: (meta: Meta, license_text: string) => void -) { - for (let meta of schemeMeta) { - if (typeof meta.license.license_text == "string") { - callback(meta, meta.license.license_text) - } else { - let license_text_obj: Verification = meta.license.license_text - // The following copied from the example code on nodejs.org: - // https://nodejs.org/api/http.html#httpgetoptions-callback - https - .get(license_text_obj.https_url, (res) => { - const { statusCode } = res - - if (statusCode < 200 || statusCode >= 300) { - throw new Error( - `Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}` - ) - } - - res.setEncoding("utf8") - let rawData = "" - res.on("data", (chunk) => { - rawData += chunk - }) - res.on("end", () => { - const hash = crypto - .createHash("sha256") - .update(rawData) - .digest("hex") - if (license_text_obj.license_checksum == hash) { - callback(meta, rawData) - } else { - throw Error( - `Checksum for ${meta.name} did not match file downloaded from ${license_text_obj.https_url}` - ) - } - }) - }) - .on("error", (e) => { - throw e - }) - } +function generateLicenseFile(schemeMetaWithLicense: MetaAndLicense[]) { + for (const { meta, licenseFile } of schemeMetaWithLicense) { + const licenseText = fs.readFileSync(licenseFile).toString() + writeLicense(meta.name, meta.url, licenseText) } } -function writeLicense(schemeMeta: Meta, text: String) { +function writeLicense( + themeName: string, + themeUrl: string, + licenseText: String +) { process.stdout.write( - `## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n` + `## [${themeName}](${themeUrl})\n\n${licenseText}\n********************************************************************************\n\n` ) } -const accepted_licenses = parseAcceptedToml(accepted_licenses_file) -checkLicenses(schemeMeta, accepted_licenses) - -getLicenseText(schemeMeta, (meta, text) => { - writeLicense(meta, text) -}) +const acceptedLicenses = parseAcceptedToml(ACCEPTED_LICENSES_FILE) +checkLicenses(schemeMeta, acceptedLicenses) +generateLicenseFile(schemeMeta) diff --git a/styles/src/buildThemes.ts b/styles/src/buildThemes.ts index 2a63a407cc4f83dba146991a5edf3757abdfab65..43537655733b10fc2dbaefebf208722acfa2effb 100644 --- a/styles/src/buildThemes.ts +++ b/styles/src/buildThemes.ts @@ -1,7 +1,7 @@ import * as fs from "fs" import { tmpdir } from "os" import * as path from "path" -import colorSchemes, { staffColorSchemes } from "./colorSchemes" +import { colorSchemes, staffColorSchemes } from "./colorSchemes" import app from "./styleTree/app" import { ColorScheme } from "./themes/common/colorScheme" import snakeCase from "./utils/snakeCase" diff --git a/styles/src/colorSchemes.ts b/styles/src/colorSchemes.ts index 4d2d7f6e0253c6b6dd8d6d579000f3d9cb53ad5f..74f1a2a03c4e44e43640534755d804e8163413d3 100644 --- a/styles/src/colorSchemes.ts +++ b/styles/src/colorSchemes.ts @@ -1,54 +1,79 @@ import fs from "fs" import path from "path" -import { ColorScheme, Meta } from "./themes/common/colorScheme" +import { ColorScheme, MetaAndLicense } from "./themes/common/colorScheme" -const colorSchemes: ColorScheme[] = [] -export default colorSchemes +const THEMES_DIRECTORY = path.resolve(`${__dirname}/themes`) +const STAFF_DIRECTORY = path.resolve(`${__dirname}/themes/staff`) +const IGNORE_ITEMS = ["staff", "common", "common.ts"] +const ACCEPT_EXTENSION = ".ts" +const LICENSE_FILE_NAME = "LICENSE" -const schemeMeta: Meta[] = [] -export { schemeMeta } +function getAllTsFiles(directoryPath: string) { + const files = fs.readdirSync(directoryPath) + const fileList: string[] = [] -const staffColorSchemes: ColorScheme[] = [] -export { staffColorSchemes } + for (const file of files) { + if (!IGNORE_ITEMS.includes(file)) { + const filePath = path.join(directoryPath, file) -const experimentalColorSchemes: ColorScheme[] = [] -export { experimentalColorSchemes } - -const themes_directory = path.resolve(`${__dirname}/themes`) - -function for_all_color_schemes_in( - themesPath: string, - callback: (module: any, path: string) => void -) { - for (const fileName of fs.readdirSync(themesPath)) { - if (fileName == "template.ts") continue - const filePath = path.join(themesPath, fileName) - - if (fs.statSync(filePath).isFile()) { - const colorScheme = require(filePath) - callback(colorScheme, path.basename(filePath)) + if (fs.statSync(filePath).isDirectory()) { + fileList.push(...getAllTsFiles(filePath)) + } else if (path.extname(file) === ACCEPT_EXTENSION) { + fileList.push(filePath) + } } } + + return fileList } -function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) { - for_all_color_schemes_in(themesPath, (colorScheme, _path) => { +function getAllColorSchemes(directoryPath: string) { + const files = getAllTsFiles(directoryPath) + return files.map((filePath) => ({ + colorScheme: require(filePath), + filePath, + fileName: path.basename(filePath), + licenseFile: `${path.dirname(filePath)}/${LICENSE_FILE_NAME}`, + })) +} + +function getColorSchemes(directoryPath: string) { + const colorSchemes: ColorScheme[] = [] + + for (const { colorScheme } of getAllColorSchemes(directoryPath)) { if (colorScheme.dark) colorSchemes.push(colorScheme.dark) - if (colorScheme.light) colorSchemes.push(colorScheme.light) - }) + else if (colorScheme.light) colorSchemes.push(colorScheme.light) + } + + return colorSchemes } -fillColorSchemes(themes_directory, colorSchemes) -fillColorSchemes(path.resolve(`${themes_directory}/staff`), staffColorSchemes) +function getMetaAndLicense(directoryPath: string) { + const meta: MetaAndLicense[] = [] + + for (const { colorScheme, filePath, licenseFile } of getAllColorSchemes( + directoryPath + )) { + const licenseExists = fs.existsSync(licenseFile) + if (!licenseExists) { + throw Error( + `Public theme should have a LICENSE file ${licenseFile}` + ) + } -function fillMeta(themesPath: string, meta: Meta[]) { - for_all_color_schemes_in(themesPath, (colorScheme, path) => { - if (colorScheme.meta) { - meta.push(colorScheme.meta) - } else { - throw Error(`Public theme ${path} must have a meta field`) + if (!colorScheme.meta) { + throw Error(`Public theme ${filePath} must have a meta field`) } - }) + + meta.push({ + meta: colorScheme.meta, + licenseFile, + }) + } + + return meta } -fillMeta(themes_directory, schemeMeta) +export const colorSchemes = getColorSchemes(THEMES_DIRECTORY) +export const staffColorSchemes = getColorSchemes(STAFF_DIRECTORY) +export const schemeMeta = getMetaAndLicense(THEMES_DIRECTORY) diff --git a/styles/src/styleTree/tabBar.ts b/styles/src/styleTree/tabBar.ts index 61d30be7e03c8eceb026ea3f0fa1c39eab9cca60..39a1ef0407ce92b966e45f19f17a89c3c248c59c 100644 --- a/styles/src/styleTree/tabBar.ts +++ b/styles/src/styleTree/tabBar.ts @@ -94,6 +94,9 @@ export default function tabBar(colorScheme: ColorScheme) { hover: { color: foreground(layer, "hovered"), }, + active: { + color: foreground(layer, "accent"), + } }, paneButtonContainer: { background: tab.background, diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 737d225784aeabe86edbdcf9a044748855ffc01f..ec992c9c0b368aa7ee792aa3b2ce5c2da3045d2c 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -13,6 +13,7 @@ import tabBar from "./tabBar" export default function workspace(colorScheme: ColorScheme) { const layer = colorScheme.lowest + const isLight = colorScheme.isLight const itemSpacing = 8 const titlebarButton = { cornerRadius: 6, @@ -119,13 +120,19 @@ export default function workspace(colorScheme: ColorScheme) { cursor: "Arrow", }, zoomedBackground: { - padding: 10, cursor: "Arrow", - background: withOpacity(background(colorScheme.lowest), 0.5) + background: isLight + ? withOpacity(background(colorScheme.lowest), 0.8) + : withOpacity(background(colorScheme.highest), 0.6) }, - zoomedForeground: { + zoomedPaneForeground: { + margin: 16, shadow: colorScheme.modalShadow, - border: border(colorScheme.highest, { overlay: true }), + border: border(colorScheme.lowest, { overlay: true }), + }, + zoomedPanelForeground: { + margin: 16, + border: border(colorScheme.lowest, { overlay: true }), }, dock: { left: { diff --git a/styles/src/themes/andromeda/LICENSE b/styles/src/themes/andromeda/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..bdd549491fac6822878157337aa5dc4d09ef53f2 --- /dev/null +++ b/styles/src/themes/andromeda/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/andromeda.ts b/styles/src/themes/andromeda/andromeda.ts similarity index 71% rename from styles/src/themes/andromeda.ts rename to styles/src/themes/andromeda/andromeda.ts index 7eba7b1481aeba8b1429b323bb51c0f78c0a6069..d7f7f53b90705bbee2431100797631d33bfbb041 100644 --- a/styles/src/themes/andromeda.ts +++ b/styles/src/themes/andromeda/andromeda.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Andromeda" @@ -34,12 +34,6 @@ export const meta: Meta = { author: "EliverLara", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md", - license_checksum: - "2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89", - }, }, url: "https://github.com/EliverLara/Andromeda", } diff --git a/styles/src/themes/atelier/LICENSE b/styles/src/themes/atelier/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9f92967a0436d7118c20cf29bfb8844dba2699b1 --- /dev/null +++ b/styles/src/themes/atelier/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2023 Bram de Haan, http://atelierbramdehaan.nl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/atelier-cave-dark.ts b/styles/src/themes/atelier/atelier-cave-dark.ts similarity index 93% rename from styles/src/themes/atelier-cave-dark.ts rename to styles/src/themes/atelier/atelier-cave-dark.ts index a56e22cd92373997cc40d19eddf5b05e9cad5a02..41ee6a745c9c9165c0e5610fb30c92bbbedce3a6 100644 --- a/styles/src/themes/atelier-cave-dark.ts +++ b/styles/src/themes/atelier/atelier-cave-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-cave-light.ts b/styles/src/themes/atelier/atelier-cave-light.ts similarity index 93% rename from styles/src/themes/atelier-cave-light.ts rename to styles/src/themes/atelier/atelier-cave-light.ts index 3b180752cf97ea1bcc515c904deb20eafc03aea6..9e0473bbfa0b63838329e21eaae11ba63abc2c53 100644 --- a/styles/src/themes/atelier-cave-light.ts +++ b/styles/src/themes/atelier/atelier-cave-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-dune-dark.ts b/styles/src/themes/atelier/atelier-dune-dark.ts similarity index 93% rename from styles/src/themes/atelier-dune-dark.ts rename to styles/src/themes/atelier/atelier-dune-dark.ts index 0ab402a99d4adf8f1e0ed41979d5cc9973696404..c21ee61a6030c03ced3adb3ae399edb1c4ac999f 100644 --- a/styles/src/themes/atelier-dune-dark.ts +++ b/styles/src/themes/atelier/atelier-dune-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-dune-light.ts b/styles/src/themes/atelier/atelier-dune-light.ts similarity index 93% rename from styles/src/themes/atelier-dune-light.ts rename to styles/src/themes/atelier/atelier-dune-light.ts index e6a09985d5e09f8612d0a41f7b3a898991b9b720..956ae72c60f6da957abffb3c7234cea5441d50a3 100644 --- a/styles/src/themes/atelier-dune-light.ts +++ b/styles/src/themes/atelier/atelier-dune-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-estuary-dark.ts b/styles/src/themes/atelier/atelier-estuary-dark.ts similarity index 93% rename from styles/src/themes/atelier-estuary-dark.ts rename to styles/src/themes/atelier/atelier-estuary-dark.ts index c4ec50c1e00ed7731d069ed3f559204b9030b10c..b9f1880d682fa8b9a9b23015beba5ffb355564b5 100644 --- a/styles/src/themes/atelier-estuary-dark.ts +++ b/styles/src/themes/atelier/atelier-estuary-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-estuary-light.ts b/styles/src/themes/atelier/atelier-estuary-light.ts similarity index 93% rename from styles/src/themes/atelier-estuary-light.ts rename to styles/src/themes/atelier/atelier-estuary-light.ts index 6fce0e44833d2d0bf8657a3465bcfb601b262644..a9eeb612edb4a45fcf0d51bcf92f95c1122697dd 100644 --- a/styles/src/themes/atelier-estuary-light.ts +++ b/styles/src/themes/atelier/atelier-estuary-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-forest-dark.ts b/styles/src/themes/atelier/atelier-forest-dark.ts similarity index 93% rename from styles/src/themes/atelier-forest-dark.ts rename to styles/src/themes/atelier/atelier-forest-dark.ts index 7c47c55a830ed780f662d83d9f768d26207dc31e..352f1ea43efe7760e6752e7d5a313ad8a5ff3dd9 100644 --- a/styles/src/themes/atelier-forest-dark.ts +++ b/styles/src/themes/atelier/atelier-forest-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-forest-light.ts b/styles/src/themes/atelier/atelier-forest-light.ts similarity index 93% rename from styles/src/themes/atelier-forest-light.ts rename to styles/src/themes/atelier/atelier-forest-light.ts index 8ce06164769a7a8dfa15fc74a77b6aa0f3c9e3af..1378c9b061490b0606e5e6b5297d81878cf5986e 100644 --- a/styles/src/themes/atelier-forest-light.ts +++ b/styles/src/themes/atelier/atelier-forest-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-heath-dark.ts b/styles/src/themes/atelier/atelier-heath-dark.ts similarity index 93% rename from styles/src/themes/atelier-heath-dark.ts rename to styles/src/themes/atelier/atelier-heath-dark.ts index 87458ab8f5ab68a30e602de9b62edbe1e4cf5c18..4c3519442271f6a0e5967dc2c4e5a276834ffa0d 100644 --- a/styles/src/themes/atelier-heath-dark.ts +++ b/styles/src/themes/atelier/atelier-heath-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-heath-light.ts b/styles/src/themes/atelier/atelier-heath-light.ts similarity index 93% rename from styles/src/themes/atelier-heath-light.ts rename to styles/src/themes/atelier/atelier-heath-light.ts index 3db34370418f1cd27e305a3175e045e591e36ae9..51b9ef93988f2e4f0eb3527a6e2a4003a1a13647 100644 --- a/styles/src/themes/atelier-heath-light.ts +++ b/styles/src/themes/atelier/atelier-heath-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-lakeside-dark.ts b/styles/src/themes/atelier/atelier-lakeside-dark.ts similarity index 93% rename from styles/src/themes/atelier-lakeside-dark.ts rename to styles/src/themes/atelier/atelier-lakeside-dark.ts index a8297ef9fd307243f1c9c84f3caa1e627120a19e..ece9179860ec0bd45db4849a179ce0ff7b7a77a8 100644 --- a/styles/src/themes/atelier-lakeside-dark.ts +++ b/styles/src/themes/atelier/atelier-lakeside-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-lakeside-light.ts b/styles/src/themes/atelier/atelier-lakeside-light.ts similarity index 93% rename from styles/src/themes/atelier-lakeside-light.ts rename to styles/src/themes/atelier/atelier-lakeside-light.ts index 408fabcaf36a50df5229ea0034d12c67bd3b3562..fd265d10c8acde6e989e5ac5e961aa706e0fc207 100644 --- a/styles/src/themes/atelier-lakeside-light.ts +++ b/styles/src/themes/atelier/atelier-lakeside-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-plateau-dark.ts b/styles/src/themes/atelier/atelier-plateau-dark.ts similarity index 93% rename from styles/src/themes/atelier-plateau-dark.ts rename to styles/src/themes/atelier/atelier-plateau-dark.ts index 731cb824e44eba0590c5f1dc4804121aa7f97cd9..ef64782bda4109269beb52b9f8c9aa65486cb4f7 100644 --- a/styles/src/themes/atelier-plateau-dark.ts +++ b/styles/src/themes/atelier/atelier-plateau-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-plateau-light.ts b/styles/src/themes/atelier/atelier-plateau-light.ts similarity index 93% rename from styles/src/themes/atelier-plateau-light.ts rename to styles/src/themes/atelier/atelier-plateau-light.ts index 96f295a69518b1216482776e44d5b36912f526da..9fe51f5b7914d283bfcc4e2651bab2f987ede16a 100644 --- a/styles/src/themes/atelier-plateau-light.ts +++ b/styles/src/themes/atelier/atelier-plateau-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-savanna-dark.ts b/styles/src/themes/atelier/atelier-savanna-dark.ts similarity index 93% rename from styles/src/themes/atelier-savanna-dark.ts rename to styles/src/themes/atelier/atelier-savanna-dark.ts index dfcb4f27cbedd41608b941da97172d083e7a8e76..36de9b817f52bc490723a8ecc5d91f271dcb6f27 100644 --- a/styles/src/themes/atelier-savanna-dark.ts +++ b/styles/src/themes/atelier/atelier-savanna-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-savanna-light.ts b/styles/src/themes/atelier/atelier-savanna-light.ts similarity index 93% rename from styles/src/themes/atelier-savanna-light.ts rename to styles/src/themes/atelier/atelier-savanna-light.ts index 4bc1389fc9cf3cb3b4870a58665a510a65d4c9e2..d5d9cb369d8da7b5a141b251b4af1d5ef8c9b978 100644 --- a/styles/src/themes/atelier-savanna-light.ts +++ b/styles/src/themes/atelier/atelier-savanna-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-seaside-dark.ts b/styles/src/themes/atelier/atelier-seaside-dark.ts similarity index 93% rename from styles/src/themes/atelier-seaside-dark.ts rename to styles/src/themes/atelier/atelier-seaside-dark.ts index 1326a277861e417711c3ea30d39230b227c9ac51..f7c49ad71eb1e885e92491f960560b5c7b72d24b 100644 --- a/styles/src/themes/atelier-seaside-dark.ts +++ b/styles/src/themes/atelier/atelier-seaside-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-seaside-light.ts b/styles/src/themes/atelier/atelier-seaside-light.ts similarity index 93% rename from styles/src/themes/atelier-seaside-light.ts rename to styles/src/themes/atelier/atelier-seaside-light.ts index 6f6823718aed9ba4e3d7325b9968b11ba5472b40..1cf64614464a5d8b3e23576b1bc110d4c21ebff6 100644 --- a/styles/src/themes/atelier-seaside-light.ts +++ b/styles/src/themes/atelier/atelier-seaside-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-sulphurpool-dark.ts b/styles/src/themes/atelier/atelier-sulphurpool-dark.ts similarity index 93% rename from styles/src/themes/atelier-sulphurpool-dark.ts rename to styles/src/themes/atelier/atelier-sulphurpool-dark.ts index dcfc0d932a67d7081f5bcdb195f6fc57cfc2d79f..b4a4e2a651981487b1e51c275d31d0c7ff70b029 100644 --- a/styles/src/themes/atelier-sulphurpool-dark.ts +++ b/styles/src/themes/atelier/atelier-sulphurpool-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/atelier-sulphurpool-light.ts b/styles/src/themes/atelier/atelier-sulphurpool-light.ts similarity index 93% rename from styles/src/themes/atelier-sulphurpool-light.ts rename to styles/src/themes/atelier/atelier-sulphurpool-light.ts index b2b5f7c328ba86be296be0fb938bf850571dbef9..046adbdf4315a4d305d96b79046631513a133a91 100644 --- a/styles/src/themes/atelier-sulphurpool-light.ts +++ b/styles/src/themes/atelier/atelier-sulphurpool-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" -import { metaCommon, name, buildSyntax, Variant } from "./common/atelier-common" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" +import { metaCommon, name, buildSyntax, Variant } from "./common" const variant: Variant = { meta: { diff --git a/styles/src/themes/common/atelier-common.ts b/styles/src/themes/atelier/common.ts similarity index 86% rename from styles/src/themes/common/atelier-common.ts rename to styles/src/themes/atelier/common.ts index 08a915d01948f300441d0904cbff2fd406e3dcba..746834c88b0103daa7be8c73e75b96dd298e1c6a 100644 --- a/styles/src/themes/common/atelier-common.ts +++ b/styles/src/themes/atelier/common.ts @@ -1,4 +1,4 @@ -import { License, Meta, ThemeSyntax } from "./colorScheme" +import { License, Meta, ThemeSyntax } from "../common/colorScheme" export interface Variant { meta: Meta @@ -29,11 +29,6 @@ export const metaCommon: { author: "Bram de Haan (http://atelierbramdehaan.nl)", license: { SPDX: "MIT", - license_text: { - https_url: "https://atelierbram.mit-license.org/license.txt", - license_checksum: - "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5", - }, }, } diff --git a/styles/src/themes/ayu/LICENSE b/styles/src/themes/ayu/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..6b83ef0582f26b04f37f8b78ef5a3121b3f3a326 --- /dev/null +++ b/styles/src/themes/ayu/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Ike Ku + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/ayu-dark.ts b/styles/src/themes/ayu/ayu-dark.ts similarity index 66% rename from styles/src/themes/ayu-dark.ts rename to styles/src/themes/ayu/ayu-dark.ts index c7e86994feec6a78879b383e5bf71941084c0b9e..1dc663f161b6baa862fb42cb07fc9ddbb9c5c3e0 100644 --- a/styles/src/themes/ayu-dark.ts +++ b/styles/src/themes/ayu/ayu-dark.ts @@ -1,5 +1,5 @@ -import { createColorScheme } from "./common/ramps" -import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common" +import { createColorScheme } from "../common/ramps" +import { ayu, meta as themeMeta, buildTheme } from "./common" export const meta = { ...themeMeta, diff --git a/styles/src/themes/ayu-light.ts b/styles/src/themes/ayu/ayu-light.ts similarity index 66% rename from styles/src/themes/ayu-light.ts rename to styles/src/themes/ayu/ayu-light.ts index 9acabf6a3957a20aca2e3fa0181cf675840ddae4..25435219447e1d7e4dd1125c59cb04cb420315eb 100644 --- a/styles/src/themes/ayu-light.ts +++ b/styles/src/themes/ayu/ayu-light.ts @@ -1,5 +1,5 @@ -import { createColorScheme } from "./common/ramps" -import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common" +import { createColorScheme } from "../common/ramps" +import { ayu, meta as themeMeta, buildTheme } from "./common" export const meta = { ...themeMeta, diff --git a/styles/src/themes/ayu-mirage.ts b/styles/src/themes/ayu/ayu-mirage.ts similarity index 66% rename from styles/src/themes/ayu-mirage.ts rename to styles/src/themes/ayu/ayu-mirage.ts index 2a01512673b73d104ead5fc20edf353b813c16bb..2ada3678ee47a95112bcff76150b8b2f54273f40 100644 --- a/styles/src/themes/ayu-mirage.ts +++ b/styles/src/themes/ayu/ayu-mirage.ts @@ -1,5 +1,5 @@ -import { createColorScheme } from "./common/ramps" -import { ayu, meta as themeMeta, buildTheme } from "./common/ayu-common" +import { createColorScheme } from "../common/ramps" +import { ayu, meta as themeMeta, buildTheme } from "./common" export const meta = { ...themeMeta, diff --git a/styles/src/themes/common/ayu-common.ts b/styles/src/themes/ayu/common.ts similarity index 88% rename from styles/src/themes/common/ayu-common.ts rename to styles/src/themes/ayu/common.ts index f08817ef492e7c56fac0e593ffd0d74d1e4d735b..e586e2d22d7c8a2fdf237ed0f19d56e4eb3bc249 100644 --- a/styles/src/themes/common/ayu-common.ts +++ b/styles/src/themes/ayu/common.ts @@ -1,8 +1,8 @@ import { dark, light, mirage } from "ayu" -import { ThemeSyntax } from "./syntax" +import { ThemeSyntax } from "../common/syntax" import chroma from "chroma-js" -import { colorRamp } from "./ramps" -import { Meta } from "./colorScheme" +import { colorRamp } from "../common/ramps" +import { Meta } from "../common/colorScheme" export const ayu = { dark, @@ -79,12 +79,6 @@ export const meta: Meta = { author: "dempfi", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/dempfi/ayu/master/LICENSE", - license_checksum: - "e0af0e0d1754c18ca075649d42f5c6d9a60f8bdc03c20dfd97105f2253a94173", - }, }, url: "https://github.com/dempfi/ayu", } diff --git a/styles/src/themes/common/colorScheme.ts b/styles/src/themes/common/colorScheme.ts index 3b5b8f69329651f20a0ad5aef834eb9435e8b5d1..5cf125ae8f723fd2130eacda5d00be3ad604bf6e 100644 --- a/styles/src/themes/common/colorScheme.ts +++ b/styles/src/themes/common/colorScheme.ts @@ -19,6 +19,11 @@ export interface ColorScheme { syntax?: Partial } +export interface MetaAndLicense { + meta: Meta + licenseFile: string +} + export interface Meta { name: string author: string @@ -28,13 +33,6 @@ export interface Meta { export interface License { SPDX: SPDXExpression - /// A url where we can download the license's text - license_text: Verification | string -} - -export interface Verification { - https_url: string - license_checksum: string } // License name -> License text diff --git a/styles/src/themes/gruvbox/LICENSE b/styles/src/themes/gruvbox/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2a9230614399a48916e74cfb74bd4625686c7bcb --- /dev/null +++ b/styles/src/themes/gruvbox/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/gruvbox-common.ts b/styles/src/themes/gruvbox/gruvbox-common.ts similarity index 83% rename from styles/src/themes/gruvbox-common.ts rename to styles/src/themes/gruvbox/gruvbox-common.ts index c42362c11c007e5371ad7134c7300667fe04304e..b113ce68c6f94450671247c066402ed6102095cb 100644 --- a/styles/src/themes/gruvbox-common.ts +++ b/styles/src/themes/gruvbox/gruvbox-common.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta, ThemeSyntax } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta, ThemeSyntax } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Gruvbox" @@ -248,8 +248,6 @@ export const meta: Meta = { name, license: { SPDX: "MIT", // "MIT/X11" - license_text: - "Copyright \n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/ or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 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.", }, author: "morhetz ", url: "https://github.com/morhetz/gruvbox", diff --git a/styles/src/themes/gruvbox-dark-hard.ts b/styles/src/themes/gruvbox/gruvbox-dark-hard.ts similarity index 100% rename from styles/src/themes/gruvbox-dark-hard.ts rename to styles/src/themes/gruvbox/gruvbox-dark-hard.ts diff --git a/styles/src/themes/gruvbox-dark-soft.ts b/styles/src/themes/gruvbox/gruvbox-dark-soft.ts similarity index 100% rename from styles/src/themes/gruvbox-dark-soft.ts rename to styles/src/themes/gruvbox/gruvbox-dark-soft.ts diff --git a/styles/src/themes/gruvbox-dark.ts b/styles/src/themes/gruvbox/gruvbox-dark.ts similarity index 100% rename from styles/src/themes/gruvbox-dark.ts rename to styles/src/themes/gruvbox/gruvbox-dark.ts diff --git a/styles/src/themes/gruvbox-light-hard.ts b/styles/src/themes/gruvbox/gruvbox-light-hard.ts similarity index 100% rename from styles/src/themes/gruvbox-light-hard.ts rename to styles/src/themes/gruvbox/gruvbox-light-hard.ts diff --git a/styles/src/themes/gruvbox-light-soft.ts b/styles/src/themes/gruvbox/gruvbox-light-soft.ts similarity index 100% rename from styles/src/themes/gruvbox-light-soft.ts rename to styles/src/themes/gruvbox/gruvbox-light-soft.ts diff --git a/styles/src/themes/gruvbox-light.ts b/styles/src/themes/gruvbox/gruvbox-light.ts similarity index 100% rename from styles/src/themes/gruvbox-light.ts rename to styles/src/themes/gruvbox/gruvbox-light.ts diff --git a/styles/src/themes/one/LICENSE b/styles/src/themes/one/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..dc07dc10ad0de56ebe0bfad8d65e82f0f5d627ef --- /dev/null +++ b/styles/src/themes/one/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/one-dark.ts b/styles/src/themes/one/one-dark.ts similarity index 83% rename from styles/src/themes/one-dark.ts rename to styles/src/themes/one/one-dark.ts index 85417a0e68651341edded4689425b15a0d8f29b1..1a88ddf7dce22e39e6712136b619e0ff2aefeccd 100644 --- a/styles/src/themes/one-dark.ts +++ b/styles/src/themes/one/one-dark.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { fontWeights } from "../common" -import { Meta, ThemeSyntax } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { fontWeights } from "../../common" +import { Meta, ThemeSyntax } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "One Dark" @@ -74,12 +74,6 @@ export const meta: Meta = { author: "simurai", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", - license_checksum: - "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8", - }, }, url: "https://github.com/atom/atom/tree/master/packages/one-dark-ui", } diff --git a/styles/src/themes/one-light.ts b/styles/src/themes/one/one-light.ts similarity index 82% rename from styles/src/themes/one-light.ts rename to styles/src/themes/one/one-light.ts index 7bf21aee17d4871681b33174e3b20bea06ced9fe..231ee3abb3d19cbc6f4bd9a0d4d150a578542db8 100644 --- a/styles/src/themes/one-light.ts +++ b/styles/src/themes/one/one-light.ts @@ -1,7 +1,7 @@ import chroma from "chroma-js" -import { fontWeights } from "../common" -import { Meta, ThemeSyntax } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { fontWeights } from "../../common" +import { Meta, ThemeSyntax } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "One Light" @@ -73,12 +73,6 @@ export const meta: Meta = { author: "simurai", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/atom/atom/master/packages/one-light-ui/LICENSE.md", - license_checksum: - "d5af8fc171f6f600c0ab4e7597dca398dda80dbe6821ce01cef78e859e7a00f8", - }, }, url: "https://github.com/atom/atom/tree/master/packages/one-light-ui", } diff --git a/styles/src/themes/rose-pine/LICENSE b/styles/src/themes/rose-pine/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..dfd60136f95374fbe3e112a6051a4854b61ac4ec --- /dev/null +++ b/styles/src/themes/rose-pine/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Emilia Dunfelt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/rose-pine-dawn.ts b/styles/src/themes/rose-pine/rose-pine-dawn.ts similarity index 71% rename from styles/src/themes/rose-pine-dawn.ts rename to styles/src/themes/rose-pine/rose-pine-dawn.ts index 427b05f72b8e5672897b7457738d4c43e2c6f603..cf953872e1a4f7a15e1e0bd3f49356ca86a5e11b 100644 --- a/styles/src/themes/rose-pine-dawn.ts +++ b/styles/src/themes/rose-pine/rose-pine-dawn.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Rosé Pine Dawn" @@ -34,12 +34,6 @@ export const meta: Meta = { author: "edunfelt", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", - license_checksum: - "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a", - }, }, url: "https://github.com/edunfelt/base16-rose-pine-scheme", } diff --git a/styles/src/themes/rose-pine-moon.ts b/styles/src/themes/rose-pine/rose-pine-moon.ts similarity index 70% rename from styles/src/themes/rose-pine-moon.ts rename to styles/src/themes/rose-pine/rose-pine-moon.ts index be2f5a8dafd366803f13737c75bdb48d7e101aa9..85ac5fd73ef73bc8e36f228a50c4ab1cc3749169 100644 --- a/styles/src/themes/rose-pine-moon.ts +++ b/styles/src/themes/rose-pine/rose-pine-moon.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Rosé Pine Moon" @@ -34,12 +34,6 @@ export const meta: Meta = { author: "edunfelt", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", - license_checksum: - "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a", - }, }, url: "https://github.com/edunfelt/base16-rose-pine-scheme", } diff --git a/styles/src/themes/rose-pine.ts b/styles/src/themes/rose-pine/rose-pine.ts similarity index 68% rename from styles/src/themes/rose-pine.ts rename to styles/src/themes/rose-pine/rose-pine.ts index 944550f1250ad01146f8622a8199cf027f4658ab..fe2817be1363076b6e171adf1fbd0469d1f85cdb 100644 --- a/styles/src/themes/rose-pine.ts +++ b/styles/src/themes/rose-pine/rose-pine.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Rosé Pine" @@ -32,12 +32,6 @@ export const meta: Meta = { author: "edunfelt", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/edunfelt/base16-rose-pine-scheme/main/LICENSE", - license_checksum: - "6ca1b9da8c78c8441c5aa43d024a4e4a7bf59d1ecca1480196e94fda0f91ee4a", - }, }, url: "https://github.com/edunfelt/base16-rose-pine-scheme", } diff --git a/styles/src/themes/sandcastle/LICENSE b/styles/src/themes/sandcastle/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..c66a06c51b46671cfe20194ac8ff545683c7a7e3 --- /dev/null +++ b/styles/src/themes/sandcastle/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 George Essig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/sandcastle.ts b/styles/src/themes/sandcastle/sandcastle.ts similarity index 68% rename from styles/src/themes/sandcastle.ts rename to styles/src/themes/sandcastle/sandcastle.ts index 483f01b27a1850463348e0a9c575086a014d5f1b..2544b6399a66168446f96b3d71654cad48d9fcdc 100644 --- a/styles/src/themes/sandcastle.ts +++ b/styles/src/themes/sandcastle/sandcastle.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Sandcastle" @@ -32,12 +32,6 @@ export const meta: Meta = { author: "gessig", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/gessig/base16-sandcastle-scheme/master/LICENSE", - license_checksum: - "8399d44b4d935b60be9fee0a76d7cc9a817b4f3f11574c9d6d1e8fd57e72ffdc", - }, }, url: "https://github.com/gessig/base16-sandcastle-scheme", } diff --git a/styles/src/themes/solarized/LICENSE b/styles/src/themes/solarized/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..221eee6f152873e2e6c52ca4a89ac1d65118843b --- /dev/null +++ b/styles/src/themes/solarized/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2011 Ethan Schoonover + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/solarized.ts b/styles/src/themes/solarized/solarized.ts similarity index 72% rename from styles/src/themes/solarized.ts rename to styles/src/themes/solarized/solarized.ts index 1210c4380608e812d39dec0f36d6ec69dab37e9b..6c826fbee75c7a18633a782bad1e0106c9cf81d7 100644 --- a/styles/src/themes/solarized.ts +++ b/styles/src/themes/solarized/solarized.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta as Metadata } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta as Metadata } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Solarized" @@ -35,12 +35,6 @@ export const meta: Metadata = { author: "Ethan Schoonover", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/altercation/solarized/master/LICENSE", - license_checksum: - "494aefdabf86acce06bd63001ad8aedad4ee38da23509d3f917d95aa3368b9a6", - }, }, url: "https://github.com/altercation/solarized", } diff --git a/styles/src/themes/summercamp/LICENSE b/styles/src/themes/summercamp/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d7525414ad01c246c21e908666064d6db4233901 --- /dev/null +++ b/styles/src/themes/summercamp/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Zoe FiriH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +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 diff --git a/styles/src/themes/summercamp.ts b/styles/src/themes/summercamp/summercamp.ts similarity index 71% rename from styles/src/themes/summercamp.ts rename to styles/src/themes/summercamp/summercamp.ts index 7df125e86606d299e52ffb07140f156744e086ce..94c0072e4fddd5eda97851c4b0d8cc2908700303 100644 --- a/styles/src/themes/summercamp.ts +++ b/styles/src/themes/summercamp/summercamp.ts @@ -1,6 +1,6 @@ import chroma from "chroma-js" -import { Meta } from "./common/colorScheme" -import { colorRamp, createColorScheme } from "./common/ramps" +import { Meta } from "../common/colorScheme" +import { colorRamp, createColorScheme } from "../common/ramps" const name = "Summercamp" @@ -34,11 +34,5 @@ export const meta: Meta = { url: "https://github.com/zoefiri/base16-sc", license: { SPDX: "MIT", - license_text: { - https_url: - "https://raw.githubusercontent.com/zoefiri/base16-sc/master/LICENSE", - license_checksum: - "fadcc834b7eaf2943800956600e8aeea4b495ecf6490f4c4b6c91556a90accaf", - }, }, }