.github/workflows/ci.yml π
@@ -19,7 +19,9 @@ env:
jobs:
rustfmt:
name: Check formatting
- runs-on: self-hosted
+ runs-on:
+ - self-hosted
+ - test
steps:
- name: Install Rust
run: |
Joseph Lyons created
.github/workflows/ci.yml | 4
Cargo.lock | 7
assets/icons/ellipsis_14.svg | 3
assets/icons/leave_12.svg | 3
assets/keymaps/default.json | 5
assets/keymaps/vim.json | 1
assets/settings/default.json | 2
crates/activity_indicator/src/activity_indicator.rs | 147
crates/call/src/call.rs | 12
crates/call/src/room.rs | 91
crates/client/src/client.rs | 22
crates/collab/Cargo.toml | 2
crates/collab/migrations.sqlite/20221109000000_test_schema.sql | 14
crates/collab/migrations/20230202155735_followers.sql | 15
crates/collab/src/db.rs | 282 +
crates/collab/src/db/follower.rs | 51
crates/collab/src/db/room.rs | 8
crates/collab/src/rpc.rs | 35
crates/collab/src/tests/integration_tests.rs | 120
crates/collab_ui/Cargo.toml | 1
crates/collab_ui/src/collab_titlebar_item.rs | 761 ++-
crates/collab_ui/src/collab_ui.rs | 6
crates/collab_ui/src/collaborator_list_popover.rs | 165
crates/collab_ui/src/contact_list.rs | 28
crates/collab_ui/src/contacts_popover.rs | 4
crates/collab_ui/src/face_pile.rs | 101
crates/context_menu/src/context_menu.rs | 47
crates/editor/src/editor.rs | 72
crates/editor/src/editor_tests.rs | 8
crates/editor/src/items.rs | 4
crates/editor/src/test/editor_lsp_test_context.rs | 6
crates/feedback/src/feedback_editor.rs | 24
crates/file_finder/src/file_finder.rs | 69
crates/fuzzy/src/matcher.rs | 1
crates/fuzzy/src/paths.rs | 29
crates/gpui/src/app.rs | 175
crates/gpui/src/app/action.rs | 8
crates/gpui/src/app/menu.rs | 37
crates/gpui/src/elements.rs | 1
crates/gpui/src/elements/flex.rs | 2
crates/gpui/src/keymap_matcher.rs | 86
crates/gpui/src/keymap_matcher/keymap_context.rs | 29
crates/gpui/src/platform/mac/platform.rs | 47
crates/gpui/src/platform/mac/window.rs | 1
crates/live_kit_client/examples/test_app.rs | 1
crates/picker/src/picker.rs | 2
crates/project_panel/src/project_panel.rs | 2
crates/rope/Cargo.toml | 2
crates/rpc/proto/zed.proto | 11
crates/rpc/src/rpc.rs | 2
crates/search/src/project_search.rs | 8
crates/terminal_view/src/terminal_view.rs | 99
crates/theme/src/theme.rs | 24
crates/theme_testbench/src/theme_testbench.rs | 47
crates/vim/src/motion.rs | 50
crates/vim/src/normal.rs | 21
crates/vim/src/state.rs | 20
crates/vim/test_data/test_enter.json | 1
crates/workspace/src/item.rs | 62
crates/workspace/src/pane.rs | 9
crates/workspace/src/shared_screen.rs | 62
crates/workspace/src/workspace.rs | 45
crates/zed/Cargo.toml | 2
crates/zed/resources/app-icon-preview.png | 0
crates/zed/resources/app-icon-preview@2x.png | 0
crates/zed/resources/app-icon.png | 0
crates/zed/resources/app-icon@2x.png | 0
crates/zed/src/menus.rs | 420 -
crates/zed/src/zed.rs | 6
styles/.prettierignore | 2
styles/package-lock.json | 229
styles/package.json | 14
styles/src/buildLicenses.ts | 112
styles/src/buildThemes.ts | 72
styles/src/colorSchemes.ts | 74
styles/src/common.ts | 95
styles/src/styleTree/app.ts | 138
styles/src/styleTree/commandPalette.ts | 54
styles/src/styleTree/components.ts | 318
styles/src/styleTree/contactFinder.ts | 126
styles/src/styleTree/contactList.ts | 356
styles/src/styleTree/contactNotification.ts | 82
styles/src/styleTree/contactsPopover.ts | 52
styles/src/styleTree/contextMenu.ts | 79
styles/src/styleTree/editor.ts | 586 +-
styles/src/styleTree/feedback.ts | 69
styles/src/styleTree/hoverPopover.ts | 82
styles/src/styleTree/incomingCallNotification.ts | 94
styles/src/styleTree/picker.ts | 146
styles/src/styleTree/projectDiagnostics.ts | 20
styles/src/styleTree/projectPanel.ts | 110
styles/src/styleTree/projectSharedNotification.ts | 94
styles/src/styleTree/search.ts | 180
styles/src/styleTree/sharedScreen.ts | 12
styles/src/styleTree/simpleMessageNotification.ts | 58
styles/src/styleTree/statusBar.ts | 214
styles/src/styleTree/tabBar.ts | 180
styles/src/styleTree/terminal.ts | 94
styles/src/styleTree/tooltip.ts | 40
styles/src/styleTree/updateNotification.ts | 54
styles/src/styleTree/workspace.ts | 471 +-
styles/src/system/lib/convert.ts | 11
styles/src/system/lib/curve.ts | 26
styles/src/system/lib/generate.ts | 159
styles/src/system/ref/color.ts | 445 ++
styles/src/system/ref/curves.ts | 25
styles/src/system/system.ts | 32
styles/src/system/types.ts | 66
styles/src/themes/andromeda.ts | 72
styles/src/themes/atelier-cave.ts | 112
styles/src/themes/atelier-sulphurpool.ts | 73
styles/src/themes/common/base16.ts | 296 -
styles/src/themes/common/colorScheme.ts | 158
styles/src/themes/common/ramps.ts | 359
styles/src/themes/common/theme.ts | 165
styles/src/themes/one-dark.ts | 97
styles/src/themes/one-light.ts | 107
styles/src/themes/rose-pine-dawn.ts | 72
styles/src/themes/rose-pine-moon.ts | 72
styles/src/themes/rose-pine.ts | 68
styles/src/themes/sandcastle.ts | 67
styles/src/themes/solarized.ts | 73
styles/src/themes/staff/abruzzo.ts | 52
styles/src/themes/staff/atelier-dune.ts | 59
styles/src/themes/staff/atelier-heath.ts | 93
styles/src/themes/staff/atelier-seaside.ts | 59
styles/src/themes/staff/ayu-mirage.ts | 52
styles/src/themes/staff/ayu.ts | 90
styles/src/themes/staff/brushtrees.ts | 128
styles/src/themes/staff/dracula.ts | 54
styles/src/themes/staff/gruvbox-medium.ts | 264
styles/src/themes/staff/monokai.ts | 54
styles/src/themes/staff/nord.ts | 54
styles/src/themes/staff/seti-ui.ts | 54
styles/src/themes/staff/tokyo-night-storm.ts | 54
styles/src/themes/staff/tokyo-night.ts | 92
styles/src/themes/staff/zed-pro.ts | 58
styles/src/themes/staff/zenburn.ts | 54
styles/src/themes/summercamp.ts | 72
styles/src/utils/color.ts | 4
styles/src/utils/snakeCase.ts | 38
styles/tsconfig.json | 20
142 files changed, 6,690 insertions(+), 5,009 deletions(-)
@@ -19,7 +19,9 @@ env:
jobs:
rustfmt:
name: Check formatting
- runs-on: self-hosted
+ runs-on:
+ - self-hosted
+ - test
steps:
- name: Install Rust
run: |
@@ -794,7 +794,7 @@ dependencies = [
[[package]]
name = "bromberg_sl2"
version = "0.6.0"
-source = "git+https://github.com/zed-industries/bromberg_sl2?rev=dac565a90e8f9245f48ff46225c915dc50f76920#dac565a90e8f9245f48ff46225c915dc50f76920"
+source = "git+https://github.com/zed-industries/bromberg_sl2?rev=950bc5482c216c395049ae33ae4501e08975f17f#950bc5482c216c395049ae33ae4501e08975f17f"
dependencies = [
"digest 0.9.0",
"lazy_static",
@@ -1188,7 +1188,7 @@ dependencies = [
[[package]]
name = "collab"
-version = "0.5.4"
+version = "0.6.1"
dependencies = [
"anyhow",
"async-tungstenite",
@@ -1257,6 +1257,7 @@ dependencies = [
"client",
"clock",
"collections",
+ "context_menu",
"editor",
"futures 0.3.25",
"fuzzy",
@@ -8356,7 +8357,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zed"
-version = "0.75.0"
+version = "0.76.0"
dependencies = [
"activity_indicator",
"anyhow",
@@ -0,0 +1,3 @@
+<svg width="14" height="4" viewBox="0 0 14 4" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.125 2C3.125 2.62132 2.62132 3.125 2 3.125C1.37868 3.125 0.875 2.62132 0.875 2C0.875 1.37868 1.37868 0.875 2 0.875C2.62132 0.875 3.125 1.37868 3.125 2ZM8.125 2C8.125 2.62132 7.62132 3.125 7 3.125C6.37868 3.125 5.875 2.62132 5.875 2C5.875 1.37868 6.37868 0.875 7 0.875C7.62132 0.875 8.125 1.37868 8.125 2ZM12 3.125C12.6213 3.125 13.125 2.62132 13.125 2C13.125 1.37868 12.6213 0.875 12 0.875C11.3787 0.875 10.875 1.37868 10.875 2C10.875 2.62132 11.3787 3.125 12 3.125Z" fill="#ABB2BF"/>
+</svg>
@@ -0,0 +1,3 @@
+<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M0 1C0 0.585786 0.335786 0.25 0.75 0.25H7.25C7.66421 0.25 8 0.585786 8 1C8 1.41421 7.66421 1.75 7.25 1.75H1.5V10.25H7.25C7.66421 10.25 8 10.5858 8 11C8 11.4142 7.66421 11.75 7.25 11.75H0.75C0.335786 11.75 0 11.4142 0 11V1ZM8.78148 2.91435C9.10493 2.65559 9.57689 2.70803 9.83565 3.03148L11.8357 5.53148C12.0548 5.80539 12.0548 6.19461 11.8357 6.46852L9.83565 8.96852C9.57689 9.29197 9.10493 9.34441 8.78148 9.08565C8.45803 8.82689 8.40559 8.35493 8.66435 8.03148L9.68953 6.75H3.75C3.33579 6.75 3 6.41421 3 6C3 5.58579 3.33579 5.25 3.75 5.25H9.68953L8.66435 3.96852C8.40559 3.64507 8.45803 3.17311 8.78148 2.91435Z" fill="#ABB2BF"/>
+</svg>
@@ -228,6 +228,7 @@
"replace_newest": true
}
],
+ "cmd-k cmd-i": "editor::Hover",
"cmd-/": [
"editor::ToggleComments",
{
@@ -418,7 +419,7 @@
{
"bindings": {
"ctrl-alt-cmd-f": "workspace::FollowNextCollaborator",
- "cmd-shift-c": "collab::ToggleCollaborationMenu",
+ "cmd-shift-c": "collab::ToggleContactsMenu",
"cmd-alt-i": "zed::DebugElements"
}
},
@@ -456,7 +457,7 @@
}
},
{
- "context": "Dock",
+ "context": "Pane && docked",
"bindings": {
"shift-escape": "dock::HideDock",
"cmd-escape": "dock::RemoveTabFromDock"
@@ -27,6 +27,7 @@
"h": "vim::Left",
"backspace": "vim::Backspace",
"j": "vim::Down",
+ "enter": "vim::NextLineStart",
"k": "vim::Up",
"l": "vim::Right",
"$": "vim::EndOfLine",
@@ -83,7 +83,7 @@
"hard_tabs": false,
// How many columns a tab should occupy.
"tab_size": 4,
- // Control what info Zed sends to our servers
+ // Control what info is collected by Zed.
"telemetry": {
// Send debug info like crash reports.
"diagnostics": true,
@@ -33,6 +33,19 @@ struct LspStatus {
status: LanguageServerBinaryStatus,
}
+struct PendingWork<'a> {
+ language_server_name: &'a str,
+ progress_token: &'a str,
+ progress: &'a LanguageServerProgress,
+}
+
+#[derive(Default)]
+struct Content {
+ icon: Option<&'static str>,
+ message: String,
+ action: Option<Box<dyn Action>>,
+}
+
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ActivityIndicator::show_error_message);
cx.add_action(ActivityIndicator::dismiss_error_message);
@@ -69,6 +82,8 @@ impl ActivityIndicator {
if let Some(auto_updater) = auto_updater.as_ref() {
cx.observe(auto_updater, |_, _, cx| cx.notify()).detach();
}
+ cx.observe_active_labeled_tasks(|_, cx| cx.notify())
+ .detach();
Self {
statuses: Default::default(),
@@ -130,7 +145,7 @@ impl ActivityIndicator {
fn pending_language_server_work<'a>(
&self,
cx: &'a AppContext,
- ) -> impl Iterator<Item = (&'a str, &'a str, &'a LanguageServerProgress)> {
+ ) -> impl Iterator<Item = PendingWork<'a>> {
self.project
.read(cx)
.language_server_statuses()
@@ -142,23 +157,29 @@ impl ActivityIndicator {
let mut pending_work = status
.pending_work
.iter()
- .map(|(token, progress)| (status.name.as_str(), token.as_str(), progress))
+ .map(|(token, progress)| PendingWork {
+ language_server_name: status.name.as_str(),
+ progress_token: token.as_str(),
+ progress,
+ })
.collect::<SmallVec<[_; 4]>>();
- pending_work.sort_by_key(|(_, _, progress)| Reverse(progress.last_update_at));
+ pending_work.sort_by_key(|work| Reverse(work.progress.last_update_at));
Some(pending_work)
}
})
.flatten()
}
- fn content_to_render(
- &mut self,
- cx: &mut RenderContext<Self>,
- ) -> (Option<&'static str>, String, Option<Box<dyn Action>>) {
+ fn content_to_render(&mut self, cx: &mut RenderContext<Self>) -> Content {
// Show any language server has pending activity.
let mut pending_work = self.pending_language_server_work(cx);
- if let Some((lang_server_name, progress_token, progress)) = pending_work.next() {
- let mut message = lang_server_name.to_string();
+ if let Some(PendingWork {
+ language_server_name,
+ progress_token,
+ progress,
+ }) = pending_work.next()
+ {
+ let mut message = language_server_name.to_string();
message.push_str(": ");
if let Some(progress_message) = progress.message.as_ref() {
@@ -176,7 +197,11 @@ impl ActivityIndicator {
write!(&mut message, " + {} more", additional_work_count).unwrap();
}
- return (None, message, None);
+ return Content {
+ icon: None,
+ message,
+ action: None,
+ };
}
// Show any language server installation info.
@@ -199,19 +224,19 @@ impl ActivityIndicator {
}
if !downloading.is_empty() {
- return (
- Some(DOWNLOAD_ICON),
- format!(
+ return Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: format!(
"Downloading {} language server{}...",
downloading.join(", "),
if downloading.len() > 1 { "s" } else { "" }
),
- None,
- );
+ action: None,
+ };
} else if !checking_for_update.is_empty() {
- return (
- Some(DOWNLOAD_ICON),
- format!(
+ return Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: format!(
"Checking for updates to {} language server{}...",
checking_for_update.join(", "),
if checking_for_update.len() > 1 {
@@ -220,53 +245,61 @@ impl ActivityIndicator {
""
}
),
- None,
- );
+ action: None,
+ };
} else if !failed.is_empty() {
- return (
- Some(WARNING_ICON),
- format!(
+ return Content {
+ icon: Some(WARNING_ICON),
+ message: format!(
"Failed to download {} language server{}. Click to show error.",
failed.join(", "),
if failed.len() > 1 { "s" } else { "" }
),
- Some(Box::new(ShowErrorMessage)),
- );
+ action: Some(Box::new(ShowErrorMessage)),
+ };
}
// Show any application auto-update info.
if let Some(updater) = &self.auto_updater {
- match &updater.read(cx).status() {
- AutoUpdateStatus::Checking => (
- Some(DOWNLOAD_ICON),
- "Checking for Zed updatesβ¦".to_string(),
- None,
- ),
- AutoUpdateStatus::Downloading => (
- Some(DOWNLOAD_ICON),
- "Downloading Zed updateβ¦".to_string(),
- None,
- ),
- AutoUpdateStatus::Installing => (
- Some(DOWNLOAD_ICON),
- "Installing Zed updateβ¦".to_string(),
- None,
- ),
- AutoUpdateStatus::Updated => (
- None,
- "Click to restart and update Zed".to_string(),
- Some(Box::new(workspace::Restart)),
- ),
- AutoUpdateStatus::Errored => (
- Some(WARNING_ICON),
- "Auto update failed".to_string(),
- Some(Box::new(DismissErrorMessage)),
- ),
+ return match &updater.read(cx).status() {
+ AutoUpdateStatus::Checking => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Checking for Zed updatesβ¦".to_string(),
+ action: None,
+ },
+ AutoUpdateStatus::Downloading => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Downloading Zed updateβ¦".to_string(),
+ action: None,
+ },
+ AutoUpdateStatus::Installing => Content {
+ icon: Some(DOWNLOAD_ICON),
+ message: "Installing Zed updateβ¦".to_string(),
+ action: None,
+ },
+ AutoUpdateStatus::Updated => Content {
+ icon: None,
+ message: "Click to restart and update Zed".to_string(),
+ action: Some(Box::new(workspace::Restart)),
+ },
+ AutoUpdateStatus::Errored => Content {
+ icon: Some(WARNING_ICON),
+ message: "Auto update failed".to_string(),
+ action: Some(Box::new(DismissErrorMessage)),
+ },
AutoUpdateStatus::Idle => Default::default(),
- }
- } else {
- Default::default()
+ };
}
+
+ if let Some(most_recent_active_task) = cx.active_labeled_tasks().last() {
+ return Content {
+ icon: None,
+ message: most_recent_active_task.to_string(),
+ action: None,
+ };
+ }
+
+ Default::default()
}
}
@@ -280,7 +313,11 @@ impl View for ActivityIndicator {
}
fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
- let (icon, message, action) = self.content_to_render(cx);
+ let Content {
+ icon,
+ message,
+ action,
+ } = self.content_to_render(cx);
let mut element = MouseEventHandler::<Self>::new(0, cx, |state, cx| {
let theme = &cx
@@ -284,6 +284,18 @@ impl ActiveCall {
}
}
+ pub fn unshare_project(
+ &mut self,
+ project: ModelHandle<Project>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ if let Some((room, _)) = self.room.as_ref() {
+ room.update(cx, |room, cx| room.unshare_project(project, cx))
+ } else {
+ Err(anyhow!("no active call"))
+ }
+ }
+
pub fn set_location(
&mut self,
project: Option<&ModelHandle<Project>>,
@@ -55,6 +55,7 @@ pub struct Room {
leave_when_empty: bool,
client: Arc<Client>,
user_store: ModelHandle<UserStore>,
+ follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
subscriptions: Vec<client::Subscription>,
pending_room_update: Option<Task<()>>,
maintain_connection: Option<Task<Option<()>>>,
@@ -148,6 +149,7 @@ impl Room {
pending_room_update: None,
client,
user_store,
+ follows_by_leader_id_project_id: Default::default(),
maintain_connection: Some(maintain_connection),
}
}
@@ -275,14 +277,12 @@ impl Room {
) -> Result<()> {
let mut client_status = client.status();
loop {
- let is_connected = client_status
- .next()
- .await
- .map_or(false, |s| s.is_connected());
-
+ let _ = client_status.try_recv();
+ let is_connected = client_status.borrow().is_connected();
// Even if we're initially connected, any future change of the status means we momentarily disconnected.
if !is_connected || client_status.next().await.is_some() {
log::info!("detected client disconnection");
+
this.upgrade(&cx)
.ok_or_else(|| anyhow!("room was dropped"))?
.update(&mut cx, |this, cx| {
@@ -296,12 +296,7 @@ impl Room {
let client_reconnection = async {
let mut remaining_attempts = 3;
while remaining_attempts > 0 {
- log::info!(
- "waiting for client status change, remaining attempts {}",
- remaining_attempts
- );
- let Some(status) = client_status.next().await else { break };
- if status.is_connected() {
+ if client_status.borrow().is_connected() {
log::info!("client reconnected, attempting to rejoin room");
let Some(this) = this.upgrade(&cx) else { break };
@@ -315,7 +310,15 @@ impl Room {
} else {
remaining_attempts -= 1;
}
+ } else if client_status.borrow().is_signed_out() {
+ return false;
}
+
+ log::info!(
+ "waiting for client status change, remaining attempts {}",
+ remaining_attempts
+ );
+ client_status.next().await;
}
false
}
@@ -337,18 +340,20 @@ impl Room {
}
}
- // The client failed to re-establish a connection to the server
- // or an error occurred while trying to re-join the room. Either way
- // we leave the room and return an error.
- if let Some(this) = this.upgrade(&cx) {
- log::info!("reconnection failed, leaving room");
- let _ = this.update(&mut cx, |this, cx| this.leave(cx));
- }
- return Err(anyhow!(
- "can't reconnect to room: client failed to re-establish connection"
- ));
+ break;
}
}
+
+ // The client failed to re-establish a connection to the server
+ // or an error occurred while trying to re-join the room. Either way
+ // we leave the room and return an error.
+ if let Some(this) = this.upgrade(&cx) {
+ log::info!("reconnection failed, leaving room");
+ let _ = this.update(&mut cx, |this, cx| this.leave(cx));
+ }
+ Err(anyhow!(
+ "can't reconnect to room: client failed to re-establish connection"
+ ))
}
fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
@@ -457,6 +462,12 @@ impl Room {
self.participant_user_ids.contains(&user_id)
}
+ pub fn followers_for(&self, leader_id: PeerId, project_id: u64) -> &[PeerId] {
+ self.follows_by_leader_id_project_id
+ .get(&(leader_id, project_id))
+ .map_or(&[], |v| v.as_slice())
+ }
+
async fn handle_room_updated(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::RoomUpdated>,
@@ -487,11 +498,13 @@ impl Room {
.iter()
.map(|p| p.user_id)
.collect::<Vec<_>>();
+
let remote_participant_user_ids = room
.participants
.iter()
.map(|p| p.user_id)
.collect::<Vec<_>>();
+
let (remote_participants, pending_participants) =
self.user_store.update(cx, move |user_store, cx| {
(
@@ -499,6 +512,7 @@ impl Room {
user_store.get_users(pending_participant_user_ids, cx),
)
});
+
self.pending_room_update = Some(cx.spawn(|this, mut cx| async move {
let (remote_participants, pending_participants) =
futures::join!(remote_participants, pending_participants);
@@ -620,6 +634,27 @@ impl Room {
}
}
+ this.follows_by_leader_id_project_id.clear();
+ for follower in room.followers {
+ let project_id = follower.project_id;
+ let (leader, follower) = match (follower.leader_id, follower.follower_id) {
+ (Some(leader), Some(follower)) => (leader, follower),
+
+ _ => {
+ log::error!("Follower message {follower:?} missing some state");
+ continue;
+ }
+ };
+
+ let list = this
+ .follows_by_leader_id_project_id
+ .entry((leader, project_id))
+ .or_insert(Vec::new());
+ if !list.contains(&follower) {
+ list.push(follower);
+ }
+ }
+
this.pending_room_update.take();
if this.should_leave() {
log::info!("room is empty, leaving");
@@ -793,6 +828,20 @@ impl Room {
})
}
+ pub(crate) fn unshare_project(
+ &mut self,
+ project: ModelHandle<Project>,
+ cx: &mut ModelContext<Self>,
+ ) -> Result<()> {
+ let project_id = match project.read(cx).remote_id() {
+ Some(project_id) => project_id,
+ None => return Ok(()),
+ };
+
+ self.client.send(proto::UnshareProject { project_id })?;
+ project.update(cx, |this, cx| this.unshare(cx))
+ }
+
pub(crate) fn set_location(
&mut self,
project: Option<&ModelHandle<Project>>,
@@ -66,7 +66,7 @@ pub const ZED_SECRET_CLIENT_TOKEN: &str = "618033988749894";
pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100);
pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5);
-actions!(client, [Authenticate]);
+actions!(client, [Authenticate, SignOut]);
pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
cx.add_global_action({
@@ -79,6 +79,16 @@ pub fn init(client: Arc<Client>, cx: &mut MutableAppContext) {
.detach();
}
});
+ cx.add_global_action({
+ let client = client.clone();
+ move |_: &SignOut, cx| {
+ let client = client.clone();
+ cx.spawn(|cx| async move {
+ client.disconnect(&cx);
+ })
+ .detach();
+ }
+ });
}
pub struct Client {
@@ -169,6 +179,10 @@ impl Status {
pub fn is_connected(&self) -> bool {
matches!(self, Self::Connected { .. })
}
+
+ pub fn is_signed_out(&self) -> bool {
+ matches!(self, Self::SignedOut | Self::UpgradeRequired)
+ }
}
struct ClientState {
@@ -1152,11 +1166,9 @@ impl Client {
})
}
- pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) -> Result<()> {
- let conn_id = self.connection_id()?;
- self.peer.disconnect(conn_id);
+ pub fn disconnect(self: &Arc<Self>, cx: &AsyncAppContext) {
+ self.peer.teardown();
self.set_status(Status::SignedOut, cx);
- Ok(())
}
fn connection_id(&self) -> Result<ConnectionId> {
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
default-run = "collab"
edition = "2021"
name = "collab"
-version = "0.5.4"
+version = "0.6.1"
publish = false
[[bin]]
@@ -143,3 +143,17 @@ CREATE TABLE "servers" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"environment" VARCHAR NOT NULL
);
+
+CREATE TABLE "followers" (
+ "id" INTEGER PRIMARY KEY AUTOINCREMENT,
+ "room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE,
+ "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
+ "leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
+ "leader_connection_id" INTEGER NOT NULL,
+ "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
+ "follower_connection_id" INTEGER NOT NULL
+);
+CREATE UNIQUE INDEX
+ "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
+ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
+CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
@@ -0,0 +1,15 @@
+CREATE TABLE IF NOT EXISTS "followers" (
+ "id" SERIAL PRIMARY KEY,
+ "room_id" INTEGER NOT NULL REFERENCES rooms (id) ON DELETE CASCADE,
+ "project_id" INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
+ "leader_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
+ "leader_connection_id" INTEGER NOT NULL,
+ "follower_connection_server_id" INTEGER NOT NULL REFERENCES servers (id) ON DELETE CASCADE,
+ "follower_connection_id" INTEGER NOT NULL
+);
+
+CREATE UNIQUE INDEX
+ "index_followers_on_project_id_and_leader_connection_server_id_and_leader_connection_id_and_follower_connection_server_id_and_follower_connection_id"
+ON "followers" ("project_id", "leader_connection_server_id", "leader_connection_id", "follower_connection_server_id", "follower_connection_id");
+
+CREATE INDEX "index_followers_on_room_id" ON "followers" ("room_id");
@@ -1,5 +1,6 @@
mod access_token;
mod contact;
+mod follower;
mod language_server;
mod project;
mod project_collaborator;
@@ -157,7 +158,7 @@ impl Database {
room_id: RoomId,
new_server_id: ServerId,
) -> Result<RoomGuard<RefreshedRoom>> {
- self.room_transaction(|tx| async move {
+ self.room_transaction(room_id, |tx| async move {
let stale_participant_filter = Condition::all()
.add(room_participant::Column::RoomId.eq(room_id))
.add(room_participant::Column::AnsweringConnectionId.is_not_null())
@@ -190,17 +191,18 @@ impl Database {
.filter(room_participant::Column::RoomId.eq(room_id))
.exec(&*tx)
.await?;
+ project::Entity::delete_many()
+ .filter(project::Column::RoomId.eq(room_id))
+ .exec(&*tx)
+ .await?;
room::Entity::delete_by_id(room_id).exec(&*tx).await?;
}
- Ok((
- room_id,
- RefreshedRoom {
- room,
- stale_participant_user_ids,
- canceled_calls_to_user_ids,
- },
- ))
+ Ok(RefreshedRoom {
+ room,
+ stale_participant_user_ids,
+ canceled_calls_to_user_ids,
+ })
})
.await
}
@@ -1129,18 +1131,16 @@ impl Database {
user_id: UserId,
connection: ConnectionId,
live_kit_room: &str,
- ) -> Result<RoomGuard<proto::Room>> {
- self.room_transaction(|tx| async move {
+ ) -> Result<proto::Room> {
+ self.transaction(|tx| async move {
let room = room::ActiveModel {
live_kit_room: ActiveValue::set(live_kit_room.into()),
..Default::default()
}
.insert(&*tx)
.await?;
- let room_id = room.id;
-
room_participant::ActiveModel {
- room_id: ActiveValue::set(room_id),
+ room_id: ActiveValue::set(room.id),
user_id: ActiveValue::set(user_id),
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
answering_connection_server_id: ActiveValue::set(Some(ServerId(
@@ -1157,8 +1157,8 @@ impl Database {
.insert(&*tx)
.await?;
- let room = self.get_room(room_id, &tx).await?;
- Ok((room_id, room))
+ let room = self.get_room(room.id, &tx).await?;
+ Ok(room)
})
.await
}
@@ -1171,7 +1171,7 @@ impl Database {
called_user_id: UserId,
initial_project_id: Option<ProjectId>,
) -> Result<RoomGuard<(proto::Room, proto::IncomingCall)>> {
- self.room_transaction(|tx| async move {
+ self.room_transaction(room_id, |tx| async move {
room_participant::ActiveModel {
room_id: ActiveValue::set(room_id),
user_id: ActiveValue::set(called_user_id),
@@ -1190,7 +1190,7 @@ impl Database {
let room = self.get_room(room_id, &tx).await?;
let incoming_call = Self::build_incoming_call(&room, called_user_id)
.ok_or_else(|| anyhow!("failed to build incoming call"))?;
- Ok((room_id, (room, incoming_call)))
+ Ok((room, incoming_call))
})
.await
}
@@ -1200,7 +1200,7 @@ impl Database {
room_id: RoomId,
called_user_id: UserId,
) -> Result<RoomGuard<proto::Room>> {
- self.room_transaction(|tx| async move {
+ self.room_transaction(room_id, |tx| async move {
room_participant::Entity::delete_many()
.filter(
room_participant::Column::RoomId
@@ -1210,7 +1210,7 @@ impl Database {
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
- Ok((room_id, room))
+ Ok(room)
})
.await
}
@@ -1257,7 +1257,7 @@ impl Database {
calling_connection: ConnectionId,
called_user_id: UserId,
) -> Result<RoomGuard<proto::Room>> {
- self.room_transaction(|tx| async move {
+ self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
@@ -1276,14 +1276,13 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no call to cancel"))?;
- let room_id = participant.room_id;
room_participant::Entity::delete(participant.into_active_model())
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
- Ok((room_id, room))
+ Ok(room)
})
.await
}
@@ -1294,7 +1293,7 @@ impl Database {
user_id: UserId,
connection: ConnectionId,
) -> Result<RoomGuard<proto::Room>> {
- self.room_transaction(|tx| async move {
+ self.room_transaction(room_id, |tx| async move {
let result = room_participant::Entity::update_many()
.filter(
Condition::all()
@@ -1316,7 +1315,7 @@ impl Database {
Err(anyhow!("room does not exist or was already joined"))?
} else {
let room = self.get_room(room_id, &tx).await?;
- Ok((room_id, room))
+ Ok(room)
}
})
.await
@@ -1328,9 +1327,9 @@ impl Database {
user_id: UserId,
connection: ConnectionId,
) -> Result<RoomGuard<RejoinedRoom>> {
- self.room_transaction(|tx| async {
+ let room_id = RoomId::from_proto(rejoin_room.id);
+ self.room_transaction(room_id, |tx| async {
let tx = tx;
- let room_id = RoomId::from_proto(rejoin_room.id);
let participant_update = room_participant::Entity::update_many()
.filter(
Condition::all()
@@ -1549,14 +1548,11 @@ impl Database {
}
let room = self.get_room(room_id, &tx).await?;
- Ok((
- room_id,
- RejoinedRoom {
- room,
- rejoined_projects,
- reshared_projects,
- },
- ))
+ Ok(RejoinedRoom {
+ room,
+ rejoined_projects,
+ reshared_projects,
+ })
})
.await
}
@@ -1717,13 +1713,78 @@ impl Database {
.await
}
+ pub async fn follow(
+ &self,
+ project_id: ProjectId,
+ leader_connection: ConnectionId,
+ follower_connection: ConnectionId,
+ ) -> Result<RoomGuard<proto::Room>> {
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
+ follower::ActiveModel {
+ room_id: ActiveValue::set(room_id),
+ project_id: ActiveValue::set(project_id),
+ leader_connection_server_id: ActiveValue::set(ServerId(
+ leader_connection.owner_id as i32,
+ )),
+ leader_connection_id: ActiveValue::set(leader_connection.id as i32),
+ follower_connection_server_id: ActiveValue::set(ServerId(
+ follower_connection.owner_id as i32,
+ )),
+ follower_connection_id: ActiveValue::set(follower_connection.id as i32),
+ ..Default::default()
+ }
+ .insert(&*tx)
+ .await?;
+
+ let room = self.get_room(room_id, &*tx).await?;
+ Ok(room)
+ })
+ .await
+ }
+
+ pub async fn unfollow(
+ &self,
+ project_id: ProjectId,
+ leader_connection: ConnectionId,
+ follower_connection: ConnectionId,
+ ) -> Result<RoomGuard<proto::Room>> {
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
+ follower::Entity::delete_many()
+ .filter(
+ Condition::all()
+ .add(follower::Column::ProjectId.eq(project_id))
+ .add(
+ follower::Column::LeaderConnectionServerId
+ .eq(leader_connection.owner_id)
+ .and(follower::Column::LeaderConnectionId.eq(leader_connection.id)),
+ )
+ .add(
+ follower::Column::FollowerConnectionServerId
+ .eq(follower_connection.owner_id)
+ .and(
+ follower::Column::FollowerConnectionId
+ .eq(follower_connection.id),
+ ),
+ ),
+ )
+ .exec(&*tx)
+ .await?;
+
+ let room = self.get_room(room_id, &*tx).await?;
+ Ok(room)
+ })
+ .await
+ }
+
pub async fn update_room_participant_location(
&self,
room_id: RoomId,
connection: ConnectionId,
location: proto::ParticipantLocation,
) -> Result<RoomGuard<proto::Room>> {
- self.room_transaction(|tx| async {
+ self.room_transaction(room_id, |tx| async {
let tx = tx;
let location_kind;
let location_project_id;
@@ -1769,7 +1830,7 @@ impl Database {
if result.rows_affected == 1 {
let room = self.get_room(room_id, &tx).await?;
- Ok((room_id, room))
+ Ok(room)
} else {
Err(anyhow!("could not update room participant location"))?
}
@@ -1926,12 +1987,25 @@ impl Database {
}
}
}
+ drop(db_projects);
+
+ let mut db_followers = db_room.find_related(follower::Entity).stream(tx).await?;
+ let mut followers = Vec::new();
+ while let Some(db_follower) = db_followers.next().await {
+ let db_follower = db_follower?;
+ followers.push(proto::Follower {
+ leader_id: Some(db_follower.leader_connection().into()),
+ follower_id: Some(db_follower.follower_connection().into()),
+ project_id: db_follower.project_id.to_proto(),
+ });
+ }
Ok(proto::Room {
id: db_room.id.to_proto(),
live_kit_room: db_room.live_kit_room,
participants: participants.into_values().collect(),
pending_participants,
+ followers,
})
}
@@ -1963,7 +2037,7 @@ impl Database {
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(ProjectId, proto::Room)>> {
- self.room_transaction(|tx| async move {
+ self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
@@ -2024,7 +2098,7 @@ impl Database {
.await?;
let room = self.get_room(room_id, &tx).await?;
- Ok((room_id, (project.id, room)))
+ Ok((project.id, room))
})
.await
}
@@ -2034,7 +2108,8 @@ impl Database {
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
- self.room_transaction(|tx| async move {
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
let project = project::Entity::find_by_id(project_id)
@@ -2042,12 +2117,11 @@ impl Database {
.await?
.ok_or_else(|| anyhow!("project not found"))?;
if project.host_connection()? == connection {
- let room_id = project.room_id;
project::Entity::delete(project.into_active_model())
.exec(&*tx)
.await?;
let room = self.get_room(room_id, &tx).await?;
- Ok((room_id, (room, guest_connection_ids)))
+ Ok((room, guest_connection_ids))
} else {
Err(anyhow!("cannot unshare a project hosted by another user"))?
}
@@ -2061,7 +2135,8 @@ impl Database {
connection: ConnectionId,
worktrees: &[proto::WorktreeMetadata],
) -> Result<RoomGuard<(proto::Room, Vec<ConnectionId>)>> {
- self.room_transaction(|tx| async move {
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
let project = project::Entity::find_by_id(project_id)
.filter(
Condition::all()
@@ -2079,7 +2154,7 @@ impl Database {
let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?;
let room = self.get_room(project.room_id, &tx).await?;
- Ok((project.room_id, (room, guest_connection_ids)))
+ Ok((room, guest_connection_ids))
})
.await
}
@@ -2124,12 +2199,12 @@ impl Database {
update: &proto::UpdateWorktree,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
- self.room_transaction(|tx| async move {
- let project_id = ProjectId::from_proto(update.project_id);
- let worktree_id = update.worktree_id as i64;
-
+ let project_id = ProjectId::from_proto(update.project_id);
+ let worktree_id = update.worktree_id as i64;
+ 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)
+ let _project = project::Entity::find_by_id(project_id)
.filter(
Condition::all()
.add(project::Column::HostConnectionId.eq(connection.id as i32))
@@ -2140,7 +2215,6 @@ impl Database {
.one(&*tx)
.await?
.ok_or_else(|| anyhow!("no such project"))?;
- let room_id = project.room_id;
// Update metadata.
worktree::Entity::update(worktree::ActiveModel {
@@ -2220,7 +2294,7 @@ impl Database {
}
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
- Ok((room_id, connection_ids))
+ Ok(connection_ids)
})
.await
}
@@ -2230,9 +2304,10 @@ impl Database {
update: &proto::UpdateDiagnosticSummary,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
- self.room_transaction(|tx| async move {
- let project_id = ProjectId::from_proto(update.project_id);
- let worktree_id = update.worktree_id as i64;
+ let project_id = ProjectId::from_proto(update.project_id);
+ let worktree_id = update.worktree_id as i64;
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
let summary = update
.summary
.as_ref()
@@ -2274,7 +2349,7 @@ impl Database {
.await?;
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
- Ok((project.room_id, connection_ids))
+ Ok(connection_ids)
})
.await
}
@@ -2284,8 +2359,9 @@ impl Database {
update: &proto::StartLanguageServer,
connection: ConnectionId,
) -> Result<RoomGuard<Vec<ConnectionId>>> {
- self.room_transaction(|tx| async move {
- let project_id = ProjectId::from_proto(update.project_id);
+ 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 {
let server = update
.server
.as_ref()
@@ -2319,7 +2395,7 @@ impl Database {
.await?;
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
- Ok((project.room_id, connection_ids))
+ Ok(connection_ids)
})
.await
}
@@ -2329,7 +2405,8 @@ impl Database {
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<(Project, ReplicaId)>> {
- self.room_transaction(|tx| async move {
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
let participant = room_participant::Entity::find()
.filter(
Condition::all()
@@ -2455,7 +2532,6 @@ impl Database {
.all(&*tx)
.await?;
- let room_id = project.room_id;
let project = Project {
collaborators: collaborators
.into_iter()
@@ -2475,7 +2551,7 @@ impl Database {
})
.collect(),
};
- Ok((room_id, (project, replica_id as ReplicaId)))
+ Ok((project, replica_id as ReplicaId))
})
.await
}
@@ -2485,7 +2561,8 @@ impl Database {
project_id: ProjectId,
connection: ConnectionId,
) -> Result<RoomGuard<LeftProject>> {
- self.room_transaction(|tx| async move {
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
let result = project_collaborator::Entity::delete_many()
.filter(
Condition::all()
@@ -2521,7 +2598,7 @@ impl Database {
host_connection_id: project.host_connection()?,
connection_ids,
};
- Ok((project.room_id, left_project))
+ Ok(left_project)
})
.await
}
@@ -2531,11 +2608,8 @@ impl Database {
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<RoomGuard<Vec<ProjectCollaborator>>> {
- self.room_transaction(|tx| async move {
- let project = project::Entity::find_by_id(project_id)
- .one(&*tx)
- .await?
- .ok_or_else(|| anyhow!("no such project"))?;
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
let collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.all(&*tx)
@@ -2553,7 +2627,7 @@ impl Database {
.iter()
.any(|collaborator| collaborator.connection_id == connection_id)
{
- Ok((project.room_id, collaborators))
+ Ok(collaborators)
} else {
Err(anyhow!("no such project"))?
}
@@ -2566,11 +2640,8 @@ impl Database {
project_id: ProjectId,
connection_id: ConnectionId,
) -> Result<RoomGuard<HashSet<ConnectionId>>> {
- self.room_transaction(|tx| async move {
- let project = project::Entity::find_by_id(project_id)
- .one(&*tx)
- .await?
- .ok_or_else(|| anyhow!("no such project"))?;
+ let room_id = self.room_id_for_project(project_id).await?;
+ self.room_transaction(room_id, |tx| async move {
let mut collaborators = project_collaborator::Entity::find()
.filter(project_collaborator::Column::ProjectId.eq(project_id))
.stream(&*tx)
@@ -2583,7 +2654,7 @@ impl Database {
}
if connection_ids.contains(&connection_id) {
- Ok((project.room_id, connection_ids))
+ Ok(connection_ids)
} else {
Err(anyhow!("no such project"))?
}
@@ -2613,6 +2684,17 @@ impl Database {
Ok(guest_connection_ids)
}
+ async fn room_id_for_project(&self, project_id: ProjectId) -> Result<RoomId> {
+ self.transaction(|tx| async move {
+ let project = project::Entity::find_by_id(project_id)
+ .one(&*tx)
+ .await?
+ .ok_or_else(|| anyhow!("project {} not found", project_id))?;
+ Ok(project.room_id)
+ })
+ .await
+ }
+
// access tokens
pub async fn create_access_token_hash(
@@ -2763,21 +2845,48 @@ impl Database {
self.run(body).await
}
- async fn room_transaction<F, Fut, T>(&self, f: F) -> Result<RoomGuard<T>>
+ async fn room_transaction<F, Fut, T>(&self, room_id: RoomId, f: F) -> Result<RoomGuard<T>>
where
F: Send + Fn(TransactionHandle) -> Fut,
- Fut: Send + Future<Output = Result<(RoomId, T)>>,
+ Fut: Send + Future<Output = Result<T>>,
{
- let data = self
- .optional_room_transaction(move |tx| {
- let future = f(tx);
- async {
- let data = future.await?;
- Ok(Some(data))
+ let body = async {
+ loop {
+ let lock = self.rooms.entry(room_id).or_default().clone();
+ let _guard = lock.lock_owned().await;
+ let (tx, result) = self.with_transaction(&f).await?;
+ match result {
+ Ok(data) => {
+ match tx.commit().await.map_err(Into::into) {
+ Ok(()) => {
+ return Ok(RoomGuard {
+ data,
+ _guard,
+ _not_send: PhantomData,
+ });
+ }
+ Err(error) => {
+ if is_serialization_error(&error) {
+ // Retry (don't break the loop)
+ } else {
+ return Err(error);
+ }
+ }
+ }
+ }
+ Err(error) => {
+ tx.rollback().await?;
+ if is_serialization_error(&error) {
+ // Retry (don't break the loop)
+ } else {
+ return Err(error);
+ }
+ }
}
- })
- .await?;
- Ok(data.unwrap())
+ }
+ };
+
+ self.run(body).await
}
async fn with_transaction<F, Fut, T>(&self, f: &F) -> Result<(DatabaseTransaction, Result<T>)>
@@ -3011,6 +3120,7 @@ macro_rules! id_type {
id_type!(AccessTokenId);
id_type!(ContactId);
+id_type!(FollowerId);
id_type!(RoomId);
id_type!(RoomParticipantId);
id_type!(ProjectId);
@@ -0,0 +1,51 @@
+use super::{FollowerId, ProjectId, RoomId, ServerId};
+use rpc::ConnectionId;
+use sea_orm::entity::prelude::*;
+use serde::Serialize;
+
+#[derive(Clone, Debug, Default, PartialEq, Eq, DeriveEntityModel, Serialize)]
+#[sea_orm(table_name = "followers")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: FollowerId,
+ pub room_id: RoomId,
+ pub project_id: ProjectId,
+ pub leader_connection_server_id: ServerId,
+ pub leader_connection_id: i32,
+ pub follower_connection_server_id: ServerId,
+ pub follower_connection_id: i32,
+}
+
+impl Model {
+ pub fn leader_connection(&self) -> ConnectionId {
+ ConnectionId {
+ owner_id: self.leader_connection_server_id.0 as u32,
+ id: self.leader_connection_id as u32,
+ }
+ }
+
+ pub fn follower_connection(&self) -> ConnectionId {
+ ConnectionId {
+ owner_id: self.follower_connection_server_id.0 as u32,
+ id: self.follower_connection_id as u32,
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+ #[sea_orm(
+ belongs_to = "super::room::Entity",
+ from = "Column::RoomId",
+ to = "super::room::Column::Id"
+ )]
+ Room,
+}
+
+impl Related<super::room::Entity> for Entity {
+ fn to() -> RelationDef {
+ Relation::Room.def()
+ }
+}
+
+impl ActiveModelBehavior for ActiveModel {}
@@ -15,6 +15,8 @@ pub enum Relation {
RoomParticipant,
#[sea_orm(has_many = "super::project::Entity")]
Project,
+ #[sea_orm(has_many = "super::follower::Entity")]
+ Follower,
}
impl Related<super::room_participant::Entity> for Entity {
@@ -29,4 +31,10 @@ impl Related<super::project::Entity> for Entity {
}
}
+impl Related<super::follower::Entity> for Entity {
+ fn to() -> RelationDef {
+ Relation::Follower.def()
+ }
+}
+
impl ActiveModelBehavior for ActiveModel {}
@@ -270,8 +270,11 @@ impl Server {
let mut live_kit_room = String::new();
let mut delete_live_kit_room = false;
- if let Ok(mut refreshed_room) =
- app_state.db.refresh_room(room_id, server_id).await
+ if let Some(mut refreshed_room) = app_state
+ .db
+ .refresh_room(room_id, server_id)
+ .await
+ .trace_err()
{
tracing::info!(
room_id = room_id.0,
@@ -1312,6 +1315,7 @@ async fn join_project(
.filter(|collaborator| collaborator.connection_id != session.connection_id)
.map(|collaborator| collaborator.to_proto())
.collect::<Vec<_>>();
+
let worktrees = project
.worktrees
.iter()
@@ -1724,6 +1728,7 @@ async fn follow(
.ok_or_else(|| anyhow!("invalid leader id"))?
.into();
let follower_id = session.connection_id;
+
{
let project_connection_ids = session
.db()
@@ -1744,6 +1749,14 @@ async fn follow(
.views
.retain(|view| view.leader_id != Some(follower_id.into()));
response.send(response_payload)?;
+
+ let room = session
+ .db()
+ .await
+ .follow(project_id, leader_id, follower_id)
+ .await?;
+ room_updated(&room, &session.peer);
+
Ok(())
}
@@ -1753,17 +1766,29 @@ async fn unfollow(request: proto::Unfollow, session: Session) -> Result<()> {
.leader_id
.ok_or_else(|| anyhow!("invalid leader id"))?
.into();
- let project_connection_ids = session
+ let follower_id = session.connection_id;
+
+ if !session
.db()
.await
.project_connection_ids(project_id, session.connection_id)
- .await?;
- if !project_connection_ids.contains(&leader_id) {
+ .await?
+ .contains(&leader_id)
+ {
Err(anyhow!("no such peer"))?;
}
+
session
.peer
.forward_send(session.connection_id, leader_id, request)?;
+
+ let room = session
+ .db()
+ .await
+ .unfollow(project_id, leader_id, follower_id)
+ .await?;
+ room_updated(&room, &session.peer);
+
Ok(())
}
@@ -733,6 +733,14 @@ async fn test_server_restarts(
deterministic.forbid_parking();
let mut server = TestServer::start(&deterministic).await;
let client_a = server.create_client(cx_a, "user_a").await;
+ client_a
+ .fs
+ .insert_tree("/a", json!({ "a.txt": "a-contents" }))
+ .await;
+
+ // Invite client B to collaborate on a project
+ let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
+
let client_b = server.create_client(cx_b, "user_b").await;
let client_c = server.create_client(cx_c, "user_c").await;
let client_d = server.create_client(cx_d, "user_d").await;
@@ -753,19 +761,19 @@ async fn test_server_restarts(
// User A calls users B, C, and D.
active_call_a
.update(cx_a, |call, cx| {
- call.invite(client_b.user_id().unwrap(), None, cx)
+ call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
})
.await
.unwrap();
active_call_a
.update(cx_a, |call, cx| {
- call.invite(client_c.user_id().unwrap(), None, cx)
+ call.invite(client_c.user_id().unwrap(), Some(project_a.clone()), cx)
})
.await
.unwrap();
active_call_a
.update(cx_a, |call, cx| {
- call.invite(client_d.user_id().unwrap(), None, cx)
+ call.invite(client_d.user_id().unwrap(), Some(project_a.clone()), cx)
})
.await
.unwrap();
@@ -1083,7 +1091,7 @@ async fn test_calls_on_multiple_connections(
assert!(incoming_call_b2.next().await.unwrap().is_none());
// User B disconnects the client that is not on the call. Everything should be fine.
- client_b1.disconnect(&cx_b1.to_async()).unwrap();
+ client_b1.disconnect(&cx_b1.to_async());
deterministic.advance_clock(RECEIVE_TIMEOUT);
client_b1
.authenticate_and_connect(false, &cx_b1.to_async())
@@ -3227,7 +3235,7 @@ async fn test_leaving_project(
buffer_b2.read_with(cx_b, |buffer, _| assert_eq!(buffer.text(), "a-contents"));
// Drop client B's connection and ensure client A and client C observe client B leaving.
- client_b.disconnect(&cx_b.to_async()).unwrap();
+ client_b.disconnect(&cx_b.to_async());
deterministic.advance_clock(RECONNECT_TIMEOUT);
project_a.read_with(cx_a, |project, _| {
assert_eq!(project.collaborators().len(), 1);
@@ -5772,7 +5780,7 @@ async fn test_contact_requests(
.is_empty());
async fn disconnect_and_reconnect(client: &TestClient, cx: &mut TestAppContext) {
- client.disconnect(&cx.to_async()).unwrap();
+ client.disconnect(&cx.to_async());
client.clear_contacts(cx).await;
client
.authenticate_and_connect(false, &cx.to_async())
@@ -5786,6 +5794,7 @@ async fn test_following(
deterministic: Arc<Deterministic>,
cx_a: &mut TestAppContext,
cx_b: &mut TestAppContext,
+ cx_c: &mut TestAppContext,
) {
deterministic.forbid_parking();
cx_a.update(editor::init);
@@ -5794,9 +5803,13 @@ async fn test_following(
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;
+ let client_c = server.create_client(cx_c, "user_c").await;
server
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
.await;
+ server
+ .make_contacts(&mut [(&client_a, cx_a), (&client_c, cx_c)])
+ .await;
let active_call_a = cx_a.read(ActiveCall::global);
let active_call_b = cx_b.read(ActiveCall::global);
@@ -5827,8 +5840,10 @@ async fn test_following(
.await
.unwrap();
- // Client A opens some editors.
let workspace_a = client_a.build_workspace(&project_a, cx_a);
+ let workspace_b = client_b.build_workspace(&project_b, cx_b);
+
+ // Client A opens some editors.
let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
let editor_a1 = workspace_a
.update(cx_a, |workspace, cx| {
@@ -5848,7 +5863,6 @@ async fn test_following(
.unwrap();
// Client B opens an editor.
- let workspace_b = client_b.build_workspace(&project_b, cx_b);
let editor_b1 = workspace_b
.update(cx_b, |workspace, cx| {
workspace.open_path((worktree_id, "1.txt"), None, true, cx)
@@ -5858,29 +5872,97 @@ async fn test_following(
.downcast::<Editor>()
.unwrap();
- let client_a_id = project_b.read_with(cx_b, |project, _| {
- project.collaborators().values().next().unwrap().peer_id
- });
- let client_b_id = project_a.read_with(cx_a, |project, _| {
- project.collaborators().values().next().unwrap().peer_id
- });
+ let peer_id_a = client_a.peer_id().unwrap();
+ let peer_id_b = client_b.peer_id().unwrap();
+ let peer_id_c = client_c.peer_id().unwrap();
- // When client B starts following client A, all visible view states are replicated to client B.
+ // Client A updates their selections in those editors
editor_a1.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([0..1]))
});
editor_a2.update(cx_a, |editor, cx| {
editor.change_selections(None, cx, |s| s.select_ranges([2..3]))
});
+
+ // When client B starts following client A, all visible view states are replicated to client B.
workspace_b
.update(cx_b, |workspace, cx| {
workspace
- .toggle_follow(&ToggleFollow(client_a_id), cx)
+ .toggle_follow(&ToggleFollow(peer_id_a), cx)
.unwrap()
})
.await
.unwrap();
+ // Client A invites client C to the call.
+ active_call_a
+ .update(cx_a, |call, cx| {
+ call.invite(client_c.current_user_id(cx_c).to_proto(), None, cx)
+ })
+ .await
+ .unwrap();
+ cx_c.foreground().run_until_parked();
+ let active_call_c = cx_c.read(ActiveCall::global);
+ active_call_c
+ .update(cx_c, |call, cx| call.accept_incoming(cx))
+ .await
+ .unwrap();
+ let project_c = client_c.build_remote_project(project_id, cx_c).await;
+ let workspace_c = client_c.build_workspace(&project_c, cx_c);
+ active_call_c
+ .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
+ .await
+ .unwrap();
+
+ // Client C also follows client A.
+ workspace_c
+ .update(cx_c, |workspace, cx| {
+ workspace
+ .toggle_follow(&ToggleFollow(peer_id_a), cx)
+ .unwrap()
+ })
+ .await
+ .unwrap();
+
+ // All clients see that clients B and C are following client A.
+ cx_c.foreground().run_until_parked();
+ for (name, active_call, cx) in [
+ ("A", &active_call_a, &cx_a),
+ ("B", &active_call_b, &cx_b),
+ ("C", &active_call_c, &cx_c),
+ ] {
+ active_call.read_with(*cx, |call, cx| {
+ let room = call.room().unwrap().read(cx);
+ assert_eq!(
+ room.followers_for(peer_id_a, project_id),
+ &[peer_id_b, peer_id_c],
+ "checking followers for A as {name}"
+ );
+ });
+ }
+
+ // Client C unfollows client A.
+ workspace_c.update(cx_c, |workspace, cx| {
+ workspace.toggle_follow(&ToggleFollow(peer_id_a), cx);
+ });
+
+ // All clients see that clients B is following client A.
+ cx_c.foreground().run_until_parked();
+ for (name, active_call, cx) in [
+ ("A", &active_call_a, &cx_a),
+ ("B", &active_call_b, &cx_b),
+ ("C", &active_call_c, &cx_c),
+ ] {
+ active_call.read_with(*cx, |call, cx| {
+ let room = call.room().unwrap().read(cx);
+ assert_eq!(
+ room.followers_for(peer_id_a, project_id),
+ &[peer_id_b],
+ "checking followers for A as {name}"
+ );
+ });
+ }
+
let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
workspace
.active_item(cx)
@@ -6033,14 +6115,14 @@ async fn test_following(
workspace_a
.update(cx_a, |workspace, cx| {
workspace
- .toggle_follow(&ToggleFollow(client_b_id), cx)
+ .toggle_follow(&ToggleFollow(peer_id_b), cx)
.unwrap()
})
.await
.unwrap();
assert_eq!(
workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
- Some(client_b_id)
+ Some(peer_id_b)
);
assert_eq!(
workspace_a.read_with(cx_a, |workspace, cx| workspace
@@ -6112,7 +6194,7 @@ async fn test_following(
);
// Following interrupts when client B disconnects.
- client_b.disconnect(&cx_b.to_async()).unwrap();
+ client_b.disconnect(&cx_b.to_async());
deterministic.advance_clock(RECONNECT_TIMEOUT);
assert_eq!(
workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
@@ -27,6 +27,7 @@ call = { path = "../call" }
client = { path = "../client" }
clock = { path = "../clock" }
collections = { path = "../collections" }
+context_menu = { path = "../context_menu" }
editor = { path = "../editor" }
fuzzy = { path = "../fuzzy" }
gpui = { path = "../gpui" }
@@ -1,33 +1,60 @@
-use crate::{contact_notification::ContactNotification, contacts_popover, ToggleScreenSharing};
-use call::{ActiveCall, ParticipantLocation};
-use client::{proto::PeerId, Authenticate, ContactEventKind, User, UserStore};
+use crate::{
+ collaborator_list_popover, collaborator_list_popover::CollaboratorListPopover,
+ contact_notification::ContactNotification, contacts_popover, face_pile::FacePile,
+ ToggleScreenSharing,
+};
+use call::{ActiveCall, ParticipantLocation, Room};
+use client::{proto::PeerId, Authenticate, ContactEventKind, SignOut, User, UserStore};
use clock::ReplicaId;
use contacts_popover::ContactsPopover;
+use context_menu::{ContextMenu, ContextMenuItem};
use gpui::{
actions,
color::Color,
elements::*,
geometry::{rect::RectF, vector::vec2f, PathBuilder},
+ impl_internal_actions,
json::{self, ToJson},
- Border, CursorStyle, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext,
+ CursorStyle, Entity, ImageData, ModelHandle, MouseButton, MutableAppContext, RenderContext,
Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
};
use settings::Settings;
-use std::ops::Range;
-use theme::Theme;
+use std::{ops::Range, sync::Arc};
+use theme::{AvatarStyle, Theme};
+use util::ResultExt;
use workspace::{FollowNextCollaborator, JoinProject, ToggleFollow, Workspace};
-actions!(collab, [ToggleCollaborationMenu, ShareProject]);
+actions!(
+ collab,
+ [
+ ToggleCollaboratorList,
+ ToggleContactsMenu,
+ ToggleUserMenu,
+ ShareProject,
+ UnshareProject,
+ ]
+);
+
+impl_internal_actions!(collab, [LeaveCall]);
+
+#[derive(Copy, Clone, PartialEq)]
+pub(crate) struct LeaveCall;
pub fn init(cx: &mut MutableAppContext) {
+ cx.add_action(CollabTitlebarItem::toggle_collaborator_list_popover);
cx.add_action(CollabTitlebarItem::toggle_contacts_popover);
cx.add_action(CollabTitlebarItem::share_project);
+ cx.add_action(CollabTitlebarItem::unshare_project);
+ cx.add_action(CollabTitlebarItem::leave_call);
+ cx.add_action(CollabTitlebarItem::toggle_user_menu);
}
pub struct CollabTitlebarItem {
workspace: WeakViewHandle<Workspace>,
user_store: ModelHandle<UserStore>,
contacts_popover: Option<ViewHandle<ContactsPopover>>,
+ user_menu: ViewHandle<ContextMenu>,
+ collaborator_list_popover: Option<ViewHandle<CollaboratorListPopover>>,
_subscriptions: Vec<Subscription>,
}
@@ -47,27 +74,61 @@ impl View for CollabTitlebarItem {
return Empty::new().boxed();
};
+ let project = workspace.read(cx).project().read(cx);
+ let mut project_title = String::new();
+ for (i, name) in project.worktree_root_names(cx).enumerate() {
+ if i > 0 {
+ project_title.push_str(", ");
+ }
+ project_title.push_str(name);
+ }
+ if project_title.is_empty() {
+ project_title = "empty project".to_owned();
+ }
+
let theme = cx.global::<Settings>().theme.clone();
- let mut container = Flex::row();
+ let mut left_container = Flex::row();
+ let mut right_container = Flex::row();
- container.add_children(self.render_toggle_screen_sharing_button(&theme, cx));
+ left_container.add_child(
+ Label::new(project_title, theme.workspace.titlebar.title.clone())
+ .contained()
+ .with_margin_right(theme.workspace.titlebar.item_spacing)
+ .aligned()
+ .left()
+ .boxed(),
+ );
- if workspace.read(cx).client().status().borrow().is_connected() {
- let project = workspace.read(cx).project().read(cx);
- if project.is_shared()
- || project.is_remote()
- || ActiveCall::global(cx).read(cx).room().is_none()
- {
- container.add_child(self.render_toggle_contacts_button(&theme, cx));
- } else {
- container.add_child(self.render_share_button(&theme, cx));
- }
+ let user = workspace.read(cx).user_store().read(cx).current_user();
+ let peer_id = workspace.read(cx).client().peer_id();
+ if let Some(((user, peer_id), room)) = user
+ .zip(peer_id)
+ .zip(ActiveCall::global(cx).read(cx).room().cloned())
+ {
+ left_container
+ .add_children(self.render_in_call_share_unshare_button(&workspace, &theme, cx));
+
+ right_container.add_children(self.render_collaborators(&workspace, &theme, &room, cx));
+ right_container
+ .add_child(self.render_current_user(&workspace, &theme, &user, peer_id, cx));
+ right_container.add_child(self.render_toggle_screen_sharing_button(&theme, &room, cx));
+ }
+
+ let status = workspace.read(cx).client().status();
+ let status = &*status.borrow();
+ if matches!(status, client::Status::Connected { .. }) {
+ right_container.add_child(self.render_toggle_contacts_button(&theme, cx));
+ } else {
+ right_container.add_children(self.render_connection_status(status, cx));
}
- container.add_children(self.render_collaborators(&workspace, &theme, cx));
- container.add_children(self.render_current_user(&workspace, &theme, cx));
- container.add_children(self.render_connection_status(&workspace, cx));
- container.boxed()
+
+ right_container.add_child(self.render_user_menu_button(&theme, cx));
+
+ Stack::new()
+ .with_child(left_container.boxed())
+ .with_child(right_container.aligned().right().boxed())
+ .boxed()
}
}
@@ -80,7 +141,7 @@ impl CollabTitlebarItem {
let active_call = ActiveCall::global(cx);
let mut subscriptions = Vec::new();
subscriptions.push(cx.observe(workspace, |_, _, cx| cx.notify()));
- subscriptions.push(cx.observe(&active_call, |_, _, cx| cx.notify()));
+ subscriptions.push(cx.observe(&active_call, |this, _, cx| this.active_call_changed(cx)));
subscriptions.push(cx.observe_window_activation(|this, active, cx| {
this.window_activation_changed(active, cx)
}));
@@ -112,6 +173,12 @@ impl CollabTitlebarItem {
workspace: workspace.downgrade(),
user_store: user_store.clone(),
contacts_popover: None,
+ user_menu: cx.add_view(|cx| {
+ let mut menu = ContextMenu::new(cx);
+ menu.set_position_mode(OverlayPositionMode::Local);
+ menu
+ }),
+ collaborator_list_popover: None,
_subscriptions: subscriptions,
}
}
@@ -129,6 +196,13 @@ impl CollabTitlebarItem {
}
}
+ fn active_call_changed(&mut self, cx: &mut ViewContext<Self>) {
+ if ActiveCall::global(cx).read(cx).room().is_none() {
+ self.contacts_popover = None;
+ }
+ cx.notify();
+ }
+
fn share_project(&mut self, _: &ShareProject, cx: &mut ViewContext<Self>) {
if let Some(workspace) = self.workspace.upgrade(cx) {
let active_call = ActiveCall::global(cx);
@@ -139,41 +213,135 @@ impl CollabTitlebarItem {
}
}
- pub fn toggle_contacts_popover(
+ fn unshare_project(&mut self, _: &UnshareProject, cx: &mut ViewContext<Self>) {
+ if let Some(workspace) = self.workspace.upgrade(cx) {
+ let active_call = ActiveCall::global(cx);
+ let project = workspace.read(cx).project().clone();
+ active_call
+ .update(cx, |call, cx| call.unshare_project(project, cx))
+ .log_err();
+ }
+ }
+
+ pub fn toggle_collaborator_list_popover(
&mut self,
- _: &ToggleCollaborationMenu,
+ _: &ToggleCollaboratorList,
cx: &mut ViewContext<Self>,
) {
- match self.contacts_popover.take() {
+ match self.collaborator_list_popover.take() {
Some(_) => {}
None => {
if let Some(workspace) = self.workspace.upgrade(cx) {
- let project = workspace.read(cx).project().clone();
let user_store = workspace.read(cx).user_store().clone();
- let view = cx.add_view(|cx| ContactsPopover::new(project, user_store, cx));
+ let view = cx.add_view(|cx| CollaboratorListPopover::new(user_store, cx));
+
cx.subscribe(&view, |this, _, event, cx| {
match event {
- contacts_popover::Event::Dismissed => {
- this.contacts_popover = None;
+ collaborator_list_popover::Event::Dismissed => {
+ this.collaborator_list_popover = None;
}
}
cx.notify();
})
.detach();
- self.contacts_popover = Some(view);
+
+ self.collaborator_list_popover = Some(view);
}
}
}
cx.notify();
}
+ pub fn toggle_contacts_popover(&mut self, _: &ToggleContactsMenu, cx: &mut ViewContext<Self>) {
+ if self.contacts_popover.take().is_none() {
+ if let Some(workspace) = self.workspace.upgrade(cx) {
+ let project = workspace.read(cx).project().clone();
+ let user_store = workspace.read(cx).user_store().clone();
+ let view = cx.add_view(|cx| ContactsPopover::new(project, user_store, cx));
+ cx.subscribe(&view, |this, _, event, cx| {
+ match event {
+ contacts_popover::Event::Dismissed => {
+ this.contacts_popover = None;
+ }
+ }
+
+ cx.notify();
+ })
+ .detach();
+ self.contacts_popover = Some(view);
+ }
+ }
+
+ cx.notify();
+ }
+
+ pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
+ let theme = cx.global::<Settings>().theme.clone();
+ let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
+ let item_style = theme.context_menu.item.disabled_style().clone();
+ self.user_menu.update(cx, |user_menu, cx| {
+ let items = if let Some(user) = self.user_store.read(cx).current_user() {
+ vec![
+ ContextMenuItem::Static(Box::new(move |_| {
+ Flex::row()
+ .with_children(user.avatar.clone().map(|avatar| {
+ Self::render_face(
+ avatar,
+ avatar_style.clone(),
+ Color::transparent_black(),
+ )
+ }))
+ .with_child(
+ Label::new(user.github_login.clone(), item_style.label.clone())
+ .boxed(),
+ )
+ .contained()
+ .with_style(item_style.container)
+ .boxed()
+ })),
+ ContextMenuItem::Item {
+ label: "Sign out".into(),
+ action: Box::new(SignOut),
+ },
+ ]
+ } else {
+ vec![ContextMenuItem::Item {
+ label: "Sign in".into(),
+ action: Box::new(Authenticate),
+ }]
+ };
+
+ user_menu.show(
+ vec2f(
+ theme
+ .workspace
+ .titlebar
+ .user_menu_button
+ .default
+ .button_width,
+ theme.workspace.titlebar.height,
+ ),
+ AnchorCorner::TopRight,
+ items,
+ cx,
+ );
+ });
+ }
+
+ fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext<Self>) {
+ ActiveCall::global(cx)
+ .update(cx, |call, cx| call.hang_up(cx))
+ .log_err();
+ }
+
fn render_toggle_contacts_button(
&self,
theme: &Theme,
cx: &mut RenderContext<Self>,
) -> ElementBox {
let titlebar = &theme.workspace.titlebar;
+
let badge = if self
.user_store
.read(cx)
@@ -194,9 +362,10 @@ impl CollabTitlebarItem {
.boxed(),
)
};
+
Stack::new()
.with_child(
- MouseEventHandler::<ToggleCollaborationMenu>::new(0, cx, |state, _| {
+ MouseEventHandler::<ToggleContactsMenu>::new(0, cx, |state, _| {
let style = titlebar
.toggle_contacts_button
.style_for(state, self.contacts_popover.is_some());
@@ -214,39 +383,31 @@ impl CollabTitlebarItem {
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(ToggleCollaborationMenu);
+ cx.dispatch_action(ToggleContactsMenu);
})
+ .with_tooltip::<ToggleContactsMenu, _>(
+ 0,
+ "Show contacts menu".into(),
+ Some(Box::new(ToggleContactsMenu)),
+ theme.tooltip.clone(),
+ cx,
+ )
.aligned()
.boxed(),
)
.with_children(badge)
- .with_children(self.contacts_popover.as_ref().map(|popover| {
- Overlay::new(
- ChildView::new(popover, cx)
- .contained()
- .with_margin_top(titlebar.height)
- .with_margin_left(titlebar.toggle_contacts_button.default.button_width)
- .with_margin_right(-titlebar.toggle_contacts_button.default.button_width)
- .boxed(),
- )
- .with_fit_mode(OverlayFitMode::SwitchAnchor)
- .with_anchor_corner(AnchorCorner::BottomLeft)
- .with_z_index(999)
- .boxed()
- }))
+ .with_children(self.render_contacts_popover_host(titlebar, cx))
.boxed()
}
fn render_toggle_screen_sharing_button(
&self,
theme: &Theme,
+ room: &ModelHandle<Room>,
cx: &mut RenderContext<Self>,
- ) -> Option<ElementBox> {
- let active_call = ActiveCall::global(cx);
- let room = active_call.read(cx).room().cloned()?;
+ ) -> ElementBox {
let icon;
let tooltip;
-
if room.read(cx).is_screen_sharing() {
icon = "icons/disable_screen_sharing_12.svg";
tooltip = "Stop Sharing Screen"
@@ -256,226 +417,368 @@ impl CollabTitlebarItem {
}
let titlebar = &theme.workspace.titlebar;
- Some(
- MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
- let style = titlebar.call_control.style_for(state, false);
- Svg::new(icon)
- .with_color(style.color)
- .constrained()
- .with_width(style.icon_width)
- .aligned()
- .constrained()
- .with_width(style.button_width)
- .with_height(style.button_width)
- .contained()
- .with_style(style.container)
- .boxed()
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(ToggleScreenSharing);
- })
- .with_tooltip::<ToggleScreenSharing, _>(
- 0,
- tooltip.into(),
- Some(Box::new(ToggleScreenSharing)),
- theme.tooltip.clone(),
- cx,
- )
- .aligned()
- .boxed(),
- )
- }
-
- fn render_share_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
- enum Share {}
-
- let titlebar = &theme.workspace.titlebar;
- MouseEventHandler::<Share>::new(0, cx, |state, _| {
- let style = titlebar.share_button.style_for(state, false);
- Label::new("Share", style.text.clone())
+ MouseEventHandler::<ToggleScreenSharing>::new(0, cx, |state, _| {
+ let style = titlebar.call_control.style_for(state, false);
+ Svg::new(icon)
+ .with_color(style.color)
+ .constrained()
+ .with_width(style.icon_width)
+ .aligned()
+ .constrained()
+ .with_width(style.button_width)
+ .with_height(style.button_width)
.contained()
.with_style(style.container)
.boxed()
})
.with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(ShareProject))
- .with_tooltip::<Share, _>(
+ .on_click(MouseButton::Left, move |_, cx| {
+ cx.dispatch_action(ToggleScreenSharing);
+ })
+ .with_tooltip::<ToggleScreenSharing, _>(
0,
- "Share project with call participants".into(),
- None,
+ tooltip.into(),
+ Some(Box::new(ToggleScreenSharing)),
theme.tooltip.clone(),
cx,
)
.aligned()
- .contained()
- .with_margin_left(theme.workspace.titlebar.avatar_margin)
.boxed()
}
+ fn render_in_call_share_unshare_button(
+ &self,
+ workspace: &ViewHandle<Workspace>,
+ theme: &Theme,
+ cx: &mut RenderContext<Self>,
+ ) -> Option<ElementBox> {
+ let project = workspace.read(cx).project();
+ if project.read(cx).is_remote() {
+ return None;
+ }
+
+ let is_shared = project.read(cx).is_shared();
+ let label = if is_shared { "Unshare" } else { "Share" };
+ let tooltip = if is_shared {
+ "Unshare project from call participants"
+ } else {
+ "Share project with call participants"
+ };
+
+ let titlebar = &theme.workspace.titlebar;
+
+ enum ShareUnshare {}
+ Some(
+ Stack::new()
+ .with_child(
+ MouseEventHandler::<ShareUnshare>::new(0, cx, |state, _| {
+ //TODO: Ensure this button has consistant width for both text variations
+ let style = titlebar
+ .share_button
+ .style_for(state, self.contacts_popover.is_some());
+ Label::new(label, style.text.clone())
+ .contained()
+ .with_style(style.container)
+ .boxed()
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, cx| {
+ if is_shared {
+ cx.dispatch_action(UnshareProject);
+ } else {
+ cx.dispatch_action(ShareProject);
+ }
+ })
+ .with_tooltip::<ShareUnshare, _>(
+ 0,
+ tooltip.to_owned(),
+ None,
+ theme.tooltip.clone(),
+ cx,
+ )
+ .boxed(),
+ )
+ .aligned()
+ .contained()
+ .with_margin_left(theme.workspace.titlebar.item_spacing)
+ .boxed(),
+ )
+ }
+
+ fn render_user_menu_button(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
+ let titlebar = &theme.workspace.titlebar;
+
+ Stack::new()
+ .with_child(
+ MouseEventHandler::<ToggleUserMenu>::new(0, cx, |state, _| {
+ let style = titlebar.call_control.style_for(state, false);
+ Svg::new("icons/ellipsis_14.svg")
+ .with_color(style.color)
+ .constrained()
+ .with_width(style.icon_width)
+ .aligned()
+ .constrained()
+ .with_width(style.button_width)
+ .with_height(style.button_width)
+ .contained()
+ .with_style(style.container)
+ .boxed()
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, move |_, cx| {
+ cx.dispatch_action(ToggleUserMenu);
+ })
+ .with_tooltip::<ToggleUserMenu, _>(
+ 0,
+ "Toggle user menu".to_owned(),
+ Some(Box::new(ToggleUserMenu)),
+ theme.tooltip.clone(),
+ cx,
+ )
+ .contained()
+ .with_margin_left(theme.workspace.titlebar.item_spacing)
+ .aligned()
+ .boxed(),
+ )
+ .with_child(ChildView::new(&self.user_menu, cx).boxed())
+ .boxed()
+ }
+
+ fn render_contacts_popover_host<'a>(
+ &'a self,
+ theme: &'a theme::Titlebar,
+ cx: &'a RenderContext<Self>,
+ ) -> Option<ElementBox> {
+ self.contacts_popover.as_ref().map(|popover| {
+ Overlay::new(
+ ChildView::new(popover, cx)
+ .contained()
+ .with_margin_top(theme.height)
+ .with_margin_left(theme.toggle_contacts_button.default.button_width)
+ .with_margin_right(-theme.toggle_contacts_button.default.button_width)
+ .boxed(),
+ )
+ .with_fit_mode(OverlayFitMode::SwitchAnchor)
+ .with_anchor_corner(AnchorCorner::BottomLeft)
+ .with_z_index(999)
+ .boxed()
+ })
+ }
+
fn render_collaborators(
&self,
workspace: &ViewHandle<Workspace>,
theme: &Theme,
+ room: &ModelHandle<Room>,
cx: &mut RenderContext<Self>,
) -> Vec<ElementBox> {
- let active_call = ActiveCall::global(cx);
- if let Some(room) = active_call.read(cx).room().cloned() {
- let project = workspace.read(cx).project().read(cx);
- let mut participants = room
- .read(cx)
- .remote_participants()
- .values()
- .cloned()
- .collect::<Vec<_>>();
- participants.sort_by_key(|p| Some(project.collaborators().get(&p.peer_id)?.replica_id));
- participants
- .into_iter()
- .filter_map(|participant| {
- let project = workspace.read(cx).project().read(cx);
- let replica_id = project
- .collaborators()
- .get(&participant.peer_id)
- .map(|collaborator| collaborator.replica_id);
- let user = participant.user.clone();
- Some(self.render_avatar(
+ let project = workspace.read(cx).project().read(cx);
+
+ let mut participants = room
+ .read(cx)
+ .remote_participants()
+ .values()
+ .cloned()
+ .collect::<Vec<_>>();
+ participants.sort_by_key(|p| Some(project.collaborators().get(&p.peer_id)?.replica_id));
+
+ participants
+ .into_iter()
+ .filter_map(|participant| {
+ let project = workspace.read(cx).project().read(cx);
+ let replica_id = project
+ .collaborators()
+ .get(&participant.peer_id)
+ .map(|collaborator| collaborator.replica_id);
+ let user = participant.user.clone();
+ Some(
+ Container::new(self.render_face_pile(
&user,
replica_id,
- Some((
- participant.peer_id,
- &user.github_login,
- participant.location,
- )),
+ participant.peer_id,
+ Some(participant.location),
workspace,
theme,
cx,
))
- })
- .collect()
- } else {
- Default::default()
- }
+ .with_margin_right(theme.workspace.titlebar.face_pile_spacing)
+ .boxed(),
+ )
+ })
+ .collect()
}
fn render_current_user(
&self,
workspace: &ViewHandle<Workspace>,
theme: &Theme,
+ user: &Arc<User>,
+ peer_id: PeerId,
cx: &mut RenderContext<Self>,
- ) -> Option<ElementBox> {
- let user = workspace.read(cx).user_store().read(cx).current_user();
+ ) -> ElementBox {
let replica_id = workspace.read(cx).project().read(cx).replica_id();
- let status = *workspace.read(cx).client().status().borrow();
- if let Some(user) = user {
- Some(self.render_avatar(&user, Some(replica_id), None, workspace, theme, cx))
- } else if matches!(status, client::Status::UpgradeRequired) {
- None
- } else {
- Some(
- MouseEventHandler::<Authenticate>::new(0, cx, |state, _| {
- let style = theme
- .workspace
- .titlebar
- .sign_in_prompt
- .style_for(state, false);
- Label::new("Sign in", style.text.clone())
- .contained()
- .with_style(style.container)
- .boxed()
- })
- .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(Authenticate))
- .with_cursor_style(CursorStyle::PointingHand)
- .aligned()
- .boxed(),
- )
- }
+ Container::new(self.render_face_pile(
+ user,
+ Some(replica_id),
+ peer_id,
+ None,
+ workspace,
+ theme,
+ cx,
+ ))
+ .with_margin_right(theme.workspace.titlebar.item_spacing)
+ .boxed()
}
- fn render_avatar(
+ fn render_face_pile(
&self,
user: &User,
replica_id: Option<ReplicaId>,
- peer: Option<(PeerId, &str, ParticipantLocation)>,
+ peer_id: PeerId,
+ location: Option<ParticipantLocation>,
workspace: &ViewHandle<Workspace>,
theme: &Theme,
cx: &mut RenderContext<Self>,
) -> ElementBox {
- let is_followed = peer.map_or(false, |(peer_id, _, _)| {
- workspace.read(cx).is_following(peer_id)
- });
+ let project_id = workspace.read(cx).project().read(cx).remote_id();
+ let room = ActiveCall::global(cx).read(cx).room();
+ let is_being_followed = workspace.read(cx).is_being_followed(peer_id);
+ let followed_by_self = room
+ .and_then(|room| {
+ Some(
+ is_being_followed
+ && room
+ .read(cx)
+ .followers_for(peer_id, project_id?)
+ .iter()
+ .any(|&follower| {
+ Some(follower) == workspace.read(cx).client().peer_id()
+ }),
+ )
+ })
+ .unwrap_or(false);
- let mut avatar_style;
- if let Some((_, _, location)) = peer.as_ref() {
- if let ParticipantLocation::SharedProject { project_id } = *location {
- if Some(project_id) == workspace.read(cx).project().read(cx).remote_id() {
- avatar_style = theme.workspace.titlebar.avatar;
- } else {
- avatar_style = theme.workspace.titlebar.inactive_avatar;
- }
- } else {
- avatar_style = theme.workspace.titlebar.inactive_avatar;
- }
- } else {
- avatar_style = theme.workspace.titlebar.avatar;
- }
+ let leader_style = theme.workspace.titlebar.leader_avatar;
+ let follower_style = theme.workspace.titlebar.follower_avatar;
- let mut replica_color = None;
+ let mut background_color = theme
+ .workspace
+ .titlebar
+ .container
+ .background_color
+ .unwrap_or_default();
if let Some(replica_id) = replica_id {
- let color = theme.editor.replica_selection_style(replica_id).cursor;
- replica_color = Some(color);
- if is_followed {
- avatar_style.border = Border::all(1.0, color);
+ if followed_by_self {
+ let selection = theme.editor.replica_selection_style(replica_id).selection;
+ background_color = Color::blend(selection, background_color);
+ background_color.a = 255;
}
}
- let content = Stack::new()
+ let mut content = Stack::new()
.with_children(user.avatar.as_ref().map(|avatar| {
- Image::new(avatar.clone())
- .with_style(avatar_style)
- .constrained()
- .with_width(theme.workspace.titlebar.avatar_width)
- .aligned()
- .boxed()
- }))
- .with_children(replica_color.map(|replica_color| {
- AvatarRibbon::new(replica_color)
- .constrained()
- .with_width(theme.workspace.titlebar.avatar_ribbon.width)
- .with_height(theme.workspace.titlebar.avatar_ribbon.height)
- .aligned()
- .bottom()
- .boxed()
+ let face_pile = FacePile::new(theme.workspace.titlebar.follower_avatar_overlap)
+ .with_child(Self::render_face(
+ avatar.clone(),
+ Self::location_style(workspace, location, leader_style, cx),
+ background_color,
+ ))
+ .with_children(
+ (|| {
+ let project_id = project_id?;
+ let room = room?.read(cx);
+ let followers = room.followers_for(peer_id, project_id);
+
+ Some(followers.into_iter().flat_map(|&follower| {
+ let remote_participant =
+ room.remote_participant_for_peer_id(follower);
+
+ let avatar = remote_participant
+ .and_then(|p| p.user.avatar.clone())
+ .or_else(|| {
+ if follower == workspace.read(cx).client().peer_id()? {
+ workspace
+ .read(cx)
+ .user_store()
+ .read(cx)
+ .current_user()?
+ .avatar
+ .clone()
+ } else {
+ None
+ }
+ })?;
+
+ let location = remote_participant.map(|p| p.location);
+
+ Some(Self::render_face(
+ avatar.clone(),
+ Self::location_style(workspace, location, follower_style, cx),
+ background_color,
+ ))
+ }))
+ })()
+ .into_iter()
+ .flatten(),
+ );
+
+ let mut container = face_pile
+ .contained()
+ .with_style(theme.workspace.titlebar.leader_selection);
+
+ if let Some(replica_id) = replica_id {
+ if followed_by_self {
+ let color = theme.editor.replica_selection_style(replica_id).selection;
+ container = container.with_background_color(color);
+ }
+ }
+
+ container.boxed()
}))
- .constrained()
- .with_width(theme.workspace.titlebar.avatar_width)
- .contained()
- .with_margin_left(theme.workspace.titlebar.avatar_margin)
+ .with_children((|| {
+ let replica_id = replica_id?;
+ let color = theme.editor.replica_selection_style(replica_id).cursor;
+ Some(
+ AvatarRibbon::new(color)
+ .constrained()
+ .with_width(theme.workspace.titlebar.avatar_ribbon.width)
+ .with_height(theme.workspace.titlebar.avatar_ribbon.height)
+ .aligned()
+ .bottom()
+ .boxed(),
+ )
+ })())
.boxed();
- if let Some((peer_id, peer_github_login, location)) = peer {
+ if let Some(location) = location {
if let Some(replica_id) = replica_id {
- MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| content)
+ content =
+ MouseEventHandler::<ToggleFollow>::new(replica_id.into(), cx, move |_, _| {
+ content
+ })
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(ToggleFollow(peer_id))
})
.with_tooltip::<ToggleFollow, _>(
peer_id.as_u64() as usize,
- if is_followed {
- format!("Unfollow {}", peer_github_login)
+ if is_being_followed {
+ format!("Unfollow {}", user.github_login)
} else {
- format!("Follow {}", peer_github_login)
+ format!("Follow {}", user.github_login)
},
Some(Box::new(FollowNextCollaborator)),
theme.tooltip.clone(),
cx,
)
- .boxed()
+ .boxed();
} else if let ParticipantLocation::SharedProject { project_id } = location {
let user_id = user.id;
- MouseEventHandler::<JoinProject>::new(peer_id.as_u64() as usize, cx, move |_, _| {
- content
- })
+ content = MouseEventHandler::<JoinProject>::new(
+ peer_id.as_u64() as usize,
+ cx,
+ move |_, _| content,
+ )
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
cx.dispatch_action(JoinProject {
@@ -485,29 +788,63 @@ impl CollabTitlebarItem {
})
.with_tooltip::<JoinProject, _>(
peer_id.as_u64() as usize,
- format!("Follow {} into external project", peer_github_login),
+ format!("Follow {} into external project", user.github_login),
Some(Box::new(FollowNextCollaborator)),
theme.tooltip.clone(),
cx,
)
- .boxed()
+ .boxed();
+ }
+ }
+ content
+ }
+
+ fn location_style(
+ workspace: &ViewHandle<Workspace>,
+ location: Option<ParticipantLocation>,
+ mut style: AvatarStyle,
+ cx: &RenderContext<Self>,
+ ) -> AvatarStyle {
+ if let Some(location) = location {
+ if let ParticipantLocation::SharedProject { project_id } = location {
+ if Some(project_id) != workspace.read(cx).project().read(cx).remote_id() {
+ style.image.grayscale = true;
+ }
} else {
- content
+ style.image.grayscale = true;
}
- } else {
- content
}
+
+ style
+ }
+
+ fn render_face(
+ avatar: Arc<ImageData>,
+ avatar_style: AvatarStyle,
+ background_color: Color,
+ ) -> ElementBox {
+ Image::new(avatar)
+ .with_style(avatar_style.image)
+ .aligned()
+ .contained()
+ .with_background_color(background_color)
+ .with_corner_radius(avatar_style.outer_corner_radius)
+ .constrained()
+ .with_width(avatar_style.outer_width)
+ .with_height(avatar_style.outer_width)
+ .aligned()
+ .boxed()
}
fn render_connection_status(
&self,
- workspace: &ViewHandle<Workspace>,
+ status: &client::Status,
cx: &mut RenderContext<Self>,
) -> Option<ElementBox> {
enum ConnectionStatusButton {}
let theme = &cx.global::<Settings>().theme.clone();
- match &*workspace.read(cx).client().status().borrow() {
+ match status {
client::Status::ConnectionError
| client::Status::ConnectionLost
| client::Status::Reauthenticating { .. }
@@ -1,8 +1,10 @@
mod collab_titlebar_item;
+mod collaborator_list_popover;
mod contact_finder;
mod contact_list;
mod contact_notification;
mod contacts_popover;
+mod face_pile;
mod incoming_call_notification;
mod notifications;
mod project_shared_notification;
@@ -10,7 +12,7 @@ mod sharing_status_indicator;
use anyhow::anyhow;
use call::ActiveCall;
-pub use collab_titlebar_item::{CollabTitlebarItem, ToggleCollaborationMenu};
+pub use collab_titlebar_item::{CollabTitlebarItem, ToggleContactsMenu};
use gpui::{actions, MutableAppContext, Task};
use std::sync::Arc;
use workspace::{AppState, JoinProject, ToggleFollow, Workspace};
@@ -116,7 +118,7 @@ fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut Mutable
});
if let Some(follow_peer_id) = follow_peer_id {
- if !workspace.is_following(follow_peer_id) {
+ if !workspace.is_being_followed(follow_peer_id) {
workspace
.toggle_follow(&ToggleFollow(follow_peer_id), cx)
.map(|follow| follow.detach_and_log_err(cx));
@@ -0,0 +1,165 @@
+use call::ActiveCall;
+use client::UserStore;
+use gpui::Action;
+use gpui::{
+ actions, elements::*, Entity, ModelHandle, MouseButton, RenderContext, View, ViewContext,
+};
+use settings::Settings;
+
+use crate::collab_titlebar_item::ToggleCollaboratorList;
+
+pub(crate) enum Event {
+ Dismissed,
+}
+
+enum Collaborator {
+ SelfUser { username: String },
+ RemoteUser { username: String },
+}
+
+actions!(collaborator_list_popover, [NoOp]);
+
+pub(crate) struct CollaboratorListPopover {
+ list_state: ListState,
+}
+
+impl Entity for CollaboratorListPopover {
+ type Event = Event;
+}
+
+impl View for CollaboratorListPopover {
+ fn ui_name() -> &'static str {
+ "CollaboratorListPopover"
+ }
+
+ fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
+ let theme = cx.global::<Settings>().theme.clone();
+
+ MouseEventHandler::<Self>::new(0, cx, |_, _| {
+ List::new(self.list_state.clone())
+ .contained()
+ .with_style(theme.contacts_popover.container) //TODO: Change the name of this theme key
+ .constrained()
+ .with_width(theme.contacts_popover.width)
+ .with_height(theme.contacts_popover.height)
+ .boxed()
+ })
+ .on_down_out(MouseButton::Left, move |_, cx| {
+ cx.dispatch_action(ToggleCollaboratorList);
+ })
+ .boxed()
+ }
+
+ fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
+ cx.emit(Event::Dismissed);
+ }
+}
+
+impl CollaboratorListPopover {
+ pub fn new(user_store: ModelHandle<UserStore>, cx: &mut ViewContext<Self>) -> Self {
+ let active_call = ActiveCall::global(cx);
+
+ let mut collaborators = user_store
+ .read(cx)
+ .current_user()
+ .map(|u| Collaborator::SelfUser {
+ username: u.github_login.clone(),
+ })
+ .into_iter()
+ .collect::<Vec<_>>();
+
+ //TODO: What should the canonical sort here look like, consult contacts list implementation
+ if let Some(room) = active_call.read(cx).room() {
+ for participant in room.read(cx).remote_participants() {
+ collaborators.push(Collaborator::RemoteUser {
+ username: participant.1.user.github_login.clone(),
+ });
+ }
+ }
+
+ Self {
+ list_state: ListState::new(
+ collaborators.len(),
+ Orientation::Top,
+ 0.,
+ cx,
+ move |_, index, cx| match &collaborators[index] {
+ Collaborator::SelfUser { username } => render_collaborator_list_entry(
+ index,
+ username,
+ None::<NoOp>,
+ None,
+ Svg::new("icons/chevron_right_12.svg"),
+ NoOp,
+ "Leave call".to_owned(),
+ cx,
+ ),
+
+ Collaborator::RemoteUser { username } => render_collaborator_list_entry(
+ index,
+ username,
+ Some(NoOp),
+ Some(format!("Follow {username}")),
+ Svg::new("icons/x_mark_12.svg"),
+ NoOp,
+ format!("Remove {username} from call"),
+ cx,
+ ),
+ },
+ ),
+ }
+ }
+}
+
+fn render_collaborator_list_entry<UA: Action + Clone, IA: Action + Clone>(
+ index: usize,
+ username: &str,
+ username_action: Option<UA>,
+ username_tooltip: Option<String>,
+ icon: Svg,
+ icon_action: IA,
+ icon_tooltip: String,
+ cx: &mut RenderContext<CollaboratorListPopover>,
+) -> ElementBox {
+ enum Username {}
+ enum UsernameTooltip {}
+ enum Icon {}
+ enum IconTooltip {}
+
+ let theme = &cx.global::<Settings>().theme;
+ let username_theme = theme.contact_list.contact_username.text.clone();
+ let tooltip_theme = theme.tooltip.clone();
+
+ let username = MouseEventHandler::<Username>::new(index, cx, |_, _| {
+ Label::new(username.to_owned(), username_theme.clone()).boxed()
+ })
+ .on_click(MouseButton::Left, move |_, cx| {
+ if let Some(username_action) = username_action.clone() {
+ cx.dispatch_action(username_action);
+ }
+ });
+
+ Flex::row()
+ .with_child(if let Some(username_tooltip) = username_tooltip {
+ username
+ .with_tooltip::<UsernameTooltip, _>(
+ index,
+ username_tooltip,
+ None,
+ tooltip_theme.clone(),
+ cx,
+ )
+ .boxed()
+ } else {
+ username.boxed()
+ })
+ .with_child(
+ MouseEventHandler::<Icon>::new(index, cx, |_, _| icon.boxed())
+ .on_click(MouseButton::Left, move |_, cx| {
+ cx.dispatch_action(icon_action.clone())
+ })
+ .with_tooltip::<IconTooltip, _>(index, icon_tooltip, None, tooltip_theme, cx)
+ .boxed(),
+ )
+ .boxed()
+}
@@ -1,3 +1,4 @@
+use super::collab_titlebar_item::LeaveCall;
use crate::contacts_popover;
use call::ActiveCall;
use client::{proto::PeerId, Contact, User, UserStore};
@@ -18,22 +19,20 @@ use serde::Deserialize;
use settings::Settings;
use std::{mem, sync::Arc};
use theme::IconButton;
-use util::ResultExt;
use workspace::{JoinProject, OpenSharedScreen};
impl_actions!(contact_list, [RemoveContact, RespondToContactRequest]);
-impl_internal_actions!(contact_list, [ToggleExpanded, Call, LeaveCall]);
+impl_internal_actions!(contact_list, [ToggleExpanded, Call]);
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(ContactList::remove_contact);
cx.add_action(ContactList::respond_to_contact_request);
- cx.add_action(ContactList::clear_filter);
+ cx.add_action(ContactList::cancel);
cx.add_action(ContactList::select_next);
cx.add_action(ContactList::select_prev);
cx.add_action(ContactList::confirm);
cx.add_action(ContactList::toggle_expanded);
cx.add_action(ContactList::call);
- cx.add_action(ContactList::leave_call);
}
#[derive(Clone, PartialEq)]
@@ -45,9 +44,6 @@ struct Call {
initial_project: Option<ModelHandle<Project>>,
}
-#[derive(Copy, Clone, PartialEq)]
-struct LeaveCall;
-
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
enum Section {
ActiveCall,
@@ -326,7 +322,7 @@ impl ContactList {
.detach();
}
- fn clear_filter(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+ fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
let did_clear = self.filter_editor.update(cx, |editor, cx| {
if editor.buffer().read(cx).len(cx) > 0 {
editor.set_text("", cx);
@@ -335,6 +331,7 @@ impl ContactList {
false
}
});
+
if !did_clear {
cx.emit(Event::Dismissed);
}
@@ -980,6 +977,7 @@ impl ContactList {
cx: &mut RenderContext<Self>,
) -> ElementBox {
enum Header {}
+ enum LeaveCallContactList {}
let header_style = theme
.header_row
@@ -992,9 +990,9 @@ impl ContactList {
};
let leave_call = if section == Section::ActiveCall {
Some(
- MouseEventHandler::<LeaveCall>::new(0, cx, |state, _| {
+ MouseEventHandler::<LeaveCallContactList>::new(0, cx, |state, _| {
let style = theme.leave_call.style_for(state, false);
- Label::new("Leave Session", style.text.clone())
+ Label::new("Leave Call", style.text.clone())
.contained()
.with_style(style.container)
.boxed()
@@ -1283,12 +1281,6 @@ impl ContactList {
})
.detach_and_log_err(cx);
}
-
- fn leave_call(&mut self, _: &LeaveCall, cx: &mut ViewContext<Self>) {
- ActiveCall::global(cx)
- .update(cx, |call, cx| call.hang_up(cx))
- .log_err();
- }
}
impl Entity for ContactList {
@@ -1302,7 +1294,7 @@ impl View for ContactList {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
- cx.set.insert("menu".into());
+ cx.add_identifier("menu");
cx
}
@@ -1334,7 +1326,7 @@ impl View for ContactList {
})
.with_tooltip::<AddContact, _>(
0,
- "Add contact".into(),
+ "Search for new contact".into(),
None,
theme.tooltip.clone(),
cx,
@@ -1,4 +1,4 @@
-use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleCollaborationMenu};
+use crate::{contact_finder::ContactFinder, contact_list::ContactList, ToggleContactsMenu};
use client::UserStore;
use gpui::{
actions, elements::*, ClipboardItem, CursorStyle, Entity, ModelHandle, MouseButton,
@@ -155,7 +155,7 @@ impl View for ContactsPopover {
.boxed()
})
.on_down_out(MouseButton::Left, move |_, cx| {
- cx.dispatch_action(ToggleCollaborationMenu);
+ cx.dispatch_action(ToggleContactsMenu);
})
.boxed()
}
@@ -0,0 +1,101 @@
+use std::ops::Range;
+
+use gpui::{
+ geometry::{
+ rect::RectF,
+ vector::{vec2f, Vector2F},
+ },
+ json::ToJson,
+ serde_json::{self, json},
+ Axis, DebugContext, Element, ElementBox, MeasurementContext, PaintContext,
+};
+
+pub(crate) struct FacePile {
+ overlap: f32,
+ faces: Vec<ElementBox>,
+}
+
+impl FacePile {
+ pub fn new(overlap: f32) -> FacePile {
+ FacePile {
+ overlap,
+ faces: Vec::new(),
+ }
+ }
+}
+
+impl Element for FacePile {
+ type LayoutState = ();
+ type PaintState = ();
+
+ fn layout(
+ &mut self,
+ constraint: gpui::SizeConstraint,
+ cx: &mut gpui::LayoutContext,
+ ) -> (Vector2F, Self::LayoutState) {
+ debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
+
+ let mut width = 0.;
+ for face in &mut self.faces {
+ width += face.layout(constraint, cx).x();
+ }
+ width -= self.overlap * self.faces.len().saturating_sub(1) as f32;
+
+ (Vector2F::new(width, constraint.max.y()), ())
+ }
+
+ fn paint(
+ &mut self,
+ bounds: RectF,
+ visible_bounds: RectF,
+ _layout: &mut Self::LayoutState,
+ cx: &mut PaintContext,
+ ) -> Self::PaintState {
+ let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+
+ let origin_y = bounds.upper_right().y();
+ let mut origin_x = bounds.upper_right().x();
+
+ for face in self.faces.iter_mut().rev() {
+ let size = face.size();
+ origin_x -= size.x();
+ cx.paint_layer(None, |cx| {
+ face.paint(vec2f(origin_x, origin_y), visible_bounds, cx);
+ });
+ origin_x += self.overlap;
+ }
+
+ ()
+ }
+
+ fn rect_for_text_range(
+ &self,
+ _: Range<usize>,
+ _: RectF,
+ _: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &MeasurementContext,
+ ) -> Option<RectF> {
+ None
+ }
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ _: &Self::LayoutState,
+ _: &Self::PaintState,
+ _: &DebugContext,
+ ) -> serde_json::Value {
+ json!({
+ "type": "FacePile",
+ "bounds": bounds.to_json()
+ })
+ }
+}
+
+impl Extend<ElementBox> for FacePile {
+ fn extend<T: IntoIterator<Item = ElementBox>>(&mut self, children: T) {
+ self.faces.extend(children);
+ }
+}
@@ -5,7 +5,9 @@ use gpui::{
};
use menu::*;
use settings::Settings;
-use std::{any::TypeId, time::Duration};
+use std::{any::TypeId, borrow::Cow, time::Duration};
+
+pub type StaticItem = Box<dyn Fn(&mut MutableAppContext) -> ElementBox>;
#[derive(Copy, Clone, PartialEq)]
struct Clicked;
@@ -24,16 +26,17 @@ pub fn init(cx: &mut MutableAppContext) {
pub enum ContextMenuItem {
Item {
- label: String,
+ label: Cow<'static, str>,
action: Box<dyn Action>,
},
+ Static(StaticItem),
Separator,
}
impl ContextMenuItem {
- pub fn item(label: impl ToString, action: impl 'static + Action) -> Self {
+ pub fn item(label: impl Into<Cow<'static, str>>, action: impl 'static + Action) -> Self {
Self::Item {
- label: label.to_string(),
+ label: label.into(),
action: Box::new(action),
}
}
@@ -42,14 +45,14 @@ impl ContextMenuItem {
Self::Separator
}
- fn is_separator(&self) -> bool {
- matches!(self, Self::Separator)
+ fn is_action(&self) -> bool {
+ matches!(self, Self::Item { .. })
}
fn action_id(&self) -> Option<TypeId> {
match self {
ContextMenuItem::Item { action, .. } => Some(action.id()),
- ContextMenuItem::Separator => None,
+ ContextMenuItem::Static(..) | ContextMenuItem::Separator => None,
}
}
}
@@ -58,6 +61,7 @@ pub struct ContextMenu {
show_count: usize,
anchor_position: Vector2F,
anchor_corner: AnchorCorner,
+ position_mode: OverlayPositionMode,
items: Vec<ContextMenuItem>,
selected_index: Option<usize>,
visible: bool,
@@ -78,7 +82,7 @@ impl View for ContextMenu {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
- cx.set.insert("menu".into());
+ cx.add_identifier("menu");
cx
}
@@ -105,6 +109,7 @@ impl View for ContextMenu {
.with_fit_mode(OverlayFitMode::SnapToWindow)
.with_anchor_position(self.anchor_position)
.with_anchor_corner(self.anchor_corner)
+ .with_position_mode(self.position_mode)
.boxed()
}
@@ -121,6 +126,7 @@ impl ContextMenu {
show_count: 0,
anchor_position: Default::default(),
anchor_corner: AnchorCorner::TopLeft,
+ position_mode: OverlayPositionMode::Window,
items: Default::default(),
selected_index: Default::default(),
visible: Default::default(),
@@ -188,13 +194,13 @@ impl ContextMenu {
}
fn select_first(&mut self, _: &SelectFirst, cx: &mut ViewContext<Self>) {
- self.selected_index = self.items.iter().position(|item| !item.is_separator());
+ self.selected_index = self.items.iter().position(|item| item.is_action());
cx.notify();
}
fn select_last(&mut self, _: &SelectLast, cx: &mut ViewContext<Self>) {
for (ix, item) in self.items.iter().enumerate().rev() {
- if !item.is_separator() {
+ if item.is_action() {
self.selected_index = Some(ix);
cx.notify();
break;
@@ -205,7 +211,7 @@ impl ContextMenu {
fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.selected_index {
for (ix, item) in self.items.iter().enumerate().skip(ix + 1) {
- if !item.is_separator() {
+ if item.is_action() {
self.selected_index = Some(ix);
cx.notify();
break;
@@ -219,7 +225,7 @@ impl ContextMenu {
fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.selected_index {
for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
- if !item.is_separator() {
+ if item.is_action() {
self.selected_index = Some(ix);
cx.notify();
break;
@@ -234,7 +240,7 @@ impl ContextMenu {
&mut self,
anchor_position: Vector2F,
anchor_corner: AnchorCorner,
- items: impl IntoIterator<Item = ContextMenuItem>,
+ items: Vec<ContextMenuItem>,
cx: &mut ViewContext<Self>,
) {
let mut items = items.into_iter().peekable();
@@ -254,6 +260,10 @@ impl ContextMenu {
cx.notify();
}
+ pub fn set_position_mode(&mut self, mode: OverlayPositionMode) {
+ self.position_mode = mode;
+ }
+
fn render_menu_for_measurement(&self, cx: &mut RenderContext<Self>) -> impl Element {
let window_id = cx.window_id();
let style = cx.global::<Settings>().theme.context_menu.clone();
@@ -273,6 +283,9 @@ impl ContextMenu {
.with_style(style.container)
.boxed()
}
+
+ ContextMenuItem::Static(f) => f(cx),
+
ContextMenuItem::Separator => Empty::new()
.collapsed()
.contained()
@@ -302,6 +315,9 @@ impl ContextMenu {
)
.boxed()
}
+
+ ContextMenuItem::Static(_) => Empty::new().boxed(),
+
ContextMenuItem::Separator => Empty::new()
.collapsed()
.constrained()
@@ -339,7 +355,7 @@ impl ContextMenu {
Flex::row()
.with_child(
- Label::new(label.to_string(), style.label.clone())
+ Label::new(label.clone(), style.label.clone())
.contained()
.boxed(),
)
@@ -366,6 +382,9 @@ impl ContextMenu {
.on_drag(MouseButton::Left, |_, _| {})
.boxed()
}
+
+ ContextMenuItem::Static(f) => f(cx),
+
ContextMenuItem::Separator => Empty::new()
.constrained()
.with_height(1.)
@@ -5071,7 +5071,7 @@ impl Editor {
GotoDefinitionKind::Type => project.type_definition(&buffer, head, cx),
});
- cx.spawn(|workspace, mut cx| async move {
+ cx.spawn_labeled("Fetching Definition...", |workspace, mut cx| async move {
let definitions = definitions.await?;
workspace.update(&mut cx, |workspace, cx| {
Editor::navigate_to_definitions(workspace, editor_handle, definitions, cx);
@@ -5151,31 +5151,36 @@ impl Editor {
let project = workspace.project().clone();
let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
- Some(cx.spawn(|workspace, mut cx| async move {
- let locations = references.await?;
- if locations.is_empty() {
- return Ok(());
- }
+ Some(cx.spawn_labeled(
+ "Finding All References...",
+ |workspace, mut cx| async move {
+ let locations = references.await?;
+ if locations.is_empty() {
+ return Ok(());
+ }
- workspace.update(&mut cx, |workspace, cx| {
- let title = locations
- .first()
- .as_ref()
- .map(|location| {
- let buffer = location.buffer.read(cx);
- format!(
- "References to `{}`",
- buffer
- .text_for_range(location.range.clone())
- .collect::<String>()
- )
- })
- .unwrap();
- Self::open_locations_in_multibuffer(workspace, locations, replica_id, title, cx);
- });
+ workspace.update(&mut cx, |workspace, cx| {
+ let title = locations
+ .first()
+ .as_ref()
+ .map(|location| {
+ let buffer = location.buffer.read(cx);
+ format!(
+ "References to `{}`",
+ buffer
+ .text_for_range(location.range.clone())
+ .collect::<String>()
+ )
+ })
+ .unwrap();
+ Self::open_locations_in_multibuffer(
+ workspace, locations, replica_id, title, cx,
+ );
+ });
- Ok(())
- }))
+ Ok(())
+ },
+ ))
}
/// Opens a multibuffer with the given project locations in it
@@ -5454,21 +5459,20 @@ impl Editor {
None => return None,
};
- Some(self.perform_format(project, cx))
+ Some(self.perform_format(project, FormatTrigger::Manual, cx))
}
fn perform_format(
&mut self,
project: ModelHandle<Project>,
+ trigger: FormatTrigger,
cx: &mut ViewContext<'_, Self>,
) -> Task<Result<()>> {
let buffer = self.buffer().clone();
let buffers = buffer.read(cx).all_buffers();
let mut timeout = cx.background().timer(FORMAT_TIMEOUT).fuse();
- let format = project.update(cx, |project, cx| {
- project.format(buffers, true, FormatTrigger::Manual, cx)
- });
+ let format = project.update(cx, |project, cx| project.format(buffers, true, trigger, cx));
cx.spawn(|_, mut cx| async move {
let transaction = futures::select_biased! {
@@ -6428,17 +6432,13 @@ impl View for Editor {
EditorMode::AutoHeight { .. } => "auto_height",
EditorMode::Full => "full",
};
- context.map.insert("mode".into(), mode.into());
+ context.add_key("mode", mode);
if self.pending_rename.is_some() {
- context.set.insert("renaming".into());
+ context.add_identifier("renaming");
}
match self.context_menu.as_ref() {
- Some(ContextMenu::Completions(_)) => {
- context.set.insert("showing_completions".into());
- }
- Some(ContextMenu::CodeActions(_)) => {
- context.set.insert("showing_code_actions".into());
- }
+ Some(ContextMenu::Completions(_)) => context.add_identifier("showing_completions"),
+ Some(ContextMenu::CodeActions(_)) => context.add_identifier("showing_code_actions"),
None => {}
}
@@ -4193,7 +4193,9 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let (_, editor) = cx.add_window(|cx| build_editor(buffer, cx));
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
- let format = editor.update(cx, |editor, cx| editor.perform_format(project.clone(), cx));
+ let format = editor.update(cx, |editor, cx| {
+ editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
+ });
fake_server
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
assert_eq!(
@@ -4225,7 +4227,9 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
futures::future::pending::<()>().await;
unreachable!()
});
- let format = editor.update(cx, |editor, cx| editor.perform_format(project, cx));
+ let format = editor.update(cx, |editor, cx| {
+ editor.perform_format(project, FormatTrigger::Manual, cx)
+ });
cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
cx.foreground().start_waiting();
format.await.unwrap();
@@ -14,7 +14,7 @@ use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, OffsetRangeExt, Point,
SelectionGoal,
};
-use project::{Item as _, Project, ProjectPath};
+use project::{FormatTrigger, Item as _, Project, ProjectPath};
use rpc::proto::{self, update_view};
use settings::Settings;
use smallvec::SmallVec;
@@ -608,7 +608,7 @@ impl Item for Editor {
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.report_event("save editor", cx);
- let format = self.perform_format(project.clone(), cx);
+ let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
let buffers = self.buffer().clone().read(cx).all_buffers();
cx.as_mut().spawn(|mut cx| async move {
format.await?;
@@ -39,7 +39,7 @@ impl<'a> EditorLspTestContext<'a> {
pane::init(cx);
});
- let params = cx.update(AppState::test);
+ let app_state = cx.update(AppState::test);
let file_name = format!(
"file.{}",
@@ -56,10 +56,10 @@ impl<'a> EditorLspTestContext<'a> {
}))
.await;
- let project = Project::test(params.fs.clone(), [], cx).await;
+ let project = Project::test(app_state.fs.clone(), [], cx).await;
project.update(cx, |project, _| project.languages().add(Arc::new(language)));
- params
+ app_state
.fs
.as_fake()
.insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
@@ -13,7 +13,6 @@ use gpui::{
elements::{ChildView, Flex, Label, ParentElement},
serde_json, AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle,
MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
- WeakViewHandle,
};
use isahc::Request;
use language::Buffer;
@@ -24,7 +23,6 @@ use serde::Serialize;
use workspace::{
item::{Item, ItemHandle},
searchable::{SearchableItem, SearchableItemHandle},
- smallvec::SmallVec,
AppState, Workspace,
};
@@ -259,16 +257,10 @@ impl Item for FeedbackEditor {
self.editor.for_each_project_item(cx, f)
}
- fn to_item_events(_: &Self::Event) -> SmallVec<[workspace::item::ItemEvent; 2]> {
- SmallVec::new()
- }
-
fn is_singleton(&self, _: &AppContext) -> bool {
true
}
- fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
-
fn can_save(&self, _: &AppContext) -> bool {
true
}
@@ -295,7 +287,7 @@ impl Item for FeedbackEditor {
_: ModelHandle<Project>,
_: &mut ViewContext<Self>,
) -> Task<anyhow::Result<()>> {
- unreachable!("reload should not have been called")
+ Task::Ready(Some(Ok(())))
}
fn clone_on_split(
@@ -322,20 +314,6 @@ impl Item for FeedbackEditor {
))
}
- fn serialized_item_kind() -> Option<&'static str> {
- None
- }
-
- fn deserialize(
- _: ModelHandle<Project>,
- _: WeakViewHandle<Workspace>,
- _: workspace::WorkspaceId,
- _: workspace::ItemId,
- _: &mut ViewContext<workspace::Pane>,
- ) -> Task<anyhow::Result<ViewHandle<Self>>> {
- unreachable!()
- }
-
fn as_searchable(&self, handle: &ViewHandle<Self>) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(handle.clone()))
}
@@ -23,6 +23,7 @@ pub struct FileFinder {
latest_search_id: usize,
latest_search_did_cancel: bool,
latest_search_query: String,
+ relative_to: Option<Arc<Path>>,
matches: Vec<PathMatch>,
selected: Option<(usize, Arc<Path>)>,
cancel_flag: Arc<AtomicBool>,
@@ -90,7 +91,11 @@ impl FileFinder {
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
workspace.toggle_modal(cx, |workspace, cx| {
let project = workspace.project().clone();
- let finder = cx.add_view(|cx| Self::new(project, cx));
+ let relative_to = workspace
+ .active_item(cx)
+ .and_then(|item| item.project_path(cx))
+ .map(|project_path| project_path.path.clone());
+ let finder = cx.add_view(|cx| Self::new(project, relative_to, cx));
cx.subscribe(&finder, Self::on_event).detach();
finder
});
@@ -115,7 +120,11 @@ impl FileFinder {
}
}
- pub fn new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
+ pub fn new(
+ project: ModelHandle<Project>,
+ relative_to: Option<Arc<Path>>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
let handle = cx.weak_handle();
cx.observe(&project, Self::project_updated).detach();
Self {
@@ -125,6 +134,7 @@ impl FileFinder {
latest_search_id: 0,
latest_search_did_cancel: false,
latest_search_query: String::new(),
+ relative_to,
matches: Vec::new(),
selected: None,
cancel_flag: Arc::new(AtomicBool::new(false)),
@@ -137,6 +147,7 @@ impl FileFinder {
}
fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()> {
+ let relative_to = self.relative_to.clone();
let worktrees = self
.project
.read(cx)
@@ -165,6 +176,7 @@ impl FileFinder {
let matches = fuzzy::match_path_sets(
candidate_sets.as_slice(),
&query,
+ relative_to,
false,
100,
&cancel_flag,
@@ -377,7 +389,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
+ cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
let query = "hi".to_string();
finder
@@ -453,7 +465,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
+ cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
finder
.update(cx, |f, cx| f.spawn_search("hi".into(), cx))
.await;
@@ -479,7 +491,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
+ cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
// Even though there is only one worktree, that worktree's filename
// is included in the matching, because the worktree is a single file.
@@ -532,8 +544,9 @@ mod tests {
let (_, workspace) = cx.add_window(|cx| {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
+
let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
+ cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
// Run a search that matches two files with the same relative path.
finder
@@ -551,6 +564,48 @@ mod tests {
});
}
+ #[gpui::test]
+ async fn test_path_distance_ordering(cx: &mut gpui::TestAppContext) {
+ cx.foreground().forbid_parking();
+
+ let app_state = cx.update(AppState::test);
+ app_state
+ .fs
+ .as_fake()
+ .insert_tree(
+ "/root",
+ json!({
+ "dir1": { "a.txt": "" },
+ "dir2": {
+ "a.txt": "",
+ "b.txt": ""
+ }
+ }),
+ )
+ .await;
+
+ let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
+ let (_, workspace) = cx.add_window(|cx| {
+ Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
+ });
+
+ // When workspace has an active item, sort items which are closer to that item
+ // first when they have the same name. In this case, b.txt is closer to dir2's a.txt
+ // so that one should be sorted earlier
+ let b_path = Some(Arc::from(Path::new("/root/dir2/b.txt")));
+ let (_, finder) =
+ cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), b_path, cx));
+
+ finder
+ .update(cx, |f, cx| f.spawn_search("a.txt".into(), cx))
+ .await;
+
+ finder.read_with(cx, |f, _| {
+ assert_eq!(f.matches[0].path.as_ref(), Path::new("dir2/a.txt"));
+ assert_eq!(f.matches[1].path.as_ref(), Path::new("dir1/a.txt"));
+ });
+ }
+
#[gpui::test]
async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) {
let app_state = cx.update(AppState::test);
@@ -573,7 +628,7 @@ mod tests {
Workspace::new(Default::default(), 0, project, |_, _| unimplemented!(), cx)
});
let (_, finder) =
- cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx));
+ cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), None, cx));
finder
.update(cx, |f, cx| f.spawn_search("dir".into(), cx))
.await;
@@ -443,6 +443,7 @@ mod tests {
positions: Vec::new(),
path: candidate.path.clone(),
path_prefix: "".into(),
+ distance_to_relative_ancestor: usize::MAX,
},
);
@@ -25,6 +25,9 @@ pub struct PathMatch {
pub worktree_id: usize,
pub path: Arc<Path>,
pub path_prefix: Arc<str>,
+ /// Number of steps removed from a shared parent with the relative path
+ /// Used to order closer paths first in the search list
+ pub distance_to_relative_ancestor: usize,
}
pub trait PathMatchCandidateSet<'a>: Send + Sync {
@@ -78,6 +81,11 @@ impl Ord for PathMatch {
.partial_cmp(&other.score)
.unwrap_or(Ordering::Equal)
.then_with(|| self.worktree_id.cmp(&other.worktree_id))
+ .then_with(|| {
+ other
+ .distance_to_relative_ancestor
+ .cmp(&self.distance_to_relative_ancestor)
+ })
.then_with(|| self.path.cmp(&other.path))
}
}
@@ -85,6 +93,7 @@ impl Ord for PathMatch {
pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
candidate_sets: &'a [Set],
query: &str,
+ relative_to: Option<Arc<Path>>,
smart_case: bool,
max_results: usize,
cancel_flag: &AtomicBool,
@@ -111,6 +120,7 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
background
.scoped(|scope| {
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
+ let relative_to = relative_to.clone();
scope.spawn(async move {
let segment_start = segment_idx * segment_size;
let segment_end = segment_start + segment_size;
@@ -149,6 +159,15 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
positions: Vec::new(),
path: candidate.path.clone(),
path_prefix: candidate_set.prefix(),
+ distance_to_relative_ancestor: relative_to.as_ref().map_or(
+ usize::MAX,
+ |relative_to| {
+ distance_between_paths(
+ candidate.path.as_ref(),
+ relative_to.as_ref(),
+ )
+ },
+ ),
},
);
}
@@ -172,3 +191,13 @@ pub async fn match_path_sets<'a, Set: PathMatchCandidateSet<'a>>(
}
results
}
+
+/// Compute the distance from a given path to some other path
+/// If there is no shared path, returns usize::MAX
+fn distance_between_paths(path: &Path, relative_to: &Path) -> usize {
+ let mut path_components = path.components();
+ let mut relative_components = relative_to.components();
+
+ while path_components.next() == relative_components.next() {}
+ path_components.count() + relative_components.count() + 1
+}
@@ -31,7 +31,7 @@ use uuid::Uuid;
pub use action::*;
use callback_collection::CallbackCollection;
-use collections::{hash_map::Entry, HashMap, HashSet, VecDeque};
+use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
pub use menu::*;
use platform::Event;
#[cfg(any(test, feature = "test-support"))]
@@ -86,7 +86,7 @@ pub trait View: Entity + Sized {
}
fn default_keymap_context() -> keymap_matcher::KeymapContext {
let mut cx = keymap_matcher::KeymapContext::default();
- cx.set.insert(Self::ui_name().into());
+ cx.add_identifier(Self::ui_name());
cx
}
fn debug_json(&self, _: &AppContext) -> serde_json::Value {
@@ -474,6 +474,7 @@ type WindowBoundsCallback = Box<dyn FnMut(WindowBounds, Uuid, &mut MutableAppCon
type KeystrokeCallback = Box<
dyn FnMut(&Keystroke, &MatchResult, Option<&Box<dyn Action>>, &mut MutableAppContext) -> bool,
>;
+type ActiveLabeledTasksCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
@@ -503,6 +504,7 @@ pub struct MutableAppContext {
window_fullscreen_observations: CallbackCollection<usize, WindowFullscreenCallback>,
window_bounds_observations: CallbackCollection<usize, WindowBoundsCallback>,
keystroke_observations: CallbackCollection<usize, KeystrokeCallback>,
+ active_labeled_task_observations: CallbackCollection<(), ActiveLabeledTasksCallback>,
#[allow(clippy::type_complexity)]
presenters_and_platform_windows:
@@ -514,6 +516,8 @@ pub struct MutableAppContext {
pending_flushes: usize,
flushing_effects: bool,
halt_action_dispatch: bool,
+ next_labeled_task_id: usize,
+ active_labeled_tasks: BTreeMap<usize, &'static str>,
}
impl MutableAppContext {
@@ -562,6 +566,7 @@ impl MutableAppContext {
window_bounds_observations: Default::default(),
keystroke_observations: Default::default(),
action_dispatch_observations: Default::default(),
+ active_labeled_task_observations: Default::default(),
presenters_and_platform_windows: Default::default(),
foreground,
pending_effects: VecDeque::new(),
@@ -570,6 +575,8 @@ impl MutableAppContext {
pending_flushes: 0,
flushing_effects: false,
halt_action_dispatch: false,
+ next_labeled_task_id: 0,
+ active_labeled_tasks: Default::default(),
}
}
@@ -794,6 +801,12 @@ impl MutableAppContext {
window.screen().display_uuid()
}
+ pub fn active_labeled_tasks<'a>(
+ &'a self,
+ ) -> impl DoubleEndedIterator<Item = &'static str> + 'a {
+ self.active_labeled_tasks.values().cloned()
+ }
+
pub fn render_view(&mut self, params: RenderParams) -> Result<ElementBox> {
let window_id = params.window_id;
let view_id = params.view_id;
@@ -1160,6 +1173,19 @@ impl MutableAppContext {
)
}
+ pub fn observe_active_labeled_tasks<F>(&mut self, callback: F) -> Subscription
+ where
+ F: 'static + FnMut(&mut MutableAppContext) -> bool,
+ {
+ let subscription_id = post_inc(&mut self.next_subscription_id);
+ self.active_labeled_task_observations
+ .add_callback((), subscription_id, Box::new(callback));
+ Subscription::ActiveLabeledTasksObservation(
+ self.active_labeled_task_observations
+ .subscribe((), subscription_id),
+ )
+ }
+
pub fn defer(&mut self, callback: impl 'static + FnOnce(&mut MutableAppContext)) {
self.pending_effects.push_back(Effect::Deferred {
callback: Box::new(callback),
@@ -2042,6 +2068,17 @@ impl MutableAppContext {
handled_by,
result,
} => self.handle_keystroke_effect(window_id, keystroke, handled_by, result),
+ Effect::ActiveLabeledTasksChanged => {
+ self.handle_active_labeled_tasks_changed_effect()
+ }
+ Effect::ActiveLabeledTasksObservation {
+ subscription_id,
+ callback,
+ } => self.active_labeled_task_observations.add_callback(
+ (),
+ subscription_id,
+ callback,
+ ),
}
self.pending_notifications.clear();
self.remove_dropped_entities();
@@ -2449,26 +2486,68 @@ impl MutableAppContext {
}
}
+ fn handle_active_labeled_tasks_changed_effect(&mut self) {
+ self.active_labeled_task_observations
+ .clone()
+ .emit((), self, move |callback, this| {
+ callback(this);
+ true
+ });
+ }
+
pub fn focus(&mut self, window_id: usize, view_id: Option<usize>) {
self.pending_effects
.push_back(Effect::Focus { window_id, view_id });
}
- pub fn spawn<F, Fut, T>(&self, f: F) -> Task<T>
+ fn spawn_internal<F, Fut, T>(&mut self, task_name: Option<&'static str>, f: F) -> Task<T>
where
F: FnOnce(AsyncAppContext) -> Fut,
Fut: 'static + Future<Output = T>,
T: 'static,
{
+ let label_id = task_name.map(|task_name| {
+ let id = post_inc(&mut self.next_labeled_task_id);
+ self.active_labeled_tasks.insert(id, task_name);
+ self.pending_effects
+ .push_back(Effect::ActiveLabeledTasksChanged);
+ id
+ });
+
let future = f(self.to_async());
let cx = self.to_async();
self.foreground.spawn(async move {
let result = future.await;
- cx.0.borrow_mut().flush_effects();
+ let mut cx = cx.0.borrow_mut();
+
+ if let Some(completed_label_id) = label_id {
+ cx.active_labeled_tasks.remove(&completed_label_id);
+ cx.pending_effects
+ .push_back(Effect::ActiveLabeledTasksChanged);
+ }
+ cx.flush_effects();
result
})
}
+ pub fn spawn_labeled<F, Fut, T>(&mut self, task_name: &'static str, f: F) -> Task<T>
+ where
+ F: FnOnce(AsyncAppContext) -> Fut,
+ Fut: 'static + Future<Output = T>,
+ T: 'static,
+ {
+ self.spawn_internal(Some(task_name), f)
+ }
+
+ pub fn spawn<F, Fut, T>(&mut self, f: F) -> Task<T>
+ where
+ F: FnOnce(AsyncAppContext) -> Fut,
+ Fut: 'static + Future<Output = T>,
+ T: 'static,
+ {
+ self.spawn_internal(None, f)
+ }
+
pub fn to_async(&self) -> AsyncAppContext {
AsyncAppContext(self.weak_self.as_ref().unwrap().upgrade().unwrap())
}
@@ -2907,6 +2986,11 @@ pub enum Effect {
window_id: usize,
callback: WindowShouldCloseSubscriptionCallback,
},
+ ActiveLabeledTasksChanged,
+ ActiveLabeledTasksObservation {
+ subscription_id: usize,
+ callback: ActiveLabeledTasksCallback,
+ },
}
impl Debug for Effect {
@@ -3066,6 +3150,16 @@ impl Debug for Effect {
)
.field("result", result)
.finish(),
+ Effect::ActiveLabeledTasksChanged => {
+ f.debug_struct("Effect::ActiveLabeledTasksChanged").finish()
+ }
+ Effect::ActiveLabeledTasksObservation {
+ subscription_id,
+ callback: _,
+ } => f
+ .debug_struct("Effect::ActiveLabeledTasksObservation")
+ .field("subscription_id", subscription_id)
+ .finish(),
}
}
}
@@ -3480,7 +3574,7 @@ impl<'a, T: Entity> ModelContext<'a, T> {
WeakModelHandle::new(self.model_id)
}
- pub fn spawn<F, Fut, S>(&self, f: F) -> Task<S>
+ pub fn spawn<F, Fut, S>(&mut self, f: F) -> Task<S>
where
F: FnOnce(ModelHandle<T>, AsyncAppContext) -> Fut,
Fut: 'static + Future<Output = S>,
@@ -3490,7 +3584,7 @@ impl<'a, T: Entity> ModelContext<'a, T> {
self.app.spawn(|cx| f(handle, cx))
}
- pub fn spawn_weak<F, Fut, S>(&self, f: F) -> Task<S>
+ pub fn spawn_weak<F, Fut, S>(&mut self, f: F) -> Task<S>
where
F: FnOnce(WeakModelHandle<T>, AsyncAppContext) -> Fut,
Fut: 'static + Future<Output = S>,
@@ -3947,6 +4041,23 @@ impl<'a, T: View> ViewContext<'a, T> {
})
}
+ pub fn observe_active_labeled_tasks<F>(&mut self, mut callback: F) -> Subscription
+ where
+ F: 'static + FnMut(&mut T, &mut ViewContext<T>),
+ {
+ let observer = self.weak_handle();
+ self.app.observe_active_labeled_tasks(move |cx| {
+ if let Some(observer) = observer.upgrade(cx) {
+ observer.update(cx, |observer, cx| {
+ callback(observer, cx);
+ });
+ true
+ } else {
+ false
+ }
+ })
+ }
+
pub fn emit(&mut self, payload: T::Event) {
self.app.pending_effects.push_back(Effect::Event {
entity_id: self.view_id,
@@ -3993,7 +4104,17 @@ impl<'a, T: View> ViewContext<'a, T> {
self.app.halt_action_dispatch = false;
}
- pub fn spawn<F, Fut, S>(&self, f: F) -> Task<S>
+ pub fn spawn_labeled<F, Fut, S>(&mut self, task_label: &'static str, f: F) -> Task<S>
+ where
+ F: FnOnce(ViewHandle<T>, AsyncAppContext) -> Fut,
+ Fut: 'static + Future<Output = S>,
+ S: 'static,
+ {
+ let handle = self.handle();
+ self.app.spawn_labeled(task_label, |cx| f(handle, cx))
+ }
+
+ pub fn spawn<F, Fut, S>(&mut self, f: F) -> Task<S>
where
F: FnOnce(ViewHandle<T>, AsyncAppContext) -> Fut,
Fut: 'static + Future<Output = S>,
@@ -4003,7 +4124,7 @@ impl<'a, T: View> ViewContext<'a, T> {
self.app.spawn(|cx| f(handle, cx))
}
- pub fn spawn_weak<F, Fut, S>(&self, f: F) -> Task<S>
+ pub fn spawn_weak<F, Fut, S>(&mut self, f: F) -> Task<S>
where
F: FnOnce(WeakViewHandle<T>, AsyncAppContext) -> Fut,
Fut: 'static + Future<Output = S>,
@@ -5121,6 +5242,9 @@ pub enum Subscription {
KeystrokeObservation(callback_collection::Subscription<usize, KeystrokeCallback>),
ReleaseObservation(callback_collection::Subscription<usize, ReleaseObservationCallback>),
ActionObservation(callback_collection::Subscription<(), ActionObservationCallback>),
+ ActiveLabeledTasksObservation(
+ callback_collection::Subscription<(), ActiveLabeledTasksCallback>,
+ ),
}
impl Subscription {
@@ -5137,6 +5261,7 @@ impl Subscription {
Subscription::KeystrokeObservation(subscription) => subscription.id(),
Subscription::ReleaseObservation(subscription) => subscription.id(),
Subscription::ActionObservation(subscription) => subscription.id(),
+ Subscription::ActiveLabeledTasksObservation(subscription) => subscription.id(),
}
}
@@ -5153,6 +5278,7 @@ impl Subscription {
Subscription::WindowBoundsObservation(subscription) => subscription.detach(),
Subscription::ReleaseObservation(subscription) => subscription.detach(),
Subscription::ActionObservation(subscription) => subscription.detach(),
+ Subscription::ActiveLabeledTasksObservation(subscription) => subscription.detach(),
}
}
}
@@ -5161,6 +5287,7 @@ impl Subscription {
mod tests {
use super::*;
use crate::{actions, elements::*, impl_actions, MouseButton, MouseButtonEvent};
+ use postage::{sink::Sink, stream::Stream};
use serde::Deserialize;
use smol::future::poll_once;
use std::{
@@ -6512,12 +6639,12 @@ mod tests {
let mut view_1 = View::new(1);
let mut view_2 = View::new(2);
let mut view_3 = View::new(3);
- view_1.keymap_context.set.insert("a".into());
- view_2.keymap_context.set.insert("a".into());
- view_2.keymap_context.set.insert("b".into());
- view_3.keymap_context.set.insert("a".into());
- view_3.keymap_context.set.insert("b".into());
- view_3.keymap_context.set.insert("c".into());
+ view_1.keymap_context.add_identifier("a");
+ view_2.keymap_context.add_identifier("a");
+ view_2.keymap_context.add_identifier("b");
+ view_3.keymap_context.add_identifier("a");
+ view_3.keymap_context.add_identifier("b");
+ view_3.keymap_context.add_identifier("c");
let (window_id, view_1) = cx.add_window(Default::default(), |_| view_1);
let view_2 = cx.add_view(&view_1, |_| view_2);
@@ -6776,6 +6903,26 @@ mod tests {
assert_eq!(presenter.borrow().rendered_views.len(), 1);
}
+ #[crate::test(self)]
+ async fn test_labeled_tasks(cx: &mut TestAppContext) {
+ assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next()));
+ let (mut sender, mut reciever) = postage::oneshot::channel::<()>();
+ let task = cx
+ .update(|cx| cx.spawn_labeled("Test Label", |_| async move { reciever.recv().await }));
+
+ assert_eq!(
+ Some("Test Label"),
+ cx.update(|cx| cx.active_labeled_tasks().next())
+ );
+ sender
+ .send(())
+ .await
+ .expect("Could not send message to complete task");
+ task.await;
+
+ assert_eq!(None, cx.update(|cx| cx.active_labeled_tasks().next()));
+ }
+
#[crate::test(self)]
async fn test_window_activation(cx: &mut TestAppContext) {
struct View(&'static str);
@@ -16,6 +16,14 @@ pub trait Action: 'static {
Self: Sized;
}
+impl std::fmt::Debug for dyn Action {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("dyn Action")
+ .field("namespace", &self.namespace())
+ .field("name", &self.name())
+ .finish()
+ }
+}
/// Define a set of unit struct types that all implement the `Action` trait.
///
/// The first argument is a namespace that will be associated with each of
@@ -11,9 +11,46 @@ pub enum MenuItem<'a> {
Action {
name: &'a str,
action: Box<dyn Action>,
+ os_action: Option<OsAction>,
},
}
+impl<'a> MenuItem<'a> {
+ pub fn separator() -> Self {
+ Self::Separator
+ }
+
+ pub fn submenu(menu: Menu<'a>) -> Self {
+ Self::Submenu(menu)
+ }
+
+ pub fn action(name: &'a str, action: impl Action) -> Self {
+ Self::Action {
+ name,
+ action: Box::new(action),
+ os_action: None,
+ }
+ }
+
+ pub fn os_action(name: &'a str, action: impl Action, os_action: OsAction) -> Self {
+ Self::Action {
+ name,
+ action: Box::new(action),
+ os_action: Some(os_action),
+ }
+ }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub enum OsAction {
+ Cut,
+ Copy,
+ Paste,
+ SelectAll,
+ Undo,
+ Redo,
+}
+
impl MutableAppContext {
pub fn set_menus(&mut self, menus: Vec<Menu>) {
self.foreground_platform
@@ -363,6 +363,7 @@ impl<T: Element> AnyElement for Lifecycle<T> {
value
}
}
+
_ => panic!("invalid element lifecycle state"),
}
}
@@ -308,7 +308,9 @@ impl Element for Flex {
}
}
}
+
child.paint(child_origin, visible_bounds, cx);
+
match self.axis {
Axis::Horizontal => child_origin += vec2f(child.size().x(), 0.0),
Axis::Vertical => child_origin += vec2f(0.0, child.size().y()),
@@ -5,7 +5,7 @@ mod keystroke;
use std::{any::TypeId, fmt::Debug};
-use collections::{BTreeMap, HashMap};
+use collections::HashMap;
use smallvec::SmallVec;
use crate::Action;
@@ -68,8 +68,8 @@ impl KeymapMatcher {
/// There exist bindings which are still waiting for more keys.
/// MatchResult::Complete(matches) =>
/// 1 or more bindings have recieved the necessary key presses.
- /// The order of the matched actions is by order in the keymap file first and
- /// position of the matching view second.
+ /// The order of the matched actions is by position of the matching first,
+ // and order in the keymap second.
pub fn push_keystroke(
&mut self,
keystroke: Keystroke,
@@ -80,8 +80,7 @@ impl KeymapMatcher {
// and then the order the binding matched in the view tree second.
// The key is the reverse position of the binding in the bindings list so that later bindings
// match before earlier ones in the user's config
- let mut matched_bindings: BTreeMap<usize, Vec<(usize, Box<dyn Action>)>> =
- Default::default();
+ let mut matched_bindings: Vec<(usize, Box<dyn Action>)> = Default::default();
let first_keystroke = self.pending_keystrokes.is_empty();
self.pending_keystrokes.push(keystroke.clone());
@@ -105,14 +104,11 @@ impl KeymapMatcher {
}
}
- for (order, binding) in self.keymap.bindings().iter().rev().enumerate() {
+ for binding in self.keymap.bindings().iter().rev() {
match binding.match_keys_and_context(&self.pending_keystrokes, &self.contexts[i..])
{
BindingMatchResult::Complete(action) => {
- matched_bindings
- .entry(order)
- .or_default()
- .push((*view_id, action));
+ matched_bindings.push((*view_id, action));
}
BindingMatchResult::Partial => {
self.pending_views
@@ -131,7 +127,7 @@ impl KeymapMatcher {
if !matched_bindings.is_empty() {
// Collect the sorted matched bindings into the final vec for ease of use
// Matched bindings are in order by precedence
- MatchResult::Matches(matched_bindings.into_values().flatten().collect())
+ MatchResult::Matches(matched_bindings)
} else if any_pending {
MatchResult::Pending
} else {
@@ -225,15 +221,47 @@ mod tests {
use super::*;
+ #[test]
+ fn test_keymap_and_view_ordering() -> Result<()> {
+ actions!(test, [EditorAction, ProjectPanelAction]);
+
+ let mut editor = KeymapContext::default();
+ editor.add_identifier("Editor");
+
+ let mut project_panel = KeymapContext::default();
+ project_panel.add_identifier("ProjectPanel");
+
+ // Editor 'deeper' in than project panel
+ let dispatch_path = vec![(2, editor), (1, project_panel)];
+
+ // But editor actions 'higher' up in keymap
+ let keymap = Keymap::new(vec![
+ Binding::new("left", EditorAction, Some("Editor")),
+ Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
+ ]);
+
+ let mut matcher = KeymapMatcher::new(keymap);
+
+ assert_eq!(
+ matcher.push_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
+ MatchResult::Matches(vec![
+ (2, Box::new(EditorAction)),
+ (1, Box::new(ProjectPanelAction)),
+ ]),
+ );
+
+ Ok(())
+ }
+
#[test]
fn test_push_keystroke() -> Result<()> {
- actions!(test, [B, AB, C, D, DA]);
+ actions!(test, [B, AB, C, D, DA, E, EF]);
let mut context1 = KeymapContext::default();
- context1.set.insert("1".into());
+ context1.add_identifier("1");
let mut context2 = KeymapContext::default();
- context2.set.insert("2".into());
+ context2.add_identifier("2");
let dispatch_path = vec![(2, context2), (1, context1)];
@@ -286,6 +314,7 @@ mod tests {
matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
);
+
// If none of the d action handlers consume the binding, a pending
// binding may then be used
assert_eq!(
@@ -366,22 +395,22 @@ mod tests {
let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
let mut context = KeymapContext::default();
- context.set.insert("a".into());
+ context.add_identifier("a");
assert!(!predicate.eval(&[context]));
let mut context = KeymapContext::default();
- context.set.insert("a".into());
- context.set.insert("b".into());
+ context.add_identifier("a");
+ context.add_identifier("b");
assert!(predicate.eval(&[context]));
let mut context = KeymapContext::default();
- context.set.insert("a".into());
- context.map.insert("c".into(), "x".into());
+ context.add_identifier("a");
+ context.add_key("c", "x");
assert!(!predicate.eval(&[context]));
let mut context = KeymapContext::default();
- context.set.insert("a".into());
- context.map.insert("c".into(), "d".into());
+ context.add_identifier("a");
+ context.add_key("c", "d");
assert!(predicate.eval(&[context]));
let predicate = KeymapContextPredicate::parse("!a").unwrap();
@@ -421,10 +450,11 @@ mod tests {
assert!(!predicate.eval(&contexts[6..]));
fn context_set(names: &[&str]) -> KeymapContext {
- KeymapContext {
- set: names.iter().copied().map(str::to_string).collect(),
- ..Default::default()
- }
+ let mut keymap = KeymapContext::new();
+ names
+ .iter()
+ .for_each(|name| keymap.add_identifier(name.to_string()));
+ keymap
}
}
@@ -447,10 +477,10 @@ mod tests {
]);
let mut context_a = KeymapContext::default();
- context_a.set.insert("a".into());
+ context_a.add_identifier("a");
let mut context_b = KeymapContext::default();
- context_b.set.insert("b".into());
+ context_b.add_identifier("b");
let mut matcher = KeymapMatcher::new(keymap);
@@ -495,7 +525,7 @@ mod tests {
matcher.clear_pending();
let mut context_c = KeymapContext::default();
- context_c.set.insert("c".into());
+ context_c.add_identifier("c");
// Pending keystrokes are maintained per-view
assert_eq!(
@@ -1,13 +1,22 @@
+use std::borrow::Cow;
+
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct KeymapContext {
- pub set: HashSet<String>,
- pub map: HashMap<String, String>,
+ set: HashSet<Cow<'static, str>>,
+ map: HashMap<Cow<'static, str>, Cow<'static, str>>,
}
impl KeymapContext {
+ pub fn new() -> Self {
+ KeymapContext {
+ set: HashSet::default(),
+ map: HashMap::default(),
+ }
+ }
+
pub fn extend(&mut self, other: &Self) {
for v in &other.set {
self.set.insert(v.clone());
@@ -16,6 +25,18 @@ impl KeymapContext {
self.map.insert(k.clone(), v.clone());
}
}
+
+ pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
+ self.set.insert(identifier.into());
+ }
+
+ pub fn add_key<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
+ &mut self,
+ key: S1,
+ value: S2,
+ ) {
+ self.map.insert(key.into(), value.into());
+ }
}
#[derive(Debug, Eq, PartialEq)]
@@ -46,12 +67,12 @@ impl KeymapContextPredicate {
Self::Identifier(name) => (&context.set).contains(name.as_str()),
Self::Equal(left, right) => context
.map
- .get(left)
+ .get(left.as_str())
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.map
- .get(left)
+ .get(left.as_str())
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),
@@ -98,6 +98,31 @@ unsafe fn build_classes() {
sel!(handleGPUIMenuItem:),
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
);
+ // Add menu item handlers so that OS save panels have the correct key commands
+ decl.add_method(
+ sel!(cut:),
+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(copy:),
+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(paste:),
+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(selectAll:),
+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(undo:),
+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+ );
+ decl.add_method(
+ sel!(redo:),
+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+ );
decl.add_method(
sel!(validateMenuItem:),
validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
@@ -193,11 +218,25 @@ impl MacForegroundPlatform {
) -> id {
match item {
MenuItem::Separator => NSMenuItem::separatorItem(nil),
- MenuItem::Action { name, action } => {
+ MenuItem::Action {
+ name,
+ action,
+ os_action,
+ } => {
+ // TODO
let keystrokes = keystroke_matcher
.bindings_for_action_type(action.as_any().type_id())
.find(|binding| binding.action().eq(action.as_ref()))
.map(|binding| binding.keystrokes());
+ let selector = match os_action {
+ Some(crate::OsAction::Cut) => selector("cut:"),
+ Some(crate::OsAction::Copy) => selector("copy:"),
+ Some(crate::OsAction::Paste) => selector("paste:"),
+ Some(crate::OsAction::SelectAll) => selector("selectAll:"),
+ Some(crate::OsAction::Undo) => selector("undo:"),
+ Some(crate::OsAction::Redo) => selector("redo:"),
+ None => selector("handleGPUIMenuItem:"),
+ };
let item;
if let Some(keystrokes) = keystrokes {
@@ -218,7 +257,7 @@ impl MacForegroundPlatform {
item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(
ns_string(name),
- selector("handleGPUIMenuItem:"),
+ selector,
ns_string(key_to_native(&keystroke.key).as_ref()),
)
.autorelease();
@@ -240,7 +279,7 @@ impl MacForegroundPlatform {
item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(
ns_string(&name),
- selector("handleGPUIMenuItem:"),
+ selector,
ns_string(""),
)
.autorelease();
@@ -249,7 +288,7 @@ impl MacForegroundPlatform {
item = NSMenuItem::alloc(nil)
.initWithTitle_action_keyEquivalent_(
ns_string(name),
- selector("handleGPUIMenuItem:"),
+ selector,
ns_string(""),
)
.autorelease();
@@ -737,6 +737,7 @@ impl platform::Window for Window {
let title = ns_string(title);
let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
let _: () = msg_send![window, setTitle: title];
+ self.0.borrow().move_traffic_light();
}
}
@@ -20,6 +20,7 @@ fn main() {
items: vec![MenuItem::Action {
name: "Quit",
action: Box::new(Quit),
+ os_action: None,
}],
}]);
@@ -126,7 +126,7 @@ impl<D: PickerDelegate> View for Picker<D> {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
- cx.set.insert("menu".into());
+ cx.add_identifier("menu");
cx
}
@@ -1314,7 +1314,7 @@ impl View for ProjectPanel {
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
let mut cx = Self::default_keymap_context();
- cx.set.insert("menu".into());
+ cx.add_identifier("menu");
cx
}
}
@@ -8,7 +8,7 @@ publish = false
path = "src/rope.rs"
[dependencies]
-bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "dac565a90e8f9245f48ff46225c915dc50f76920" }
+bromberg_sl2 = { git = "https://github.com/zed-industries/bromberg_sl2", rev = "950bc5482c216c395049ae33ae4501e08975f17f" }
smallvec = { version = "1.6", features = ["union"] }
sum_tree = { path = "../sum_tree" }
arrayvec = "0.7.1"
@@ -16,7 +16,7 @@ message Envelope {
Error error = 6;
Ping ping = 7;
Test test = 8;
-
+
CreateRoom create_room = 9;
CreateRoomResponse create_room_response = 10;
JoinRoom join_room = 11;
@@ -206,7 +206,8 @@ message Room {
uint64 id = 1;
repeated Participant participants = 2;
repeated PendingParticipant pending_participants = 3;
- string live_kit_room = 4;
+ repeated Follower followers = 4;
+ string live_kit_room = 5;
}
message Participant {
@@ -227,6 +228,12 @@ message ParticipantProject {
repeated string worktree_root_names = 2;
}
+message Follower {
+ PeerId leader_id = 1;
+ PeerId follower_id = 2;
+ uint64 project_id = 3;
+}
+
message ParticipantLocation {
oneof variant {
SharedProject shared_project = 1;
@@ -6,4 +6,4 @@ pub use conn::Connection;
pub use peer::*;
mod macros;
-pub const PROTOCOL_VERSION: u32 = 46;
+pub const PROTOCOL_VERSION: u32 = 49;
@@ -248,15 +248,15 @@ impl Item for ProjectSearchView {
tab_theme: &theme::Tab,
cx: &gpui::AppContext,
) -> ElementBox {
- let settings = cx.global::<Settings>();
- let search_theme = &settings.theme.search;
Flex::row()
.with_child(
Svg::new("icons/magnifying_glass_12.svg")
.with_color(tab_theme.label.text.color)
.constrained()
- .with_width(search_theme.tab_icon_width)
+ .with_width(tab_theme.icon_width)
.aligned()
+ .contained()
+ .with_margin_right(tab_theme.spacing)
.boxed(),
)
.with_children(self.model.read(cx).active_query.as_ref().map(|query| {
@@ -264,8 +264,6 @@ impl Item for ProjectSearchView {
Label::new(query_text, tab_theme.label.clone())
.aligned()
- .contained()
- .with_margin_left(search_theme.tab_icon_spacing)
.boxed()
}))
.boxed()
@@ -21,7 +21,7 @@ use gpui::{
use project::{LocalWorktree, Project};
use serde::Deserialize;
use settings::{Settings, TerminalBlink, WorkingDirectory};
-use smallvec::SmallVec;
+use smallvec::{smallvec, SmallVec};
use smol::Timer;
use terminal::{
alacritty_terminal::{
@@ -469,53 +469,50 @@ impl View for TerminalView {
let mut context = Self::default_keymap_context();
let mode = self.terminal.read(cx).last_content.mode;
- context.map.insert(
- "screen".to_string(),
- (if mode.contains(TermMode::ALT_SCREEN) {
+ context.add_key(
+ "screen",
+ if mode.contains(TermMode::ALT_SCREEN) {
"alt"
} else {
"normal"
- })
- .to_string(),
+ },
);
if mode.contains(TermMode::APP_CURSOR) {
- context.set.insert("DECCKM".to_string());
+ context.add_identifier("DECCKM");
}
if mode.contains(TermMode::APP_KEYPAD) {
- context.set.insert("DECPAM".to_string());
- }
- //Note the ! here
- if !mode.contains(TermMode::APP_KEYPAD) {
- context.set.insert("DECPNM".to_string());
+ context.add_identifier("DECPAM");
+ } else {
+ context.add_identifier("DECPNM");
}
if mode.contains(TermMode::SHOW_CURSOR) {
- context.set.insert("DECTCEM".to_string());
+ context.add_identifier("DECTCEM");
}
if mode.contains(TermMode::LINE_WRAP) {
- context.set.insert("DECAWM".to_string());
+ context.add_identifier("DECAWM");
}
if mode.contains(TermMode::ORIGIN) {
- context.set.insert("DECOM".to_string());
+ context.add_identifier("DECOM");
}
if mode.contains(TermMode::INSERT) {
- context.set.insert("IRM".to_string());
+ context.add_identifier("IRM");
}
//LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
- context.set.insert("LNM".to_string());
+ context.add_identifier("LNM");
}
if mode.contains(TermMode::FOCUS_IN_OUT) {
- context.set.insert("report_focus".to_string());
+ context.add_identifier("report_focus");
}
if mode.contains(TermMode::ALTERNATE_SCROLL) {
- context.set.insert("alternate_scroll".to_string());
+ context.add_identifier("alternate_scroll");
}
if mode.contains(TermMode::BRACKETED_PASTE) {
- context.set.insert("bracketed_paste".to_string());
+ context.add_identifier("bracketed_paste");
}
if mode.intersects(TermMode::MOUSE_MODE) {
- context.set.insert("any_mouse_reporting".to_string());
+ context.add_identifier("any_mouse_reporting");
}
{
let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
@@ -527,9 +524,7 @@ impl View for TerminalView {
} else {
"off"
};
- context
- .map
- .insert("mouse_reporting".to_string(), mouse_reporting.to_string());
+ context.add_key("mouse_reporting", mouse_reporting);
}
{
let format = if mode.contains(TermMode::SGR_MOUSE) {
@@ -539,9 +534,7 @@ impl View for TerminalView {
} else {
"normal"
};
- context
- .map
- .insert("mouse_format".to_string(), format.to_string());
+ context.add_key("mouse_format", format);
}
context
}
@@ -589,11 +582,16 @@ impl Item for TerminalView {
Flex::row()
.with_child(
- Label::new(title, tab_theme.label.clone())
+ gpui::elements::Svg::new("icons/terminal_12.svg")
+ .with_color(tab_theme.label.text.color)
+ .constrained()
+ .with_width(tab_theme.icon_width)
.aligned()
.contained()
+ .with_margin_right(tab_theme.spacing)
.boxed(),
)
+ .with_child(Label::new(title, tab_theme.label.clone()).aligned().boxed())
.boxed()
}
@@ -616,43 +614,6 @@ impl Item for TerminalView {
None
}
- fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
-
- fn is_singleton(&self, _cx: &gpui::AppContext) -> bool {
- false
- }
-
- fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
-
- fn can_save(&self, _cx: &gpui::AppContext) -> bool {
- false
- }
-
- fn save(
- &mut self,
- _project: gpui::ModelHandle<Project>,
- _cx: &mut ViewContext<Self>,
- ) -> gpui::Task<gpui::anyhow::Result<()>> {
- unreachable!("save should not have been called");
- }
-
- fn save_as(
- &mut self,
- _project: gpui::ModelHandle<Project>,
- _abs_path: std::path::PathBuf,
- _cx: &mut ViewContext<Self>,
- ) -> gpui::Task<gpui::anyhow::Result<()>> {
- unreachable!("save_as should not have been called");
- }
-
- fn reload(
- &mut self,
- _project: gpui::ModelHandle<Project>,
- _cx: &mut ViewContext<Self>,
- ) -> gpui::Task<gpui::anyhow::Result<()>> {
- gpui::Task::ready(Ok(()))
- }
-
fn is_dirty(&self, _cx: &gpui::AppContext) -> bool {
self.has_bell()
}
@@ -667,10 +628,10 @@ impl Item for TerminalView {
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
match event {
- Event::BreadcrumbsChanged => smallvec::smallvec![ItemEvent::UpdateBreadcrumbs],
- Event::TitleChanged | Event::Wakeup => smallvec::smallvec![ItemEvent::UpdateTab],
- Event::CloseTerminal => smallvec::smallvec![ItemEvent::CloseItem],
- _ => smallvec::smallvec![],
+ Event::BreadcrumbsChanged => smallvec![ItemEvent::UpdateBreadcrumbs],
+ Event::TitleChanged | Event::Wakeup => smallvec![ItemEvent::UpdateTab],
+ Event::CloseTerminal => smallvec![ItemEvent::CloseItem],
+ _ => smallvec![],
}
}
@@ -74,20 +74,32 @@ pub struct Titlebar {
pub container: ContainerStyle,
pub height: f32,
pub title: TextStyle,
- pub avatar_width: f32,
- pub avatar_margin: f32,
+ pub item_spacing: f32,
+ pub face_pile_spacing: f32,
pub avatar_ribbon: AvatarRibbon,
+ pub follower_avatar_overlap: f32,
+ pub leader_selection: ContainerStyle,
pub offline_icon: OfflineIcon,
- pub avatar: ImageStyle,
- pub inactive_avatar: ImageStyle,
+ pub leader_avatar: AvatarStyle,
+ pub follower_avatar: AvatarStyle,
+ pub inactive_avatar_grayscale: bool,
pub sign_in_prompt: Interactive<ContainedText>,
pub outdated_warning: ContainedText,
pub share_button: Interactive<ContainedText>,
pub call_control: Interactive<IconButton>,
pub toggle_contacts_button: Interactive<IconButton>,
+ pub user_menu_button: Interactive<IconButton>,
pub toggle_contacts_badge: ContainerStyle,
}
+#[derive(Copy, Clone, Deserialize, Default)]
+pub struct AvatarStyle {
+ #[serde(flatten)]
+ pub image: ImageStyle,
+ pub outer_width: f32,
+ pub outer_corner_radius: f32,
+}
+
#[derive(Deserialize, Default)]
pub struct ContactsPopover {
#[serde(flatten)]
@@ -246,8 +258,6 @@ pub struct Search {
pub match_background: Color,
pub match_index: ContainedText,
pub results_status: TextStyle,
- pub tab_icon_width: f32,
- pub tab_icon_spacing: f32,
pub dismiss_button: Interactive<IconButton>,
}
@@ -381,7 +391,7 @@ pub struct InviteLink {
pub icon: Icon,
}
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Clone, Copy, Default)]
pub struct Icon {
#[serde(flatten)]
pub container: ContainerStyle,
@@ -11,12 +11,8 @@ use gpui::{
};
use project::Project;
use settings::Settings;
-use smallvec::SmallVec;
use theme::{ColorScheme, Layer, Style, StyleSet};
-use workspace::{
- item::{Item, ItemEvent},
- register_deserializable_item, Pane, Workspace,
-};
+use workspace::{item::Item, register_deserializable_item, Pane, Workspace};
actions!(theme, [DeployThemeTestbench]);
@@ -314,47 +310,6 @@ impl Item for ThemeTestbench {
.boxed()
}
- fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
-
- fn is_singleton(&self, _: &AppContext) -> bool {
- false
- }
-
- fn set_nav_history(&mut self, _: workspace::ItemNavHistory, _: &mut ViewContext<Self>) {}
-
- fn can_save(&self, _: &AppContext) -> bool {
- false
- }
-
- fn save(
- &mut self,
- _: gpui::ModelHandle<Project>,
- _: &mut ViewContext<Self>,
- ) -> gpui::Task<gpui::anyhow::Result<()>> {
- unreachable!("save should not have been called");
- }
-
- fn save_as(
- &mut self,
- _: gpui::ModelHandle<Project>,
- _: std::path::PathBuf,
- _: &mut ViewContext<Self>,
- ) -> gpui::Task<gpui::anyhow::Result<()>> {
- unreachable!("save_as should not have been called");
- }
-
- fn reload(
- &mut self,
- _: gpui::ModelHandle<Project>,
- _: &mut ViewContext<Self>,
- ) -> gpui::Task<gpui::anyhow::Result<()>> {
- gpui::Task::ready(Ok(()))
- }
-
- fn to_item_events(_: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
- SmallVec::new()
- }
-
fn serialized_item_kind() -> Option<&'static str> {
Some("ThemeTestBench")
}
@@ -36,6 +36,7 @@ pub enum Motion {
Matching,
FindForward { before: bool, text: Arc<str> },
FindBackward { after: bool, text: Arc<str> },
+ NextLineStart,
}
#[derive(Clone, Deserialize, PartialEq)]
@@ -74,6 +75,7 @@ actions!(
StartOfDocument,
EndOfDocument,
Matching,
+ NextLineStart,
]
);
impl_actions!(vim, [NextWordStart, NextWordEnd, PreviousWordStart]);
@@ -111,6 +113,7 @@ pub fn init(cx: &mut MutableAppContext) {
&PreviousWordStart { ignore_punctuation }: &PreviousWordStart,
cx: _| { motion(Motion::PreviousWordStart { ignore_punctuation }, cx) },
);
+ cx.add_action(|_: &mut Workspace, &NextLineStart, cx: _| motion(Motion::NextLineStart, cx))
}
pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) {
@@ -138,15 +141,43 @@ pub(crate) fn motion(motion: Motion, cx: &mut MutableAppContext) {
impl Motion {
pub fn linewise(&self) -> bool {
use Motion::*;
- matches!(
- self,
- Down | Up | StartOfDocument | EndOfDocument | CurrentLine
- )
+ match self {
+ Down | Up | StartOfDocument | EndOfDocument | CurrentLine | NextLineStart => true,
+ EndOfLine
+ | NextWordEnd { .. }
+ | Matching
+ | FindForward { .. }
+ | Left
+ | Backspace
+ | Right
+ | StartOfLine
+ | NextWordStart { .. }
+ | PreviousWordStart { .. }
+ | FirstNonWhitespace
+ | FindBackward { .. } => false,
+ }
}
pub fn infallible(&self) -> bool {
use Motion::*;
- matches!(self, StartOfDocument | CurrentLine | EndOfDocument)
+ match self {
+ StartOfDocument | EndOfDocument | CurrentLine => true,
+ Down
+ | Up
+ | EndOfLine
+ | NextWordEnd { .. }
+ | Matching
+ | FindForward { .. }
+ | Left
+ | Backspace
+ | Right
+ | StartOfLine
+ | NextWordStart { .. }
+ | PreviousWordStart { .. }
+ | FirstNonWhitespace
+ | FindBackward { .. }
+ | NextLineStart => false,
+ }
}
pub fn inclusive(&self) -> bool {
@@ -160,7 +191,8 @@ impl Motion {
| EndOfLine
| NextWordEnd { .. }
| Matching
- | FindForward { .. } => true,
+ | FindForward { .. }
+ | NextLineStart => true,
Left
| Backspace
| Right
@@ -214,6 +246,7 @@ impl Motion {
find_backward(map, point, *after, text.clone(), times),
SelectionGoal::None,
),
+ NextLineStart => (next_line_start(map, point, times), SelectionGoal::None),
};
(new_point != point || infallible).then_some((new_point, goal))
@@ -543,3 +576,8 @@ fn find_backward(
})
.unwrap_or(from)
}
+
+fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint {
+ let new_row = (point.row() + times as u32).min(map.max_buffer_row());
+ map.clip_point(DisplayPoint::new(new_row, 0), Bias::Left)
+}
@@ -473,6 +473,7 @@ pub(crate) fn normal_replace(text: Arc<str>, cx: &mut MutableAppContext) {
#[cfg(test)]
mod test {
+ use gpui::TestAppContext;
use indoc::indoc;
use crate::{
@@ -515,15 +516,15 @@ mod test {
.await;
}
- // #[gpui::test]
- // async fn test_enter(cx: &mut gpui::TestAppContext) {
- // let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
- // cx.assert_all(indoc! {"
- // ΛThe qΛuick broΛwn
- // Λfox jumps"
- // })
- // .await;
- // }
+ #[gpui::test]
+ async fn test_enter(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await.binding(["enter"]);
+ cx.assert_all(indoc! {"
+ ΛThe qΛuick broΛwn
+ Λfox jumps"
+ })
+ .await;
+ }
#[gpui::test]
async fn test_k(cx: &mut gpui::TestAppContext) {
@@ -1030,7 +1031,7 @@ mod test {
}
#[gpui::test]
- async fn test_percent(cx: &mut gpui::TestAppContext) {
+ async fn test_percent(cx: &mut TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await.binding(["%"]);
cx.assert_all("Λconsole.logΛ(ΛvaΛrΛ)Λ;").await;
cx.assert_all("Λconsole.logΛ(Λ'var', Λ[Λ1, Λ2, 3Λ]Λ)Λ;")
@@ -73,34 +73,30 @@ impl VimState {
pub fn keymap_context_layer(&self) -> KeymapContext {
let mut context = KeymapContext::default();
- context.map.insert(
- "vim_mode".to_string(),
+ context.add_key(
+ "vim_mode",
match self.mode {
Mode::Normal => "normal",
Mode::Visual { .. } => "visual",
Mode::Insert => "insert",
- }
- .to_string(),
+ },
);
if self.vim_controlled() {
- context.set.insert("VimControl".to_string());
+ context.add_identifier("VimControl");
}
let active_operator = self.operator_stack.last();
if let Some(active_operator) = active_operator {
for context_flag in active_operator.context_flags().into_iter() {
- context.set.insert(context_flag.to_string());
+ context.add_identifier(*context_flag);
}
}
- context.map.insert(
- "vim_operator".to_string(),
- active_operator
- .map(|op| op.id())
- .unwrap_or_else(|| "none")
- .to_string(),
+ context.add_key(
+ "vim_operator",
+ active_operator.map(|op| op.id()).unwrap_or_else(|| "none"),
);
context
@@ -0,0 +1 @@
+[{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"},{"Text":"The quick brown\nfox jumps"},{"Mode":"Normal"},{"Selection":{"start":[1,0],"end":[1,0]}},{"Mode":"Normal"}]
@@ -49,9 +49,11 @@ pub trait Item: View {
}
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
-> ElementBox;
- fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item));
- fn is_singleton(&self, cx: &AppContext) -> bool;
- fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>);
+ fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
+ fn is_singleton(&self, _cx: &AppContext) -> bool {
+ false
+ }
+ fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
where
Self: Sized,
@@ -64,23 +66,31 @@ pub trait Item: View {
fn has_conflict(&self, _: &AppContext) -> bool {
false
}
- fn can_save(&self, cx: &AppContext) -> bool;
+ fn can_save(&self, _cx: &AppContext) -> bool {
+ false
+ }
fn save(
&mut self,
- project: ModelHandle<Project>,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>>;
+ _project: ModelHandle<Project>,
+ _cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ unimplemented!("save() must be implemented if can_save() returns true")
+ }
fn save_as(
&mut self,
- project: ModelHandle<Project>,
- abs_path: PathBuf,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>>;
+ _project: ModelHandle<Project>,
+ _abs_path: PathBuf,
+ _cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ unimplemented!("save_as() must be implemented if can_save() returns true")
+ }
fn reload(
&mut self,
- project: ModelHandle<Project>,
- cx: &mut ViewContext<Self>,
- ) -> Task<Result<()>>;
+ _project: ModelHandle<Project>,
+ _cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ unimplemented!("reload() must be implemented if can_save() returns true")
+ }
fn git_diff_recalc(
&mut self,
_project: ModelHandle<Project>,
@@ -88,7 +98,9 @@ pub trait Item: View {
) -> Task<Result<()>> {
Task::ready(Ok(()))
}
- fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]>;
+ fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+ SmallVec::new()
+ }
fn should_close_item_on_event(_: &Self::Event) -> bool {
false
}
@@ -124,15 +136,21 @@ pub trait Item: View {
fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
- fn serialized_item_kind() -> Option<&'static str>;
+ fn serialized_item_kind() -> Option<&'static str> {
+ None
+ }
fn deserialize(
- project: ModelHandle<Project>,
- workspace: WeakViewHandle<Workspace>,
- workspace_id: WorkspaceId,
- item_id: ItemId,
- cx: &mut ViewContext<Pane>,
- ) -> Task<Result<ViewHandle<Self>>>;
+ _project: ModelHandle<Project>,
+ _workspace: WeakViewHandle<Workspace>,
+ _workspace_id: WorkspaceId,
+ _item_id: ItemId,
+ _cx: &mut ViewContext<Pane>,
+ ) -> Task<Result<ViewHandle<Self>>> {
+ unimplemented!(
+ "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
+ )
+ }
}
pub trait ItemHandle: 'static + fmt::Debug {
@@ -21,6 +21,7 @@ use gpui::{
vector::{vec2f, Vector2F},
},
impl_actions, impl_internal_actions,
+ keymap_matcher::KeymapContext,
platform::{CursorStyle, NavigationDirection},
Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
ModelHandle, MouseButton, MutableAppContext, PromptLevel, Quad, RenderContext, Task, View,
@@ -1550,6 +1551,14 @@ impl View for Pane {
}
}
}
+
+ fn keymap_context(&self, _: &AppContext) -> KeymapContext {
+ let mut keymap = Self::default_keymap_context();
+ if self.docked.is_some() {
+ keymap.add_identifier("docked");
+ }
+ keymap
+ }
}
fn tab_bar_button<A: Action>(
@@ -1,23 +1,18 @@
use crate::{
- item::ItemEvent, persistence::model::ItemId, Item, ItemNavHistory, Pane, Workspace, WorkspaceId,
+ item::{Item, ItemEvent},
+ ItemNavHistory, WorkspaceId,
};
-use anyhow::{anyhow, Result};
use call::participant::{Frame, RemoteVideoTrack};
use client::{proto::PeerId, User};
use futures::StreamExt;
use gpui::{
elements::*,
geometry::{rect::RectF, vector::vec2f},
- AppContext, Entity, ModelHandle, MouseButton, RenderContext, Task, View, ViewContext,
- ViewHandle, WeakViewHandle,
+ AppContext, Entity, MouseButton, RenderContext, Task, View, ViewContext,
};
-use project::Project;
use settings::Settings;
use smallvec::SmallVec;
-use std::{
- path::PathBuf,
- sync::{Arc, Weak},
-};
+use std::sync::{Arc, Weak};
pub enum Event {
Close,
@@ -130,12 +125,6 @@ impl Item for SharedScreen {
.boxed()
}
- fn for_each_project_item(&self, _: &AppContext, _: &mut dyn FnMut(usize, &dyn project::Item)) {}
-
- fn is_singleton(&self, _: &AppContext) -> bool {
- false
- }
-
fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
self.nav_history = Some(history);
}
@@ -149,52 +138,9 @@ impl Item for SharedScreen {
Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
}
- fn can_save(&self, _: &AppContext) -> bool {
- false
- }
-
- fn save(
- &mut self,
- _: ModelHandle<project::Project>,
- _: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- Task::ready(Err(anyhow!("Item::save called on SharedScreen")))
- }
-
- fn save_as(
- &mut self,
- _: ModelHandle<project::Project>,
- _: PathBuf,
- _: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- Task::ready(Err(anyhow!("Item::save_as called on SharedScreen")))
- }
-
- fn reload(
- &mut self,
- _: ModelHandle<project::Project>,
- _: &mut ViewContext<Self>,
- ) -> Task<Result<()>> {
- Task::ready(Err(anyhow!("Item::reload called on SharedScreen")))
- }
-
fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
match event {
Event::Close => smallvec::smallvec!(ItemEvent::CloseItem),
}
}
-
- fn serialized_item_kind() -> Option<&'static str> {
- None
- }
-
- fn deserialize(
- _project: ModelHandle<Project>,
- _workspace: WeakViewHandle<Workspace>,
- _workspace_id: WorkspaceId,
- _item_id: ItemId,
- _cx: &mut ViewContext<Pane>,
- ) -> Task<Result<ViewHandle<Self>>> {
- unreachable!("Shared screen can not be deserialized")
- }
}
@@ -837,7 +837,7 @@ impl Workspace {
&self.project
}
- pub fn client(&self) -> &Arc<Client> {
+ pub fn client(&self) -> &Client {
&self.client
}
@@ -1589,13 +1589,17 @@ impl Workspace {
}
let item = pane.read(cx).active_item()?;
- let new_pane = self.add_pane(cx);
- if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
- Pane::add_item(self, &new_pane, clone, true, true, None, cx);
- }
- self.center.split(&pane, &new_pane, direction).unwrap();
+ let maybe_pane_handle =
+ if let Some(clone) = item.clone_on_split(self.database_id(), cx.as_mut()) {
+ let new_pane = self.add_pane(cx);
+ Pane::add_item(self, &new_pane, clone, true, true, None, cx);
+ self.center.split(&pane, &new_pane, direction).unwrap();
+ Some(new_pane)
+ } else {
+ None
+ };
cx.notify();
- Some(new_pane)
+ maybe_pane_handle
}
pub fn split_pane_with_item(&mut self, action: &SplitWithItem, cx: &mut ViewContext<Self>) {
@@ -1828,24 +1832,15 @@ impl Workspace {
None
}
- pub fn is_following(&self, peer_id: PeerId) -> bool {
+ pub fn is_being_followed(&self, peer_id: PeerId) -> bool {
self.follower_states_by_leader.contains_key(&peer_id)
}
- pub fn is_followed(&self, peer_id: PeerId) -> bool {
+ pub fn is_followed_by(&self, peer_id: PeerId) -> bool {
self.leader_state.followers.contains(&peer_id)
}
fn render_titlebar(&self, theme: &Theme, cx: &mut RenderContext<Self>) -> ElementBox {
- let project = &self.project.read(cx);
- let mut worktree_root_names = String::new();
- for (i, name) in project.worktree_root_names(cx).enumerate() {
- if i > 0 {
- worktree_root_names.push_str(", ");
- }
- worktree_root_names.push_str(name);
- }
-
// TODO: There should be a better system in place for this
// (https://github.com/zed-industries/zed/issues/1290)
let is_fullscreen = cx.window_is_fullscreen(cx.window_id());
@@ -1862,16 +1857,10 @@ impl Workspace {
MouseEventHandler::<TitleBar>::new(0, cx, |_, cx| {
Container::new(
Stack::new()
- .with_child(
- Label::new(worktree_root_names, theme.workspace.titlebar.title.clone())
- .aligned()
- .left()
- .boxed(),
- )
.with_children(
self.titlebar_item
.as_ref()
- .map(|item| ChildView::new(item, cx).aligned().right().boxed()),
+ .map(|item| ChildView::new(item, cx).boxed()),
)
.boxed(),
)
@@ -2727,11 +2716,7 @@ impl View for Workspace {
}
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
- let mut keymap = Self::default_keymap_context();
- if self.active_pane() == self.dock_pane() {
- keymap.set.insert("Dock".into());
- }
- keymap
+ Self::default_keymap_context()
}
}
@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
description = "The fast, collaborative code editor."
edition = "2021"
name = "zed"
-version = "0.75.0"
+version = "0.76.0"
publish = false
[lib]
@@ -1,4 +1,4 @@
-use gpui::{Menu, MenuItem};
+use gpui::{Menu, MenuItem, OsAction};
#[cfg(target_os = "macos")]
pub fn menus() -> Vec<Menu<'static>> {
@@ -6,363 +6,159 @@ pub fn menus() -> Vec<Menu<'static>> {
Menu {
name: "Zed",
items: vec![
- MenuItem::Action {
- name: "About Zedβ¦",
- action: Box::new(super::About),
- },
- MenuItem::Action {
- name: "Check for Updates",
- action: Box::new(auto_update::Check),
- },
- MenuItem::Separator,
- MenuItem::Submenu(Menu {
+ MenuItem::action("About Zedβ¦", super::About),
+ MenuItem::action("Check for Updates", auto_update::Check),
+ MenuItem::separator(),
+ MenuItem::submenu(Menu {
name: "Preferences",
items: vec![
- MenuItem::Action {
- name: "Open Settings",
- action: Box::new(super::OpenSettings),
- },
- MenuItem::Action {
- name: "Open Key Bindings",
- action: Box::new(super::OpenKeymap),
- },
- MenuItem::Action {
- name: "Open Default Settings",
- action: Box::new(super::OpenDefaultSettings),
- },
- MenuItem::Action {
- name: "Open Default Key Bindings",
- action: Box::new(super::OpenDefaultKeymap),
- },
- MenuItem::Action {
- name: "Select Theme",
- action: Box::new(theme_selector::Toggle),
- },
+ MenuItem::action("Open Settings", super::OpenSettings),
+ MenuItem::action("Open Key Bindings", super::OpenKeymap),
+ MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
+ MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
+ MenuItem::action("Select Theme", theme_selector::Toggle),
],
}),
- MenuItem::Action {
- name: "Install CLI",
- action: Box::new(super::InstallCommandLineInterface),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Hide Zed",
- action: Box::new(super::Hide),
- },
- MenuItem::Action {
- name: "Hide Others",
- action: Box::new(super::HideOthers),
- },
- MenuItem::Action {
- name: "Show All",
- action: Box::new(super::ShowAll),
- },
- MenuItem::Action {
- name: "Quit",
- action: Box::new(super::Quit),
- },
+ MenuItem::action("Install CLI", super::InstallCommandLineInterface),
+ MenuItem::separator(),
+ MenuItem::action("Hide Zed", super::Hide),
+ MenuItem::action("Hide Others", super::HideOthers),
+ MenuItem::action("Show All", super::ShowAll),
+ MenuItem::action("Quit", super::Quit),
],
},
Menu {
name: "File",
items: vec![
- MenuItem::Action {
- name: "New",
- action: Box::new(workspace::NewFile),
- },
- MenuItem::Action {
- name: "New Window",
- action: Box::new(workspace::NewWindow),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Openβ¦",
- action: Box::new(workspace::Open),
- },
- MenuItem::Action {
- name: "Open Recent...",
- action: Box::new(recent_projects::OpenRecent),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Add Folder to Projectβ¦",
- action: Box::new(workspace::AddFolderToProject),
- },
- MenuItem::Action {
- name: "Save",
- action: Box::new(workspace::Save),
- },
- MenuItem::Action {
- name: "Save Asβ¦",
- action: Box::new(workspace::SaveAs),
- },
- MenuItem::Action {
- name: "Save All",
- action: Box::new(workspace::SaveAll),
- },
- MenuItem::Action {
- name: "Close Editor",
- action: Box::new(workspace::CloseActiveItem),
- },
- MenuItem::Action {
- name: "Close Window",
- action: Box::new(workspace::CloseWindow),
- },
+ MenuItem::action("New", workspace::NewFile),
+ MenuItem::action("New Window", workspace::NewWindow),
+ MenuItem::separator(),
+ MenuItem::action("Openβ¦", workspace::Open),
+ MenuItem::action("Open Recent...", recent_projects::OpenRecent),
+ MenuItem::separator(),
+ MenuItem::action("Add Folder to Projectβ¦", workspace::AddFolderToProject),
+ MenuItem::action("Save", workspace::Save),
+ MenuItem::action("Save Asβ¦", workspace::SaveAs),
+ MenuItem::action("Save All", workspace::SaveAll),
+ MenuItem::action("Close Editor", workspace::CloseActiveItem),
+ MenuItem::action("Close Window", workspace::CloseWindow),
],
},
Menu {
name: "Edit",
items: vec![
- MenuItem::Action {
- name: "Undo",
- action: Box::new(editor::Undo),
- },
- MenuItem::Action {
- name: "Redo",
- action: Box::new(editor::Redo),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Cut",
- action: Box::new(editor::Cut),
- },
- MenuItem::Action {
- name: "Copy",
- action: Box::new(editor::Copy),
- },
- MenuItem::Action {
- name: "Paste",
- action: Box::new(editor::Paste),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Find",
- action: Box::new(search::buffer_search::Deploy { focus: true }),
- },
- MenuItem::Action {
- name: "Find In Project",
- action: Box::new(workspace::NewSearch),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Toggle Line Comment",
- action: Box::new(editor::ToggleComments::default()),
- },
- MenuItem::Action {
- name: "Emoji & Symbols",
- action: Box::new(editor::ShowCharacterPalette),
- },
+ MenuItem::os_action("Undo", editor::Undo, OsAction::Undo),
+ MenuItem::os_action("Redo", editor::Redo, OsAction::Redo),
+ MenuItem::separator(),
+ MenuItem::os_action("Cut", editor::Cut, OsAction::Cut),
+ MenuItem::os_action("Copy", editor::Copy, OsAction::Copy),
+ MenuItem::os_action("Paste", editor::Paste, OsAction::Paste),
+ MenuItem::separator(),
+ MenuItem::action("Find", search::buffer_search::Deploy { focus: true }),
+ MenuItem::action("Find In Project", workspace::NewSearch),
+ MenuItem::separator(),
+ MenuItem::action("Toggle Line Comment", editor::ToggleComments::default()),
+ MenuItem::action("Emoji & Symbols", editor::ShowCharacterPalette),
],
},
Menu {
name: "Selection",
items: vec![
- MenuItem::Action {
- name: "Select All",
- action: Box::new(editor::SelectAll),
- },
- MenuItem::Action {
- name: "Expand Selection",
- action: Box::new(editor::SelectLargerSyntaxNode),
- },
- MenuItem::Action {
- name: "Shrink Selection",
- action: Box::new(editor::SelectSmallerSyntaxNode),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Add Cursor Above",
- action: Box::new(editor::AddSelectionAbove),
- },
- MenuItem::Action {
- name: "Add Cursor Below",
- action: Box::new(editor::AddSelectionBelow),
- },
- MenuItem::Action {
- name: "Select Next Occurrence",
- action: Box::new(editor::SelectNext {
+ MenuItem::os_action("Select All", editor::SelectAll, OsAction::SelectAll),
+ MenuItem::action("Expand Selection", editor::SelectLargerSyntaxNode),
+ MenuItem::action("Shrink Selection", editor::SelectSmallerSyntaxNode),
+ MenuItem::separator(),
+ MenuItem::action("Add Cursor Above", editor::AddSelectionAbove),
+ MenuItem::action("Add Cursor Below", editor::AddSelectionBelow),
+ MenuItem::action(
+ "Select Next Occurrence",
+ editor::SelectNext {
replace_newest: false,
- }),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Move Line Up",
- action: Box::new(editor::MoveLineUp),
- },
- MenuItem::Action {
- name: "Move Line Down",
- action: Box::new(editor::MoveLineDown),
- },
- MenuItem::Action {
- name: "Duplicate Selection",
- action: Box::new(editor::DuplicateLine),
- },
+ },
+ ),
+ MenuItem::separator(),
+ MenuItem::action("Move Line Up", editor::MoveLineUp),
+ MenuItem::action("Move Line Down", editor::MoveLineDown),
+ MenuItem::action("Duplicate Selection", editor::DuplicateLine),
],
},
Menu {
name: "View",
items: vec![
- MenuItem::Action {
- name: "Zoom In",
- action: Box::new(super::IncreaseBufferFontSize),
- },
- MenuItem::Action {
- name: "Zoom Out",
- action: Box::new(super::DecreaseBufferFontSize),
- },
- MenuItem::Action {
- name: "Reset Zoom",
- action: Box::new(super::ResetBufferFontSize),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Toggle Left Sidebar",
- action: Box::new(workspace::ToggleLeftSidebar),
- },
- MenuItem::Submenu(Menu {
+ MenuItem::action("Zoom In", super::IncreaseBufferFontSize),
+ MenuItem::action("Zoom Out", super::DecreaseBufferFontSize),
+ MenuItem::action("Reset Zoom", super::ResetBufferFontSize),
+ MenuItem::separator(),
+ MenuItem::action("Toggle Left Sidebar", workspace::ToggleLeftSidebar),
+ MenuItem::submenu(Menu {
name: "Editor Layout",
items: vec![
- MenuItem::Action {
- name: "Split Up",
- action: Box::new(workspace::SplitUp),
- },
- MenuItem::Action {
- name: "Split Down",
- action: Box::new(workspace::SplitDown),
- },
- MenuItem::Action {
- name: "Split Left",
- action: Box::new(workspace::SplitLeft),
- },
- MenuItem::Action {
- name: "Split Right",
- action: Box::new(workspace::SplitRight),
- },
+ MenuItem::action("Split Up", workspace::SplitUp),
+ MenuItem::action("Split Down", workspace::SplitDown),
+ MenuItem::action("Split Left", workspace::SplitLeft),
+ MenuItem::action("Split Right", workspace::SplitRight),
],
}),
- MenuItem::Separator,
- MenuItem::Action {
- name: "Project Panel",
- action: Box::new(project_panel::ToggleFocus),
- },
- MenuItem::Action {
- name: "Command Palette",
- action: Box::new(command_palette::Toggle),
- },
- MenuItem::Action {
- name: "Diagnostics",
- action: Box::new(diagnostics::Deploy),
- },
- MenuItem::Separator,
+ MenuItem::separator(),
+ MenuItem::action("Project Panel", project_panel::ToggleFocus),
+ MenuItem::action("Command Palette", command_palette::Toggle),
+ MenuItem::action("Diagnostics", diagnostics::Deploy),
+ MenuItem::separator(),
],
},
Menu {
name: "Go",
items: vec![
- MenuItem::Action {
- name: "Back",
- action: Box::new(workspace::GoBack { pane: None }),
- },
- MenuItem::Action {
- name: "Forward",
- action: Box::new(workspace::GoForward { pane: None }),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Go to File",
- action: Box::new(file_finder::Toggle),
- },
- MenuItem::Action {
- name: "Go to Symbol in Project",
- action: Box::new(project_symbols::Toggle),
- },
- MenuItem::Action {
- name: "Go to Symbol in Editor",
- action: Box::new(outline::Toggle),
- },
- MenuItem::Action {
- name: "Go to Definition",
- action: Box::new(editor::GoToDefinition),
- },
- MenuItem::Action {
- name: "Go to Type Definition",
- action: Box::new(editor::GoToTypeDefinition),
- },
- MenuItem::Action {
- name: "Find All References",
- action: Box::new(editor::FindAllReferences),
- },
- MenuItem::Action {
- name: "Go to Line/Column",
- action: Box::new(go_to_line::Toggle),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Next Problem",
- action: Box::new(editor::GoToDiagnostic),
- },
- MenuItem::Action {
- name: "Previous Problem",
- action: Box::new(editor::GoToPrevDiagnostic),
- },
+ MenuItem::action("Back", workspace::GoBack { pane: None }),
+ MenuItem::action("Forward", workspace::GoForward { pane: None }),
+ MenuItem::separator(),
+ MenuItem::action("Go to File", file_finder::Toggle),
+ MenuItem::action("Go to Symbol in Project", project_symbols::Toggle),
+ MenuItem::action("Go to Symbol in Editor", outline::Toggle),
+ MenuItem::action("Go to Definition", editor::GoToDefinition),
+ MenuItem::action("Go to Type Definition", editor::GoToTypeDefinition),
+ MenuItem::action("Find All References", editor::FindAllReferences),
+ MenuItem::action("Go to Line/Column", go_to_line::Toggle),
+ MenuItem::separator(),
+ MenuItem::action("Next Problem", editor::GoToDiagnostic),
+ MenuItem::action("Previous Problem", editor::GoToPrevDiagnostic),
],
},
Menu {
name: "Window",
items: vec![
- MenuItem::Action {
- name: "Minimize",
- action: Box::new(super::Minimize),
- },
- MenuItem::Action {
- name: "Zoom",
- action: Box::new(super::Zoom),
- },
- MenuItem::Separator,
+ MenuItem::action("Minimize", super::Minimize),
+ MenuItem::action("Zoom", super::Zoom),
+ MenuItem::separator(),
],
},
Menu {
name: "Help",
items: vec![
- MenuItem::Action {
- name: "Command Palette",
- action: Box::new(command_palette::Toggle),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "View Telemetry Log",
- action: Box::new(crate::OpenTelemetryLog),
- },
- MenuItem::Action {
- name: "View Dependency Licenses",
- action: Box::new(crate::OpenLicenses),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Copy System Specs Into Clipboard",
- action: Box::new(feedback::CopySystemSpecsIntoClipboard),
- },
- MenuItem::Action {
- name: "File Bug Report",
- action: Box::new(feedback::FileBugReport),
- },
- MenuItem::Action {
- name: "Request Feature",
- action: Box::new(feedback::RequestFeature),
- },
- MenuItem::Separator,
- MenuItem::Action {
- name: "Documentation",
- action: Box::new(crate::OpenBrowser {
+ MenuItem::action("Command Palette", command_palette::Toggle),
+ MenuItem::separator(),
+ MenuItem::action("View Telemetry Log", crate::OpenTelemetryLog),
+ MenuItem::action("View Dependency Licenses", crate::OpenLicenses),
+ MenuItem::separator(),
+ MenuItem::action(
+ "Copy System Specs Into Clipboard",
+ feedback::CopySystemSpecsIntoClipboard,
+ ),
+ MenuItem::action("File Bug Report", feedback::FileBugReport),
+ MenuItem::action("Request Feature", feedback::RequestFeature),
+ MenuItem::separator(),
+ MenuItem::action(
+ "Documentation",
+ crate::OpenBrowser {
url: "https://zed.dev/docs".into(),
- }),
- },
- MenuItem::Action {
- name: "Zed Twitter",
- action: Box::new(crate::OpenBrowser {
+ },
+ ),
+ MenuItem::action(
+ "Zed Twitter",
+ crate::OpenBrowser {
url: "https://twitter.com/zeddotdev".into(),
- }),
- },
+ },
+ ),
],
},
]
@@ -6,7 +6,7 @@ use anyhow::{anyhow, Context, Result};
use assets::Assets;
use breadcrumbs::Breadcrumbs;
pub use client;
-use collab_ui::{CollabTitlebarItem, ToggleCollaborationMenu};
+use collab_ui::{CollabTitlebarItem, ToggleContactsMenu};
use collections::VecDeque;
pub use editor;
use editor::{Editor, MultiBuffer};
@@ -99,9 +99,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
},
);
cx.add_action(
- |workspace: &mut Workspace,
- _: &ToggleCollaborationMenu,
- cx: &mut ViewContext<Workspace>| {
+ |workspace: &mut Workspace, _: &ToggleContactsMenu, cx: &mut ViewContext<Workspace>| {
if let Some(item) = workspace
.titlebar_item()
.and_then(|item| item.downcast::<CollabTitlebarItem>())
@@ -0,0 +1,2 @@
+package-lock.json
+package.json
@@ -9,67 +9,83 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
- "@types/chroma-js": "^2.1.3",
- "@types/node": "^17.0.23",
+ "@types/chroma-js": "^2.4.0",
+ "@types/node": "^18.14.1",
+ "bezier-easing": "^2.1.0",
"case-anything": "^2.1.10",
"chroma-js": "^2.4.2",
+ "deepmerge": "^4.3.0",
"toml": "^3.0.0",
- "ts-node": "^10.7.0"
- }
- },
- "node_modules/@cspotcode/source-map-consumer": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
- "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
- "engines": {
- "node": ">= 12"
+ "ts-node": "^10.9.1"
}
},
"node_modules/@cspotcode/source-map-support": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
- "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dependencies": {
- "@cspotcode/source-map-consumer": "0.8.0"
+ "@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
"node_modules/@tsconfig/node10": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
- "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg=="
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+ "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
},
"node_modules/@tsconfig/node12": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
- "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw=="
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
},
"node_modules/@tsconfig/node14": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
- "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
},
"node_modules/@tsconfig/node16": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
- "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ=="
},
"node_modules/@types/chroma-js": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
- "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz",
+ "integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw=="
},
"node_modules/@types/node": {
- "version": "17.0.23",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
- "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw=="
+ "version": "18.14.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
+ "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
},
"node_modules/acorn": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
- "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
"bin": {
"acorn": "bin/acorn"
},
@@ -90,6 +106,11 @@
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
},
+ "node_modules/bezier-easing": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
+ "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
+ },
"node_modules/case-anything": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz",
@@ -111,6 +132,14 @@
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
},
+ "node_modules/deepmerge": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+ "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -130,11 +159,11 @@
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
},
"node_modules/ts-node": {
- "version": "10.7.0",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz",
- "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==",
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+ "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"dependencies": {
- "@cspotcode/source-map-support": "0.7.0",
+ "@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
@@ -145,7 +174,7 @@
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.0",
+ "v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
@@ -172,9 +201,9 @@
}
},
"node_modules/typescript": {
- "version": "4.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
- "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
@@ -185,9 +214,9 @@
}
},
"node_modules/v8-compile-cache-lib": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
- "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA=="
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
},
"node_modules/yn": {
"version": "3.1.1",
@@ -199,53 +228,67 @@
}
},
"dependencies": {
- "@cspotcode/source-map-consumer": {
- "version": "0.8.0",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
- "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg=="
- },
"@cspotcode/source-map-support": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
- "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "requires": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"requires": {
- "@cspotcode/source-map-consumer": "0.8.0"
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@tsconfig/node10": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
- "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg=="
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+ "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA=="
},
"@tsconfig/node12": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
- "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw=="
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="
},
"@tsconfig/node14": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
- "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="
},
"@tsconfig/node16": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
- "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA=="
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
+ "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ=="
},
"@types/chroma-js": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
- "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.0.tgz",
+ "integrity": "sha512-JklMxityrwjBTjGY2anH8JaTx3yjRU3/sEHSblLH1ba5lqcSh1LnImXJZO5peJfXyqKYWjHTGy4s5Wz++hARrw=="
},
"@types/node": {
- "version": "17.0.23",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz",
- "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw=="
+ "version": "18.14.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
+ "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
},
"acorn": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
- "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
},
"acorn-walk": {
"version": "8.2.0",
@@ -257,6 +300,11 @@
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="
},
+ "bezier-easing": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
+ "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
+ },
"case-anything": {
"version": "2.1.10",
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.10.tgz",
@@ -272,6 +320,11 @@
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
},
+ "deepmerge": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+ "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og=="
+ },
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -288,11 +341,11 @@
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="
},
"ts-node": {
- "version": "10.7.0",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz",
- "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==",
+ "version": "10.9.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+ "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"requires": {
- "@cspotcode/source-map-support": "0.7.0",
+ "@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
@@ -303,20 +356,20 @@
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.0",
+ "v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
}
},
"typescript": {
- "version": "4.6.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
- "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"peer": true
},
"v8-compile-cache-lib": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
- "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA=="
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
},
"yn": {
"version": "3.1.1",
@@ -10,11 +10,19 @@
"author": "",
"license": "ISC",
"dependencies": {
- "@types/chroma-js": "^2.1.3",
- "@types/node": "^17.0.23",
+ "@types/chroma-js": "^2.4.0",
+ "@types/node": "^18.14.1",
+ "bezier-easing": "^2.1.0",
"case-anything": "^2.1.10",
"chroma-js": "^2.4.2",
+ "deepmerge": "^4.3.0",
"toml": "^3.0.0",
- "ts-node": "^10.7.0"
+ "ts-node": "^10.9.1"
+ },
+ "prettier": {
+ "semi": false,
+ "printWidth": 80,
+ "htmlWhitespaceSensitivity": "strict",
+ "tabWidth": 4
}
}
@@ -1,73 +1,87 @@
-import * as fs from "fs";
-import toml from "toml";
-import {
- schemeMeta
-} from "./colorSchemes";
-import { Meta } from "./themes/common/colorScheme";
-import https from "https";
-import crypto from "crypto";
+import * as fs from "fs"
+import toml from "toml"
+import { schemeMeta } from "./colorSchemes"
+import { Meta } from "./themes/common/colorScheme"
+import https from "https"
+import crypto from "crypto"
const accepted_licenses_file = `${__dirname}/../../script/licenses/zed-licenses.toml`
// Use the cargo-about configuration file as the source of truth for supported licenses.
function parseAcceptedToml(file: string): string[] {
- let buffer = fs.readFileSync(file).toString();
+ let buffer = fs.readFileSync(file).toString()
- let obj = toml.parse(buffer);
+ let obj = toml.parse(buffer)
- if (!Array.isArray(obj.accepted)) {
- throw Error("Accepted license source is malformed")
- }
+ if (!Array.isArray(obj.accepted)) {
+ throw Error("Accepted license source is malformed")
+ }
- return obj.accepted
+ return obj.accepted
}
-
function checkLicenses(schemeMeta: Meta[], licenses: string[]) {
- for (let meta of schemeMeta) {
- // FIXME: Add support for conjuctions and conditions
- if (licenses.indexOf(meta.license.SPDX) < 0) {
- throw Error(`License for theme ${meta.name} (${meta.license.SPDX}) is not supported`)
+ for (let meta of schemeMeta) {
+ // FIXME: Add support for conjuctions and conditions
+ if (licenses.indexOf(meta.license.SPDX) < 0) {
+ throw Error(
+ `License for theme ${meta.name} (${meta.license.SPDX}) is not supported`
+ )
+ }
}
- }
}
+function getLicenseText(
+ schemeMeta: Meta[],
+ callback: (meta: Meta, license_text: string) => void
+) {
+ for (let meta of schemeMeta) {
+ // The following copied from the example code on nodejs.org:
+ // https://nodejs.org/api/http.html#httpgetoptions-callback
+ https
+ .get(meta.license.https_url, (res) => {
+ const { statusCode } = res
-function getLicenseText(schemeMeta: Meta[], callback: (meta: Meta, license_text: string) => void) {
- for (let meta of schemeMeta) {
- // The following copied from the example code on nodejs.org:
- // https://nodejs.org/api/http.html#httpgetoptions-callback
- https.get(meta.license.https_url, (res) => {
- const { statusCode } = res;
+ if (statusCode < 200 || statusCode >= 300) {
+ throw new Error(
+ `Failed to fetch license for: ${meta.name}, Status Code: ${statusCode}`
+ )
+ }
- 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 (meta.license.license_checksum == hash) {
- callback(meta, rawData)
- } else {
- throw Error(`Checksum for ${meta.name} did not match file downloaded from ${meta.license.https_url}`)
- }
- });
- }).on('error', (e) => {
- throw e
- });
- }
+ res.setEncoding("utf8")
+ let rawData = ""
+ res.on("data", (chunk) => {
+ rawData += chunk
+ })
+ res.on("end", () => {
+ const hash = crypto
+ .createHash("sha256")
+ .update(rawData)
+ .digest("hex")
+ if (meta.license.license_checksum == hash) {
+ callback(meta, rawData)
+ } else {
+ throw Error(
+ `Checksum for ${meta.name} did not match file downloaded from ${meta.license.https_url}`
+ )
+ }
+ })
+ })
+ .on("error", (e) => {
+ throw e
+ })
+ }
}
function writeLicense(schemeMeta: Meta, text: String) {
- process.stdout.write(`## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`)
+ process.stdout.write(
+ `## [${schemeMeta.name}](${schemeMeta.url})\n\n${text}\n********************************************************************************\n\n`
+ )
}
-const accepted_licenses = parseAcceptedToml(accepted_licenses_file);
+const accepted_licenses = parseAcceptedToml(accepted_licenses_file)
checkLicenses(schemeMeta, accepted_licenses)
getLicenseText(schemeMeta, (meta, text) => {
- writeLicense(meta, text)
-});
+ writeLicense(meta, text)
+})
@@ -1,50 +1,52 @@
-import * as fs from "fs";
-import { tmpdir } from "os";
-import * as path from "path";
-import colorSchemes, {
- staffColorSchemes,
-} from "./colorSchemes";
-import app from "./styleTree/app";
-import { ColorScheme } from "./themes/common/colorScheme";
-import snakeCase from "./utils/snakeCase";
+import * as fs from "fs"
+import { tmpdir } from "os"
+import * as path from "path"
+import colorSchemes, { staffColorSchemes } from "./colorSchemes"
+import app from "./styleTree/app"
+import { ColorScheme } from "./themes/common/colorScheme"
+import snakeCase from "./utils/snakeCase"
const assetsDirectory = `${__dirname}/../../assets`
-const themeDirectory = `${assetsDirectory}/themes`;
-const staffDirectory = `${themeDirectory}/staff`;
+const themeDirectory = `${assetsDirectory}/themes`
+const staffDirectory = `${themeDirectory}/staff`
-const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes"));
+const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes"))
// Clear existing themes
function clearThemes(themeDirectory: string) {
- if (!fs.existsSync(themeDirectory)) {
- fs.mkdirSync(themeDirectory, { recursive: true });
- } else {
- for (const file of fs.readdirSync(themeDirectory)) {
- if (file.endsWith(".json")) {
- const name = file.replace(/\.json$/, "");
- if (!colorSchemes.find((colorScheme) => colorScheme.name === name)) {
- fs.unlinkSync(path.join(themeDirectory, file));
+ if (!fs.existsSync(themeDirectory)) {
+ fs.mkdirSync(themeDirectory, { recursive: true })
+ } else {
+ for (const file of fs.readdirSync(themeDirectory)) {
+ if (file.endsWith(".json")) {
+ const name = file.replace(/\.json$/, "")
+ if (
+ !colorSchemes.find(
+ (colorScheme) => colorScheme.name === name
+ )
+ ) {
+ fs.unlinkSync(path.join(themeDirectory, file))
+ }
+ }
}
- }
}
- }
}
-clearThemes(themeDirectory);
-clearThemes(staffDirectory);
+clearThemes(themeDirectory)
+clearThemes(staffDirectory)
function writeThemes(colorSchemes: ColorScheme[], outputDirectory: string) {
- for (let colorScheme of colorSchemes) {
- let styleTree = snakeCase(app(colorScheme));
- let styleTreeJSON = JSON.stringify(styleTree, null, 2);
- let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`);
- let outPath = path.join(outputDirectory, `${colorScheme.name}.json`);
- fs.writeFileSync(tempPath, styleTreeJSON);
- fs.renameSync(tempPath, outPath);
- console.log(`- ${outPath} created`);
- }
+ for (let colorScheme of colorSchemes) {
+ let styleTree = snakeCase(app(colorScheme))
+ let styleTreeJSON = JSON.stringify(styleTree, null, 2)
+ let tempPath = path.join(tempDirectory, `${colorScheme.name}.json`)
+ let outPath = path.join(outputDirectory, `${colorScheme.name}.json`)
+ fs.writeFileSync(tempPath, styleTreeJSON)
+ fs.renameSync(tempPath, outPath)
+ console.log(`- ${outPath} created`)
+ }
}
// Write new themes to theme directory
-writeThemes(colorSchemes, themeDirectory);
-writeThemes(staffColorSchemes, staffDirectory);
+writeThemes(colorSchemes, themeDirectory)
+writeThemes(staffColorSchemes, staffDirectory)
@@ -1,54 +1,54 @@
-import fs from "fs";
-import path from "path";
-import { ColorScheme, Meta } from "./themes/common/colorScheme";
+import fs from "fs"
+import path from "path"
+import { ColorScheme, Meta } from "./themes/common/colorScheme"
-const colorSchemes: ColorScheme[] = [];
-export default colorSchemes;
+const colorSchemes: ColorScheme[] = []
+export default colorSchemes
-const schemeMeta: Meta[] = [];
-export { schemeMeta };
+const schemeMeta: Meta[] = []
+export { schemeMeta }
-const staffColorSchemes: ColorScheme[] = [];
-export { staffColorSchemes };
+const staffColorSchemes: ColorScheme[] = []
+export { staffColorSchemes }
-const experimentalColorSchemes: ColorScheme[] = [];
-export { experimentalColorSchemes };
+const experimentalColorSchemes: ColorScheme[] = []
+export { experimentalColorSchemes }
-const themes_directory = path.resolve(`${__dirname}/themes`);
+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);
+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).isFile()) {
+ const colorScheme = require(filePath)
+ callback(colorScheme, path.basename(filePath))
+ }
}
- }
}
function fillColorSchemes(themesPath: string, colorSchemes: ColorScheme[]) {
- for_all_color_schemes_in(themesPath, (colorScheme, _path) => {
- if (colorScheme.dark) colorSchemes.push(colorScheme.dark);
- if (colorScheme.light) colorSchemes.push(colorScheme.light);
- })
+ for_all_color_schemes_in(themesPath, (colorScheme, _path) => {
+ if (colorScheme.dark) colorSchemes.push(colorScheme.dark)
+ if (colorScheme.light) colorSchemes.push(colorScheme.light)
+ })
}
-fillColorSchemes(themes_directory, colorSchemes);
-fillColorSchemes(
- path.resolve(`${themes_directory}/staff`),
- staffColorSchemes
-);
+fillColorSchemes(themes_directory, colorSchemes)
+fillColorSchemes(path.resolve(`${themes_directory}/staff`), staffColorSchemes)
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`)
- }
- })
+ 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`)
+ }
+ })
}
-fillMeta(themes_directory, schemeMeta);
+fillMeta(themes_directory, schemeMeta)
@@ -1,66 +1,45 @@
export const fontFamilies = {
- sans: "Zed Sans",
- mono: "Zed Mono",
-};
+ sans: "Zed Sans",
+ mono: "Zed Mono",
+}
export const fontSizes = {
- "3xs": 8,
- "2xs": 10,
- xs: 12,
- sm: 14,
- md: 16,
- lg: 18,
- xl: 20,
-};
+ "3xs": 8,
+ "2xs": 10,
+ xs: 12,
+ sm: 14,
+ md: 16,
+ lg: 18,
+ xl: 20,
+}
export type FontWeight =
- | "thin"
- | "extra_light"
- | "light"
- | "normal"
- | "medium"
- | "semibold"
- | "bold"
- | "extra_bold"
- | "black";
+ | "thin"
+ | "extra_light"
+ | "light"
+ | "normal"
+ | "medium"
+ | "semibold"
+ | "bold"
+ | "extra_bold"
+ | "black"
export const fontWeights: { [key: string]: FontWeight } = {
- thin: "thin",
- extra_light: "extra_light",
- light: "light",
- normal: "normal",
- medium: "medium",
- semibold: "semibold",
- bold: "bold",
- extra_bold: "extra_bold",
- black: "black",
-};
+ thin: "thin",
+ extra_light: "extra_light",
+ light: "light",
+ normal: "normal",
+ medium: "medium",
+ semibold: "semibold",
+ bold: "bold",
+ extra_bold: "extra_bold",
+ black: "black",
+}
export const sizes = {
- px: 1,
- xs: 2,
- sm: 4,
- md: 6,
- lg: 8,
- xl: 12,
-};
-
-// export const colors = {
-// neutral: colorRamp(["white", "black"], { steps: 37, increment: 25 }), // (900/25) + 1
-// rose: colorRamp("#F43F5EFF"),
-// red: colorRamp("#EF4444FF"),
-// orange: colorRamp("#F97316FF"),
-// amber: colorRamp("#F59E0BFF"),
-// yellow: colorRamp("#EAB308FF"),
-// lime: colorRamp("#84CC16FF"),
-// green: colorRamp("#22C55EFF"),
-// emerald: colorRamp("#10B981FF"),
-// teal: colorRamp("#14B8A6FF"),
-// cyan: colorRamp("#06BBD4FF"),
-// sky: colorRamp("#0EA5E9FF"),
-// blue: colorRamp("#3B82F6FF"),
-// indigo: colorRamp("#6366F1FF"),
-// violet: colorRamp("#8B5CF6FF"),
-// purple: colorRamp("#A855F7FF"),
-// fuschia: colorRamp("#D946E4FF"),
-// pink: colorRamp("#EC4899FF"),
-// }
+ px: 1,
+ xs: 2,
+ sm: 4,
+ md: 6,
+ lg: 8,
+ xl: 12,
+}
@@ -1,72 +1,72 @@
-import { text } from "./components";
-import contactFinder from "./contactFinder";
-import contactsPopover from "./contactsPopover";
-import commandPalette from "./commandPalette";
-import editor from "./editor";
-import projectPanel from "./projectPanel";
-import search from "./search";
-import picker from "./picker";
-import workspace from "./workspace";
-import contextMenu from "./contextMenu";
-import sharedScreen from "./sharedScreen";
-import projectDiagnostics from "./projectDiagnostics";
-import contactNotification from "./contactNotification";
-import updateNotification from "./updateNotification";
-import simpleMessageNotification from "./simpleMessageNotification";
-import projectSharedNotification from "./projectSharedNotification";
-import tooltip from "./tooltip";
-import terminal from "./terminal";
-import contactList from "./contactList";
-import incomingCallNotification from "./incomingCallNotification";
-import { ColorScheme } from "../themes/common/colorScheme";
-import feedback from "./feedback";
+import { text } from "./components"
+import contactFinder from "./contactFinder"
+import contactsPopover from "./contactsPopover"
+import commandPalette from "./commandPalette"
+import editor from "./editor"
+import projectPanel from "./projectPanel"
+import search from "./search"
+import picker from "./picker"
+import workspace from "./workspace"
+import contextMenu from "./contextMenu"
+import sharedScreen from "./sharedScreen"
+import projectDiagnostics from "./projectDiagnostics"
+import contactNotification from "./contactNotification"
+import updateNotification from "./updateNotification"
+import simpleMessageNotification from "./simpleMessageNotification"
+import projectSharedNotification from "./projectSharedNotification"
+import tooltip from "./tooltip"
+import terminal from "./terminal"
+import contactList from "./contactList"
+import incomingCallNotification from "./incomingCallNotification"
+import { ColorScheme } from "../themes/common/colorScheme"
+import feedback from "./feedback"
export default function app(colorScheme: ColorScheme): Object {
- return {
- meta: {
- name: colorScheme.name,
- isLight: colorScheme.isLight,
- },
- commandPalette: commandPalette(colorScheme),
- contactNotification: contactNotification(colorScheme),
- projectSharedNotification: projectSharedNotification(colorScheme),
- incomingCallNotification: incomingCallNotification(colorScheme),
- picker: picker(colorScheme),
- workspace: workspace(colorScheme),
- contextMenu: contextMenu(colorScheme),
- editor: editor(colorScheme),
- projectDiagnostics: projectDiagnostics(colorScheme),
- projectPanel: projectPanel(colorScheme),
- contactsPopover: contactsPopover(colorScheme),
- contactFinder: contactFinder(colorScheme),
- contactList: contactList(colorScheme),
- search: search(colorScheme),
- sharedScreen: sharedScreen(colorScheme),
- breadcrumbs: {
- ...text(colorScheme.highest, "sans", "variant"),
- padding: {
- left: 6,
- },
- },
- updateNotification: updateNotification(colorScheme),
- simpleMessageNotification: simpleMessageNotification(colorScheme),
- tooltip: tooltip(colorScheme),
- terminal: terminal(colorScheme),
- feedback: feedback(colorScheme),
- colorScheme: {
- ...colorScheme,
- players: Object.values(colorScheme.players),
- ramps: {
- neutral: colorScheme.ramps.neutral.colors(100, "hex"),
- red: colorScheme.ramps.red.colors(100, "hex"),
- orange: colorScheme.ramps.orange.colors(100, "hex"),
- yellow: colorScheme.ramps.yellow.colors(100, "hex"),
- green: colorScheme.ramps.green.colors(100, "hex"),
- cyan: colorScheme.ramps.cyan.colors(100, "hex"),
- blue: colorScheme.ramps.blue.colors(100, "hex"),
- violet: colorScheme.ramps.violet.colors(100, "hex"),
- magenta: colorScheme.ramps.magenta.colors(100, "hex"),
- },
- },
- };
+ return {
+ meta: {
+ name: colorScheme.name,
+ isLight: colorScheme.isLight,
+ },
+ commandPalette: commandPalette(colorScheme),
+ contactNotification: contactNotification(colorScheme),
+ projectSharedNotification: projectSharedNotification(colorScheme),
+ incomingCallNotification: incomingCallNotification(colorScheme),
+ picker: picker(colorScheme),
+ workspace: workspace(colorScheme),
+ contextMenu: contextMenu(colorScheme),
+ editor: editor(colorScheme),
+ projectDiagnostics: projectDiagnostics(colorScheme),
+ projectPanel: projectPanel(colorScheme),
+ contactsPopover: contactsPopover(colorScheme),
+ contactFinder: contactFinder(colorScheme),
+ contactList: contactList(colorScheme),
+ search: search(colorScheme),
+ sharedScreen: sharedScreen(colorScheme),
+ breadcrumbs: {
+ ...text(colorScheme.highest, "sans", "variant"),
+ padding: {
+ left: 6,
+ },
+ },
+ updateNotification: updateNotification(colorScheme),
+ simpleMessageNotification: simpleMessageNotification(colorScheme),
+ tooltip: tooltip(colorScheme),
+ terminal: terminal(colorScheme),
+ feedback: feedback(colorScheme),
+ colorScheme: {
+ ...colorScheme,
+ players: Object.values(colorScheme.players),
+ ramps: {
+ neutral: colorScheme.ramps.neutral.colors(100, "hex"),
+ red: colorScheme.ramps.red.colors(100, "hex"),
+ orange: colorScheme.ramps.orange.colors(100, "hex"),
+ yellow: colorScheme.ramps.yellow.colors(100, "hex"),
+ green: colorScheme.ramps.green.colors(100, "hex"),
+ cyan: colorScheme.ramps.cyan.colors(100, "hex"),
+ blue: colorScheme.ramps.blue.colors(100, "hex"),
+ violet: colorScheme.ramps.violet.colors(100, "hex"),
+ magenta: colorScheme.ramps.magenta.colors(100, "hex"),
+ },
+ },
+ }
}
@@ -1,30 +1,30 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { withOpacity } from "../utils/color";
-import { text, background } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { withOpacity } from "../utils/color"
+import { text, background } from "./components"
export default function commandPalette(colorScheme: ColorScheme) {
- let layer = colorScheme.highest;
- return {
- keystrokeSpacing: 8,
- key: {
- text: text(layer, "mono", "variant", "default", { size: "xs" }),
- cornerRadius: 2,
- background: background(layer, "on"),
- padding: {
- top: 1,
- bottom: 1,
- left: 6,
- right: 6,
- },
- margin: {
- top: 1,
- bottom: 1,
- left: 2,
- },
- active: {
- text: text(layer, "mono", "on", "default", { size: "xs" }),
- background: withOpacity(background(layer, "on"), 0.2),
- },
- },
- };
+ let layer = colorScheme.highest
+ return {
+ keystrokeSpacing: 8,
+ key: {
+ text: text(layer, "mono", "variant", "default", { size: "xs" }),
+ cornerRadius: 2,
+ background: background(layer, "on"),
+ padding: {
+ top: 1,
+ bottom: 1,
+ left: 6,
+ right: 6,
+ },
+ margin: {
+ top: 1,
+ bottom: 1,
+ left: 2,
+ },
+ active: {
+ text: text(layer, "mono", "on", "default", { size: "xs" }),
+ background: withOpacity(background(layer, "on"), 0.2),
+ },
+ },
+ }
}
@@ -1,210 +1,210 @@
-import { fontFamilies, fontSizes, FontWeight } from "../common";
-import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme";
+import { fontFamilies, fontSizes, FontWeight } from "../common"
+import { Layer, Styles, StyleSets, Style } from "../themes/common/colorScheme"
function isStyleSet(key: any): key is StyleSets {
- return [
- "base",
- "variant",
- "on",
- "accent",
- "positive",
- "warning",
- "negative",
- ].includes(key);
+ return [
+ "base",
+ "variant",
+ "on",
+ "accent",
+ "positive",
+ "warning",
+ "negative",
+ ].includes(key)
}
function isStyle(key: any): key is Styles {
- return [
- "default",
- "active",
- "disabled",
- "hovered",
- "pressed",
- "inverted",
- ].includes(key);
+ return [
+ "default",
+ "active",
+ "disabled",
+ "hovered",
+ "pressed",
+ "inverted",
+ ].includes(key)
}
function getStyle(
- layer: Layer,
- possibleStyleSetOrStyle?: any,
- possibleStyle?: any
+ layer: Layer,
+ possibleStyleSetOrStyle?: any,
+ possibleStyle?: any
): Style {
- let styleSet: StyleSets = "base";
- let style: Styles = "default";
- if (isStyleSet(possibleStyleSetOrStyle)) {
- styleSet = possibleStyleSetOrStyle;
- } else if (isStyle(possibleStyleSetOrStyle)) {
- style = possibleStyleSetOrStyle;
- }
-
- if (isStyle(possibleStyle)) {
- style = possibleStyle;
- }
-
- return layer[styleSet][style];
+ let styleSet: StyleSets = "base"
+ let style: Styles = "default"
+ if (isStyleSet(possibleStyleSetOrStyle)) {
+ styleSet = possibleStyleSetOrStyle
+ } else if (isStyle(possibleStyleSetOrStyle)) {
+ style = possibleStyleSetOrStyle
+ }
+
+ if (isStyle(possibleStyle)) {
+ style = possibleStyle
+ }
+
+ return layer[styleSet][style]
}
-export function background(layer: Layer, style?: Styles): string;
+export function background(layer: Layer, style?: Styles): string
export function background(
- layer: Layer,
- styleSet?: StyleSets,
- style?: Styles
-): string;
+ layer: Layer,
+ styleSet?: StyleSets,
+ style?: Styles
+): string
export function background(
- layer: Layer,
- styleSetOrStyles?: StyleSets | Styles,
- style?: Styles
+ layer: Layer,
+ styleSetOrStyles?: StyleSets | Styles,
+ style?: Styles
): string {
- return getStyle(layer, styleSetOrStyles, style).background;
+ return getStyle(layer, styleSetOrStyles, style).background
}
-export function borderColor(layer: Layer, style?: Styles): string;
+export function borderColor(layer: Layer, style?: Styles): string
export function borderColor(
- layer: Layer,
- styleSet?: StyleSets,
- style?: Styles
-): string;
+ layer: Layer,
+ styleSet?: StyleSets,
+ style?: Styles
+): string
export function borderColor(
- layer: Layer,
- styleSetOrStyles?: StyleSets | Styles,
- style?: Styles
+ layer: Layer,
+ styleSetOrStyles?: StyleSets | Styles,
+ style?: Styles
): string {
- return getStyle(layer, styleSetOrStyles, style).border;
+ return getStyle(layer, styleSetOrStyles, style).border
}
-export function foreground(layer: Layer, style?: Styles): string;
+export function foreground(layer: Layer, style?: Styles): string
export function foreground(
- layer: Layer,
- styleSet?: StyleSets,
- style?: Styles
-): string;
+ layer: Layer,
+ styleSet?: StyleSets,
+ style?: Styles
+): string
export function foreground(
- layer: Layer,
- styleSetOrStyles?: StyleSets | Styles,
- style?: Styles
+ layer: Layer,
+ styleSetOrStyles?: StyleSets | Styles,
+ style?: Styles
): string {
- return getStyle(layer, styleSetOrStyles, style).foreground;
+ return getStyle(layer, styleSetOrStyles, style).foreground
}
interface Text {
- family: keyof typeof fontFamilies;
- color: string;
- size: number;
- weight?: FontWeight;
- underline?: boolean;
+ family: keyof typeof fontFamilies
+ color: string
+ size: number
+ weight?: FontWeight
+ underline?: boolean
}
interface TextProperties {
- size?: keyof typeof fontSizes;
- weight?: FontWeight;
- underline?: boolean;
- color?: string;
+ size?: keyof typeof fontSizes
+ weight?: FontWeight
+ underline?: boolean
+ color?: string
}
export function text(
- layer: Layer,
- fontFamily: keyof typeof fontFamilies,
- styleSet: StyleSets,
- style: Styles,
- properties?: TextProperties
-): Text;
+ layer: Layer,
+ fontFamily: keyof typeof fontFamilies,
+ styleSet: StyleSets,
+ style: Styles,
+ properties?: TextProperties
+): Text
export function text(
- layer: Layer,
- fontFamily: keyof typeof fontFamilies,
- styleSet: StyleSets,
- properties?: TextProperties
-): Text;
+ layer: Layer,
+ fontFamily: keyof typeof fontFamilies,
+ styleSet: StyleSets,
+ properties?: TextProperties
+): Text
export function text(
- layer: Layer,
- fontFamily: keyof typeof fontFamilies,
- style: Styles,
- properties?: TextProperties
-): Text;
+ layer: Layer,
+ fontFamily: keyof typeof fontFamilies,
+ style: Styles,
+ properties?: TextProperties
+): Text
export function text(
- layer: Layer,
- fontFamily: keyof typeof fontFamilies,
- properties?: TextProperties
-): Text;
+ layer: Layer,
+ fontFamily: keyof typeof fontFamilies,
+ properties?: TextProperties
+): Text
export function text(
- layer: Layer,
- fontFamily: keyof typeof fontFamilies,
- styleSetStyleOrProperties?: StyleSets | Styles | TextProperties,
- styleOrProperties?: Styles | TextProperties,
- properties?: TextProperties
+ layer: Layer,
+ fontFamily: keyof typeof fontFamilies,
+ styleSetStyleOrProperties?: StyleSets | Styles | TextProperties,
+ styleOrProperties?: Styles | TextProperties,
+ properties?: TextProperties
) {
- let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties);
-
- if (typeof styleSetStyleOrProperties === "object") {
- properties = styleSetStyleOrProperties;
- }
- if (typeof styleOrProperties === "object") {
- properties = styleOrProperties;
- }
-
- let size = fontSizes[properties?.size || "sm"];
- let color = properties?.color || style.foreground;
-
- return {
- family: fontFamilies[fontFamily],
- ...properties,
- color,
- size,
- };
+ let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties)
+
+ if (typeof styleSetStyleOrProperties === "object") {
+ properties = styleSetStyleOrProperties
+ }
+ if (typeof styleOrProperties === "object") {
+ properties = styleOrProperties
+ }
+
+ let size = fontSizes[properties?.size || "sm"]
+ let color = properties?.color || style.foreground
+
+ return {
+ family: fontFamilies[fontFamily],
+ ...properties,
+ color,
+ size,
+ }
}
export interface Border {
- color: string;
- width: number;
- top?: boolean;
- bottom?: boolean;
- left?: boolean;
- right?: boolean;
- overlay?: boolean;
+ color: string
+ width: number
+ top?: boolean
+ bottom?: boolean
+ left?: boolean
+ right?: boolean
+ overlay?: boolean
}
export interface BorderProperties {
- width?: number;
- top?: boolean;
- bottom?: boolean;
- left?: boolean;
- right?: boolean;
- overlay?: boolean;
+ width?: number
+ top?: boolean
+ bottom?: boolean
+ left?: boolean
+ right?: boolean
+ overlay?: boolean
}
export function border(
- layer: Layer,
- styleSet: StyleSets,
- style: Styles,
- properties?: BorderProperties
-): Border;
+ layer: Layer,
+ styleSet: StyleSets,
+ style: Styles,
+ properties?: BorderProperties
+): Border
export function border(
- layer: Layer,
- styleSet: StyleSets,
- properties?: BorderProperties
-): Border;
+ layer: Layer,
+ styleSet: StyleSets,
+ properties?: BorderProperties
+): Border
export function border(
- layer: Layer,
- style: Styles,
- properties?: BorderProperties
-): Border;
-export function border(layer: Layer, properties?: BorderProperties): Border;
+ layer: Layer,
+ style: Styles,
+ properties?: BorderProperties
+): Border
+export function border(layer: Layer, properties?: BorderProperties): Border
export function border(
- layer: Layer,
- styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties,
- styleOrProperties?: Styles | BorderProperties,
- properties?: BorderProperties
+ layer: Layer,
+ styleSetStyleOrProperties?: StyleSets | Styles | BorderProperties,
+ styleOrProperties?: Styles | BorderProperties,
+ properties?: BorderProperties
): Border {
- let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties);
-
- if (typeof styleSetStyleOrProperties === "object") {
- properties = styleSetStyleOrProperties;
- }
- if (typeof styleOrProperties === "object") {
- properties = styleOrProperties;
- }
-
- return {
- color: style.border,
- width: 1,
- ...properties,
- };
+ let style = getStyle(layer, styleSetStyleOrProperties, styleOrProperties)
+
+ if (typeof styleSetStyleOrProperties === "object") {
+ properties = styleSetStyleOrProperties
+ }
+ if (typeof styleOrProperties === "object") {
+ properties = styleOrProperties
+ }
+
+ return {
+ color: style.border,
+ width: 1,
+ ...properties,
+ }
}
@@ -1,70 +1,70 @@
-import picker from "./picker";
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, foreground, text } from "./components";
+import picker from "./picker"
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, foreground, text } from "./components"
export default function contactFinder(colorScheme: ColorScheme) {
- let layer = colorScheme.middle;
+ let layer = colorScheme.middle
- const sideMargin = 6;
- const contactButton = {
- background: background(layer, "variant"),
- color: foreground(layer, "variant"),
- iconWidth: 8,
- buttonWidth: 16,
- cornerRadius: 8,
- };
+ const sideMargin = 6
+ const contactButton = {
+ background: background(layer, "variant"),
+ color: foreground(layer, "variant"),
+ iconWidth: 8,
+ buttonWidth: 16,
+ cornerRadius: 8,
+ }
- const pickerStyle = picker(colorScheme);
- const pickerInput = {
- background: background(layer, "on"),
- cornerRadius: 6,
- text: text(layer, "mono",),
- placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
- selection: colorScheme.players[0],
- border: border(layer),
- padding: {
- bottom: 4,
- left: 8,
- right: 8,
- top: 4,
- },
- margin: {
- left: sideMargin,
- right: sideMargin,
+ const pickerStyle = picker(colorScheme)
+ const pickerInput = {
+ background: background(layer, "on"),
+ cornerRadius: 6,
+ text: text(layer, "mono"),
+ placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
+ selection: colorScheme.players[0],
+ border: border(layer),
+ padding: {
+ bottom: 4,
+ left: 8,
+ right: 8,
+ top: 4,
+ },
+ margin: {
+ left: sideMargin,
+ right: sideMargin,
+ },
}
- };
- return {
- picker: {
- emptyContainer: {},
- item: {
- ...pickerStyle.item,
- margin: { left: sideMargin, right: sideMargin },
- },
- noMatches: pickerStyle.noMatches,
- inputEditor: pickerInput,
- emptyInputEditor: pickerInput
- },
- rowHeight: 28,
- contactAvatar: {
- cornerRadius: 10,
- width: 18,
- },
- contactUsername: {
- padding: {
- left: 8,
- },
- },
- contactButton: {
- ...contactButton,
- hover: {
- background: background(layer, "variant", "hovered"),
- },
- },
- disabledContactButton: {
- ...contactButton,
- background: background(layer, "disabled"),
- color: foreground(layer, "disabled"),
- },
- };
+ return {
+ picker: {
+ emptyContainer: {},
+ item: {
+ ...pickerStyle.item,
+ margin: { left: sideMargin, right: sideMargin },
+ },
+ noMatches: pickerStyle.noMatches,
+ inputEditor: pickerInput,
+ emptyInputEditor: pickerInput,
+ },
+ rowHeight: 28,
+ contactAvatar: {
+ cornerRadius: 10,
+ width: 18,
+ },
+ contactUsername: {
+ padding: {
+ left: 8,
+ },
+ },
+ contactButton: {
+ ...contactButton,
+ hover: {
+ background: background(layer, "variant", "hovered"),
+ },
+ },
+ disabledContactButton: {
+ ...contactButton,
+ background: background(layer, "disabled"),
+ color: foreground(layer, "disabled"),
+ },
+ }
}
@@ -1,186 +1,182 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import {
- background,
- border,
- borderColor,
- foreground,
- text,
-} from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, borderColor, foreground, text } from "./components"
export default function contactsPanel(colorScheme: ColorScheme) {
- const nameMargin = 8;
- const sidePadding = 12;
+ const nameMargin = 8
+ const sidePadding = 12
- let layer = colorScheme.middle;
+ let layer = colorScheme.middle
- const contactButton = {
- background: background(layer, "on"),
- color: foreground(layer, "on"),
- iconWidth: 8,
- buttonWidth: 16,
- cornerRadius: 8,
- };
- const projectRow = {
- guestAvatarSpacing: 4,
- height: 24,
- guestAvatar: {
- cornerRadius: 8,
- width: 14,
- },
- name: {
- ...text(layer, "mono", { size: "sm" }),
- margin: {
- left: nameMargin,
- right: 6,
- },
- },
- guests: {
- margin: {
- left: nameMargin,
- right: nameMargin,
- },
- },
- padding: {
- left: sidePadding,
- right: sidePadding,
- },
- };
+ const contactButton = {
+ background: background(layer, "on"),
+ color: foreground(layer, "on"),
+ iconWidth: 8,
+ buttonWidth: 16,
+ cornerRadius: 8,
+ }
+ const projectRow = {
+ guestAvatarSpacing: 4,
+ height: 24,
+ guestAvatar: {
+ cornerRadius: 8,
+ width: 14,
+ },
+ name: {
+ ...text(layer, "mono", { size: "sm" }),
+ margin: {
+ left: nameMargin,
+ right: 6,
+ },
+ },
+ guests: {
+ margin: {
+ left: nameMargin,
+ right: nameMargin,
+ },
+ },
+ padding: {
+ left: sidePadding,
+ right: sidePadding,
+ },
+ }
- return {
- background: background(layer),
- padding: { top: 12, bottom: 0 },
- userQueryEditor: {
- background: background(layer, "on"),
- cornerRadius: 6,
- text: text(layer, "mono", "on"),
- placeholderText: text(layer, "mono", "on", "disabled", { size: "xs" }),
- selection: colorScheme.players[0],
- border: border(layer, "on"),
- padding: {
- bottom: 4,
- left: 8,
- right: 8,
- top: 4,
- },
- margin: {
- left: 6,
- },
- },
- userQueryEditorHeight: 33,
- addContactButton: {
- margin: { left: 6, right: 12 },
- color: foreground(layer, "on"),
- buttonWidth: 28,
- iconWidth: 16,
- },
- rowHeight: 28,
- sectionIconSize: 8,
- headerRow: {
- ...text(layer, "mono", { size: "sm" }),
- margin: { top: 14 },
- padding: {
- left: sidePadding,
- right: sidePadding,
- },
- active: {
- ...text(layer, "mono", "active", { size: "sm" }),
- background: background(layer, "active"),
- },
- },
- leaveCall: {
- background: background(layer),
- border: border(layer),
- cornerRadius: 6,
- margin: {
- top: 1,
- },
- padding: {
- top: 1,
- bottom: 1,
- left: 7,
- right: 7,
- },
- ...text(layer, "sans", "variant", { size: "xs" }),
- hover: {
- ...text(layer, "sans", "hovered", { size: "xs" }),
- background: background(layer, "hovered"),
- border: border(layer, "hovered"),
- },
- },
- contactRow: {
- padding: {
- left: sidePadding,
- right: sidePadding,
- },
- active: {
- background: background(layer, "active"),
- },
- },
- contactAvatar: {
- cornerRadius: 10,
- width: 18,
- },
- contactStatusFree: {
- cornerRadius: 4,
- padding: 4,
- margin: { top: 12, left: 12 },
- background: foreground(layer, "positive"),
- },
- contactStatusBusy: {
- cornerRadius: 4,
- padding: 4,
- margin: { top: 12, left: 12 },
- background: foreground(layer, "negative"),
- },
- contactUsername: {
- ...text(layer, "mono", { size: "sm" }),
- margin: {
- left: nameMargin,
- },
- },
- contactButtonSpacing: nameMargin,
- contactButton: {
- ...contactButton,
- hover: {
- background: background(layer, "hovered"),
- },
- },
- disabledButton: {
- ...contactButton,
- background: background(layer, "on"),
- color: foreground(layer, "on"),
- },
- callingIndicator: {
- ...text(layer, "mono", "variant", { size: "xs" }),
- },
- treeBranch: {
- color: borderColor(layer),
- width: 1,
- hover: {
- color: borderColor(layer),
- },
- active: {
- color: borderColor(layer),
- },
- },
- projectRow: {
- ...projectRow,
- background: background(layer),
- icon: {
- margin: { left: nameMargin },
- color: foreground(layer, "variant"),
- width: 12,
- },
- name: {
- ...projectRow.name,
- ...text(layer, "mono", { size: "sm" }),
- },
- hover: {
- background: background(layer, "hovered"),
- },
- active: {
- background: background(layer, "active"),
- },
- },
- };
+ return {
+ background: background(layer),
+ padding: { top: 12, bottom: 0 },
+ userQueryEditor: {
+ background: background(layer, "on"),
+ cornerRadius: 6,
+ text: text(layer, "mono", "on"),
+ placeholderText: text(layer, "mono", "on", "disabled", {
+ size: "xs",
+ }),
+ selection: colorScheme.players[0],
+ border: border(layer, "on"),
+ padding: {
+ bottom: 4,
+ left: 8,
+ right: 8,
+ top: 4,
+ },
+ margin: {
+ left: 6,
+ },
+ },
+ userQueryEditorHeight: 33,
+ addContactButton: {
+ margin: { left: 6, right: 12 },
+ color: foreground(layer, "on"),
+ buttonWidth: 28,
+ iconWidth: 16,
+ },
+ rowHeight: 28,
+ sectionIconSize: 8,
+ headerRow: {
+ ...text(layer, "mono", { size: "sm" }),
+ margin: { top: 14 },
+ padding: {
+ left: sidePadding,
+ right: sidePadding,
+ },
+ active: {
+ ...text(layer, "mono", "active", { size: "sm" }),
+ background: background(layer, "active"),
+ },
+ },
+ leaveCall: {
+ background: background(layer),
+ border: border(layer),
+ cornerRadius: 6,
+ margin: {
+ top: 1,
+ },
+ padding: {
+ top: 1,
+ bottom: 1,
+ left: 7,
+ right: 7,
+ },
+ ...text(layer, "sans", "variant", { size: "xs" }),
+ hover: {
+ ...text(layer, "sans", "hovered", { size: "xs" }),
+ background: background(layer, "hovered"),
+ border: border(layer, "hovered"),
+ },
+ },
+ contactRow: {
+ padding: {
+ left: sidePadding,
+ right: sidePadding,
+ },
+ active: {
+ background: background(layer, "active"),
+ },
+ },
+ contactAvatar: {
+ cornerRadius: 10,
+ width: 18,
+ },
+ contactStatusFree: {
+ cornerRadius: 4,
+ padding: 4,
+ margin: { top: 12, left: 12 },
+ background: foreground(layer, "positive"),
+ },
+ contactStatusBusy: {
+ cornerRadius: 4,
+ padding: 4,
+ margin: { top: 12, left: 12 },
+ background: foreground(layer, "negative"),
+ },
+ contactUsername: {
+ ...text(layer, "mono", { size: "sm" }),
+ margin: {
+ left: nameMargin,
+ },
+ },
+ contactButtonSpacing: nameMargin,
+ contactButton: {
+ ...contactButton,
+ hover: {
+ background: background(layer, "hovered"),
+ },
+ },
+ disabledButton: {
+ ...contactButton,
+ background: background(layer, "on"),
+ color: foreground(layer, "on"),
+ },
+ callingIndicator: {
+ ...text(layer, "mono", "variant", { size: "xs" }),
+ },
+ treeBranch: {
+ color: borderColor(layer),
+ width: 1,
+ hover: {
+ color: borderColor(layer),
+ },
+ active: {
+ color: borderColor(layer),
+ },
+ },
+ projectRow: {
+ ...projectRow,
+ background: background(layer),
+ icon: {
+ margin: { left: nameMargin },
+ color: foreground(layer, "variant"),
+ width: 12,
+ },
+ name: {
+ ...projectRow.name,
+ ...text(layer, "mono", { size: "sm" }),
+ },
+ hover: {
+ background: background(layer, "hovered"),
+ },
+ active: {
+ background: background(layer, "active"),
+ },
+ },
+ }
}
@@ -1,45 +1,45 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, foreground, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, foreground, text } from "./components"
-const avatarSize = 12;
-const headerPadding = 8;
+const avatarSize = 12
+const headerPadding = 8
export default function contactNotification(colorScheme: ColorScheme): Object {
- let layer = colorScheme.lowest;
- return {
- headerAvatar: {
- height: avatarSize,
- width: avatarSize,
- cornerRadius: 6,
- },
- headerMessage: {
- ...text(layer, "sans", { size: "xs" }),
- margin: { left: headerPadding, right: headerPadding },
- },
- headerHeight: 18,
- bodyMessage: {
- ...text(layer, "sans", { size: "xs" }),
- margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
- },
- button: {
- ...text(layer, "sans", "on", { size: "xs" }),
- background: background(layer, "on"),
- padding: 4,
- cornerRadius: 6,
- margin: { left: 6 },
- hover: {
- background: background(layer, "on", "hovered"),
- },
- },
- dismissButton: {
- color: foreground(layer, "variant"),
- iconWidth: 8,
- iconHeight: 8,
- buttonWidth: 8,
- buttonHeight: 8,
- hover: {
- color: foreground(layer, "hovered"),
- },
- },
- };
+ let layer = colorScheme.lowest
+ return {
+ headerAvatar: {
+ height: avatarSize,
+ width: avatarSize,
+ cornerRadius: 6,
+ },
+ headerMessage: {
+ ...text(layer, "sans", { size: "xs" }),
+ margin: { left: headerPadding, right: headerPadding },
+ },
+ headerHeight: 18,
+ bodyMessage: {
+ ...text(layer, "sans", { size: "xs" }),
+ margin: { left: avatarSize + headerPadding, top: 6, bottom: 6 },
+ },
+ button: {
+ ...text(layer, "sans", "on", { size: "xs" }),
+ background: background(layer, "on"),
+ padding: 4,
+ cornerRadius: 6,
+ margin: { left: 6 },
+ hover: {
+ background: background(layer, "on", "hovered"),
+ },
+ },
+ dismissButton: {
+ color: foreground(layer, "variant"),
+ iconWidth: 8,
+ iconHeight: 8,
+ buttonWidth: 8,
+ buttonHeight: 8,
+ hover: {
+ color: foreground(layer, "hovered"),
+ },
+ },
+ }
}
@@ -1,29 +1,29 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
export default function contactsPopover(colorScheme: ColorScheme) {
- let layer = colorScheme.middle;
- const sidePadding = 12;
- return {
- background: background(layer),
- cornerRadius: 6,
- padding: { top: 6 },
- margin: { top: -6 },
- shadow: colorScheme.popoverShadow,
- border: border(layer),
- width: 300,
- height: 400,
- inviteRowHeight: 28,
- inviteRow: {
- padding: {
- left: sidePadding,
- right: sidePadding,
- },
- border: border(layer, { top: true }),
- text: text(layer, "sans", "variant", { size: "sm" }),
- hover: {
- text: text(layer, "sans", "hovered", { size: "sm" }),
- },
- },
- }
+ let layer = colorScheme.middle
+ const sidePadding = 12
+ return {
+ background: background(layer),
+ cornerRadius: 6,
+ padding: { top: 6 },
+ margin: { top: -6 },
+ shadow: colorScheme.popoverShadow,
+ border: border(layer),
+ width: 300,
+ height: 400,
+ inviteRowHeight: 28,
+ inviteRow: {
+ padding: {
+ left: sidePadding,
+ right: sidePadding,
+ },
+ border: border(layer, { top: true }),
+ text: text(layer, "sans", "variant", { size: "sm" }),
+ hover: {
+ text: text(layer, "sans", "hovered", { size: "sm" }),
+ },
+ },
+ }
}
@@ -1,41 +1,44 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, borderColor, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, borderColor, text } from "./components"
export default function contextMenu(colorScheme: ColorScheme) {
- let layer = colorScheme.middle;
- return {
- background: background(layer),
- cornerRadius: 10,
- padding: 4,
- shadow: colorScheme.popoverShadow,
- border: border(layer),
- keystrokeMargin: 30,
- item: {
- iconSpacing: 8,
- iconWidth: 14,
- padding: { left: 6, right: 6, top: 2, bottom: 2 },
- cornerRadius: 6,
- label: text(layer, "sans", { size: "sm" }),
- keystroke: {
- ...text(layer, "sans", "variant", { size: "sm", weight: "bold" }),
- padding: { left: 3, right: 3 },
- },
- hover: {
- background: background(layer, "hovered"),
- label: text(layer, "sans", "hovered", { size: "sm" }),
- },
- active: {
- background: background(layer, "active"),
- label: text(layer, "sans", "active", { size: "sm" }),
- },
- activeHover: {
- background: background(layer, "active"),
- label: text(layer, "sans", "active", { size: "sm" }),
- },
- },
- separator: {
- background: borderColor(layer),
- margin: { top: 2, bottom: 2 },
- },
- };
+ let layer = colorScheme.middle
+ return {
+ background: background(layer),
+ cornerRadius: 10,
+ padding: 4,
+ shadow: colorScheme.popoverShadow,
+ border: border(layer),
+ keystrokeMargin: 30,
+ item: {
+ iconSpacing: 8,
+ iconWidth: 14,
+ padding: { left: 6, right: 6, top: 2, bottom: 2 },
+ cornerRadius: 6,
+ label: text(layer, "sans", { size: "sm" }),
+ keystroke: {
+ ...text(layer, "sans", "variant", {
+ size: "sm",
+ weight: "bold",
+ }),
+ padding: { left: 3, right: 3 },
+ },
+ hover: {
+ background: background(layer, "hovered"),
+ label: text(layer, "sans", "hovered", { size: "sm" }),
+ },
+ active: {
+ background: background(layer, "active"),
+ label: text(layer, "sans", "active", { size: "sm" }),
+ },
+ activeHover: {
+ background: background(layer, "active"),
+ label: text(layer, "sans", "active", { size: "sm" }),
+ },
+ },
+ separator: {
+ background: borderColor(layer),
+ margin: { top: 2, bottom: 2 },
+ },
+ }
}
@@ -1,285 +1,317 @@
-import { fontWeights } from "../common";
-import { withOpacity } from "../utils/color";
-import { ColorScheme, Layer, StyleSets } from "../themes/common/colorScheme";
+import { fontWeights } from "../common"
+import { withOpacity } from "../utils/color"
import {
- background,
- border,
- borderColor,
- foreground,
- text,
-} from "./components";
-import hoverPopover from "./hoverPopover";
+ ColorScheme,
+ Layer,
+ StyleSets,
+ Syntax,
+ ThemeSyntax,
+} from "../themes/common/colorScheme"
+import { background, border, borderColor, foreground, text } from "./components"
+import hoverPopover from "./hoverPopover"
+
+import deepmerge from "deepmerge"
export default function editor(colorScheme: ColorScheme) {
- let layer = colorScheme.highest;
+ let layer = colorScheme.highest
- const autocompleteItem = {
- cornerRadius: 6,
- padding: {
- bottom: 2,
- left: 6,
- right: 6,
- top: 2,
- },
- };
+ const autocompleteItem = {
+ cornerRadius: 6,
+ padding: {
+ bottom: 2,
+ left: 6,
+ right: 6,
+ top: 2,
+ },
+ }
- function diagnostic(layer: Layer, styleSet: StyleSets) {
- return {
- textScaleFactor: 0.857,
- header: {
- border: border(layer, {
- top: true,
- }),
- },
- message: {
- text: text(layer, "sans", styleSet, "default", { size: "sm" }),
- highlightText: text(layer, "sans", styleSet, "default", {
- size: "sm",
- weight: "bold",
- }),
- },
- };
- }
+ function diagnostic(layer: Layer, styleSet: StyleSets) {
+ return {
+ textScaleFactor: 0.857,
+ header: {
+ border: border(layer, {
+ top: true,
+ }),
+ },
+ message: {
+ text: text(layer, "sans", styleSet, "default", { size: "sm" }),
+ highlightText: text(layer, "sans", styleSet, "default", {
+ size: "sm",
+ weight: "bold",
+ }),
+ },
+ }
+ }
+
+ const defaultSyntax: Syntax = {
+ primary: {
+ color: colorScheme.ramps.neutral(1).hex(),
+ weight: fontWeights.normal,
+ },
+ "variable.special": {
+ // Highlights for self, this, etc
+ color: colorScheme.ramps.blue(0.7).hex(),
+ weight: fontWeights.normal,
+ },
+ comment: {
+ color: colorScheme.ramps.neutral(0.71).hex(),
+ weight: fontWeights.normal,
+ },
+ punctuation: {
+ color: colorScheme.ramps.neutral(0.86).hex(),
+ weight: fontWeights.normal,
+ },
+ constant: {
+ color: colorScheme.ramps.green(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ keyword: {
+ color: colorScheme.ramps.blue(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ function: {
+ color: colorScheme.ramps.yellow(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ type: {
+ color: colorScheme.ramps.cyan(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ constructor: {
+ color: colorScheme.ramps.blue(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ variant: {
+ color: colorScheme.ramps.blue(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ property: {
+ color: colorScheme.ramps.blue(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ enum: {
+ color: colorScheme.ramps.orange(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ operator: {
+ color: colorScheme.ramps.orange(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ string: {
+ color: colorScheme.ramps.orange(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ number: {
+ color: colorScheme.ramps.green(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ boolean: {
+ color: colorScheme.ramps.green(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ predictive: {
+ color: colorScheme.ramps.neutral(0.57).hex(),
+ weight: fontWeights.normal,
+ },
+ title: {
+ color: colorScheme.ramps.yellow(0.5).hex(),
+ weight: fontWeights.bold,
+ },
+ emphasis: {
+ color: colorScheme.ramps.blue(0.5).hex(),
+ weight: fontWeights.normal,
+ },
+ "emphasis.strong": {
+ color: colorScheme.ramps.blue(0.5).hex(),
+ weight: fontWeights.bold,
+ },
+ linkUri: {
+ color: colorScheme.ramps.green(0.5).hex(),
+ weight: fontWeights.normal,
+ underline: true,
+ },
+ linkText: {
+ color: colorScheme.ramps.orange(0.5).hex(),
+ weight: fontWeights.normal,
+ italic: true,
+ },
+ }
+
+ function createSyntax(colorScheme: ColorScheme): Syntax {
+ if (!colorScheme.syntax) {
+ return defaultSyntax
+ }
- const syntax = {
- primary: {
- color: colorScheme.ramps.neutral(1).hex(),
- weight: fontWeights.normal,
- },
- "variable.special": {
- // Highlights for self, this, etc
- color: colorScheme.ramps.blue(0.7).hex(),
- weight: fontWeights.normal,
- },
- comment: {
- color: colorScheme.ramps.neutral(0.71).hex(),
- weight: fontWeights.normal,
- },
- punctuation: {
- color: colorScheme.ramps.neutral(0.86).hex(),
- weight: fontWeights.normal,
- },
- constant: {
- color: colorScheme.ramps.green(0.5).hex(),
- weight: fontWeights.normal,
- },
- keyword: {
- color: colorScheme.ramps.blue(0.5).hex(),
- weight: fontWeights.normal,
- },
- function: {
- color: colorScheme.ramps.yellow(0.5).hex(),
- weight: fontWeights.normal,
- },
- type: {
- color: colorScheme.ramps.cyan(0.5).hex(),
- weight: fontWeights.normal,
- },
- constructor: {
- color: colorScheme.ramps.blue(0.5).hex(),
- weight: fontWeights.normal,
- },
- variant: {
- color: colorScheme.ramps.blue(0.5).hex(),
- weight: fontWeights.normal,
- },
- property: {
- color: colorScheme.ramps.blue(0.5).hex(),
- weight: fontWeights.normal,
- },
- enum: {
- color: colorScheme.ramps.orange(0.5).hex(),
- weight: fontWeights.normal,
- },
- operator: {
- color: colorScheme.ramps.orange(0.5).hex(),
- weight: fontWeights.normal,
- },
- string: {
- color: colorScheme.ramps.orange(0.5).hex(),
- weight: fontWeights.normal,
- },
- number: {
- color: colorScheme.ramps.green(0.5).hex(),
- weight: fontWeights.normal,
- },
- boolean: {
- color: colorScheme.ramps.green(0.5).hex(),
- weight: fontWeights.normal,
- },
- predictive: {
- color: colorScheme.ramps.neutral(0.57).hex(),
- weight: fontWeights.normal,
- },
- title: {
- color: colorScheme.ramps.yellow(0.5).hex(),
- weight: fontWeights.bold,
- },
- emphasis: {
- color: colorScheme.ramps.blue(0.5).hex(),
- weight: fontWeights.normal,
- },
- "emphasis.strong": {
- color: colorScheme.ramps.blue(0.5).hex(),
- weight: fontWeights.bold,
- },
- linkUri: {
- color: colorScheme.ramps.green(0.5).hex(),
- weight: fontWeights.normal,
- underline: true,
- },
- linkText: {
- color: colorScheme.ramps.orange(0.5).hex(),
- weight: fontWeights.normal,
- italic: true,
- },
- };
+ return deepmerge<Syntax, Partial<ThemeSyntax>>(
+ defaultSyntax,
+ colorScheme.syntax,
+ {
+ arrayMerge: (destinationArray, sourceArray) => [
+ ...destinationArray,
+ ...sourceArray,
+ ],
+ }
+ )
+ }
- return {
- textColor: syntax.primary.color,
- background: background(layer),
- activeLineBackground: withOpacity(background(layer, "on"), 0.75),
- highlightedLineBackground: background(layer, "on"),
- codeActions: {
- indicator: foreground(layer, "variant"),
- verticalScale: 0.55,
- },
- diff: {
- deleted: foreground(layer, "negative"),
- modified: foreground(layer, "warning"),
- inserted: foreground(layer, "positive"),
- removedWidthEm: 0.275,
- widthEm: 0.16,
- cornerRadius: 0.05,
- },
- /** Highlights matching occurences of what is under the cursor
- * as well as matched brackets
- */
- documentHighlightReadBackground: withOpacity(foreground(layer, "accent"), 0.1),
- documentHighlightWriteBackground: colorScheme.ramps
- .neutral(0.5)
- .alpha(0.4)
- .hex(), // TODO: This was blend * 2
- errorColor: background(layer, "negative"),
- gutterBackground: background(layer),
- gutterPaddingFactor: 3.5,
- lineNumber: withOpacity(foreground(layer), 0.35),
- lineNumberActive: foreground(layer),
- renameFade: 0.6,
- unnecessaryCodeFade: 0.5,
- selection: colorScheme.players[0],
- guestSelections: [
- colorScheme.players[1],
- colorScheme.players[2],
- colorScheme.players[3],
- colorScheme.players[4],
- colorScheme.players[5],
- colorScheme.players[6],
- colorScheme.players[7],
- ],
- autocomplete: {
- background: background(colorScheme.middle),
- cornerRadius: 8,
- padding: 4,
- margin: {
- left: -14,
- },
- border: border(colorScheme.middle),
- shadow: colorScheme.popoverShadow,
- matchHighlight: foreground(colorScheme.middle, "accent"),
- item: autocompleteItem,
- hoveredItem: {
- ...autocompleteItem,
- matchHighlight: foreground(colorScheme.middle, "accent", "hovered"),
- background: background(colorScheme.middle, "hovered"),
- },
- selectedItem: {
- ...autocompleteItem,
- matchHighlight: foreground(colorScheme.middle, "accent", "active"),
- background: background(colorScheme.middle, "active"),
- },
- },
- diagnosticHeader: {
- background: background(colorScheme.middle),
- iconWidthFactor: 1.5,
- textScaleFactor: 0.857,
- border: border(colorScheme.middle, {
- bottom: true,
- top: true,
- }),
- code: {
- ...text(colorScheme.middle, "mono", { size: "sm" }),
- margin: {
- left: 10,
- },
- },
- message: {
- highlightText: text(colorScheme.middle, "sans", {
- size: "sm",
- weight: "bold",
- }),
- text: text(colorScheme.middle, "sans", { size: "sm" }),
- },
- },
- diagnosticPathHeader: {
- background: background(colorScheme.middle),
- textScaleFactor: 0.857,
- filename: text(colorScheme.middle, "mono", { size: "sm" }),
- path: {
- ...text(colorScheme.middle, "mono", { size: "sm" }),
- margin: {
- left: 12,
- },
- },
- },
- errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
- warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
- informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
- hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
- invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
- invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
- invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
- invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
- hoverPopover: hoverPopover(colorScheme),
- linkDefinition: {
- color: syntax.linkUri.color,
- underline: syntax.linkUri.underline,
- },
- jumpIcon: {
- color: foreground(layer, "on"),
- iconWidth: 20,
- buttonWidth: 20,
- cornerRadius: 6,
- padding: {
- top: 6,
- bottom: 6,
- left: 6,
- right: 6,
- },
- hover: {
- background: background(layer, "on", "hovered"),
- },
- },
- scrollbar: {
- width: 12,
- minHeightFactor: 1.0,
- track: {
- border: border(layer, "variant", { left: true }),
- },
- thumb: {
- background: withOpacity(background(layer, "inverted"), 0.4),
- border: {
- width: 1,
- color: borderColor(layer, "variant"),
- },
- },
- },
- compositionMark: {
- underline: {
- thickness: 1.0,
- color: borderColor(layer),
- },
- },
- syntax,
- };
+ const syntax = createSyntax(colorScheme)
+
+ return {
+ textColor: syntax.primary.color,
+ background: background(layer),
+ activeLineBackground: withOpacity(background(layer, "on"), 0.75),
+ highlightedLineBackground: background(layer, "on"),
+ codeActions: {
+ indicator: foreground(layer, "variant"),
+ verticalScale: 0.55,
+ },
+ diff: {
+ deleted: foreground(layer, "negative"),
+ modified: foreground(layer, "warning"),
+ inserted: foreground(layer, "positive"),
+ removedWidthEm: 0.275,
+ widthEm: 0.16,
+ cornerRadius: 0.05,
+ },
+ /** Highlights matching occurences of what is under the cursor
+ * as well as matched brackets
+ */
+ documentHighlightReadBackground: withOpacity(
+ foreground(layer, "accent"),
+ 0.1
+ ),
+ documentHighlightWriteBackground: colorScheme.ramps
+ .neutral(0.5)
+ .alpha(0.4)
+ .hex(), // TODO: This was blend * 2
+ errorColor: background(layer, "negative"),
+ gutterBackground: background(layer),
+ gutterPaddingFactor: 3.5,
+ lineNumber: withOpacity(foreground(layer), 0.35),
+ lineNumberActive: foreground(layer),
+ renameFade: 0.6,
+ unnecessaryCodeFade: 0.5,
+ selection: colorScheme.players[0],
+ guestSelections: [
+ colorScheme.players[1],
+ colorScheme.players[2],
+ colorScheme.players[3],
+ colorScheme.players[4],
+ colorScheme.players[5],
+ colorScheme.players[6],
+ colorScheme.players[7],
+ ],
+ autocomplete: {
+ background: background(colorScheme.middle),
+ cornerRadius: 8,
+ padding: 4,
+ margin: {
+ left: -14,
+ },
+ border: border(colorScheme.middle),
+ shadow: colorScheme.popoverShadow,
+ matchHighlight: foreground(colorScheme.middle, "accent"),
+ item: autocompleteItem,
+ hoveredItem: {
+ ...autocompleteItem,
+ matchHighlight: foreground(
+ colorScheme.middle,
+ "accent",
+ "hovered"
+ ),
+ background: background(colorScheme.middle, "hovered"),
+ },
+ selectedItem: {
+ ...autocompleteItem,
+ matchHighlight: foreground(
+ colorScheme.middle,
+ "accent",
+ "active"
+ ),
+ background: background(colorScheme.middle, "active"),
+ },
+ },
+ diagnosticHeader: {
+ background: background(colorScheme.middle),
+ iconWidthFactor: 1.5,
+ textScaleFactor: 0.857,
+ border: border(colorScheme.middle, {
+ bottom: true,
+ top: true,
+ }),
+ code: {
+ ...text(colorScheme.middle, "mono", { size: "sm" }),
+ margin: {
+ left: 10,
+ },
+ },
+ message: {
+ highlightText: text(colorScheme.middle, "sans", {
+ size: "sm",
+ weight: "bold",
+ }),
+ text: text(colorScheme.middle, "sans", { size: "sm" }),
+ },
+ },
+ diagnosticPathHeader: {
+ background: background(colorScheme.middle),
+ textScaleFactor: 0.857,
+ filename: text(colorScheme.middle, "mono", { size: "sm" }),
+ path: {
+ ...text(colorScheme.middle, "mono", { size: "sm" }),
+ margin: {
+ left: 12,
+ },
+ },
+ },
+ errorDiagnostic: diagnostic(colorScheme.middle, "negative"),
+ warningDiagnostic: diagnostic(colorScheme.middle, "warning"),
+ informationDiagnostic: diagnostic(colorScheme.middle, "accent"),
+ hintDiagnostic: diagnostic(colorScheme.middle, "warning"),
+ invalidErrorDiagnostic: diagnostic(colorScheme.middle, "base"),
+ invalidHintDiagnostic: diagnostic(colorScheme.middle, "base"),
+ invalidInformationDiagnostic: diagnostic(colorScheme.middle, "base"),
+ invalidWarningDiagnostic: diagnostic(colorScheme.middle, "base"),
+ hoverPopover: hoverPopover(colorScheme),
+ linkDefinition: {
+ color: syntax.linkUri.color,
+ underline: syntax.linkUri.underline,
+ },
+ jumpIcon: {
+ color: foreground(layer, "on"),
+ iconWidth: 20,
+ buttonWidth: 20,
+ cornerRadius: 6,
+ padding: {
+ top: 6,
+ bottom: 6,
+ left: 6,
+ right: 6,
+ },
+ hover: {
+ background: background(layer, "on", "hovered"),
+ },
+ },
+ scrollbar: {
+ width: 12,
+ minHeightFactor: 1.0,
+ track: {
+ border: border(layer, "variant", { left: true }),
+ },
+ thumb: {
+ background: withOpacity(background(layer, "inverted"), 0.4),
+ border: {
+ width: 1,
+ color: borderColor(layer, "variant"),
+ },
+ },
+ },
+ compositionMark: {
+ underline: {
+ thickness: 1.0,
+ color: borderColor(layer),
+ },
+ },
+ syntax,
+ }
}
@@ -1,39 +1,38 @@
-
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
export default function feedback(colorScheme: ColorScheme) {
- let layer = colorScheme.highest;
+ let layer = colorScheme.highest
- return {
- submit_button: {
- ...text(layer, "mono", "on"),
- background: background(layer, "on"),
- cornerRadius: 6,
- border: border(layer, "on"),
- margin: {
- right: 4,
- },
- padding: {
- bottom: 2,
- left: 10,
- right: 10,
- top: 2,
- },
- clicked: {
- ...text(layer, "mono", "on", "pressed"),
- background: background(layer, "on", "pressed"),
- border: border(layer, "on", "pressed"),
- },
- hover: {
- ...text(layer, "mono", "on", "hovered"),
- background: background(layer, "on", "hovered"),
- border: border(layer, "on", "hovered"),
- },
- },
- button_margin: 8,
- info_text_default: text(layer, "sans", "default", { size: "xs" }),
- link_text_default: text(layer, "sans", "default", { size: "xs", underline: true }),
- link_text_hover: text(layer, "sans", "hovered", { size: "xs", underline: true })
- };
+ return {
+ submit_button: {
+ ...text(layer, "mono", "on"),
+ background: background(layer, "on"),
+ cornerRadius: 6,
+ border: border(layer, "on"),
+ margin: {
+ right: 4,
+ },
+ padding: {
+ bottom: 2,
+ left: 10,
+ right: 10,
+ top: 2,
+ },
+ clicked: {
+ ...text(layer, "mono", "on", "pressed"),
+ background: background(layer, "on", "pressed"),
+ border: border(layer, "on", "pressed"),
+ },
+ hover: {
+ ...text(layer, "mono", "on", "hovered"),
+ background: background(layer, "on", "hovered"),
+ border: border(layer, "on", "hovered"),
+ },
+ },
+ button_margin: 8,
+ info_text_default: text(layer, "sans", "default", { size: "xs" }),
+ link_text_default: text(layer, "sans", "default", { size: "xs", underline: true }),
+ link_text_hover: text(layer, "sans", "hovered", { size: "xs", underline: true })
+ }
}
@@ -1,45 +1,45 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
export default function HoverPopover(colorScheme: ColorScheme) {
- let layer = colorScheme.middle;
- let baseContainer = {
- background: background(layer),
- cornerRadius: 8,
- padding: {
- left: 8,
- right: 8,
- top: 4,
- bottom: 4,
- },
- shadow: colorScheme.popoverShadow,
- border: border(layer),
- margin: {
- left: -8,
- },
- };
+ let layer = colorScheme.middle
+ let baseContainer = {
+ background: background(layer),
+ cornerRadius: 8,
+ padding: {
+ left: 8,
+ right: 8,
+ top: 4,
+ bottom: 4,
+ },
+ shadow: colorScheme.popoverShadow,
+ border: border(layer),
+ margin: {
+ left: -8,
+ },
+ }
- return {
- container: baseContainer,
- infoContainer: {
- ...baseContainer,
- background: background(layer, "accent"),
- border: border(layer, "accent"),
- },
- warningContainer: {
- ...baseContainer,
- background: background(layer, "warning"),
- border: border(layer, "warning"),
- },
- errorContainer: {
- ...baseContainer,
- background: background(layer, "negative"),
- border: border(layer, "negative"),
- },
- block_style: {
- padding: { top: 4 },
- },
- prose: text(layer, "sans", { size: "sm" }),
- highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better
- };
+ return {
+ container: baseContainer,
+ infoContainer: {
+ ...baseContainer,
+ background: background(layer, "accent"),
+ border: border(layer, "accent"),
+ },
+ warningContainer: {
+ ...baseContainer,
+ background: background(layer, "warning"),
+ border: border(layer, "warning"),
+ },
+ errorContainer: {
+ ...baseContainer,
+ background: background(layer, "negative"),
+ border: border(layer, "negative"),
+ },
+ block_style: {
+ padding: { top: 4 },
+ },
+ prose: text(layer, "sans", { size: "sm" }),
+ highlight: colorScheme.ramps.neutral(0.5).alpha(0.2).hex(), // TODO: blend was used here. Replace with something better
+ }
}
@@ -1,45 +1,53 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
-export default function incomingCallNotification(colorScheme: ColorScheme): Object {
- let layer = colorScheme.middle;
- const avatarSize = 48;
- return {
- windowHeight: 74,
- windowWidth: 380,
- background: background(layer),
- callerContainer: {
- padding: 12,
- },
- callerAvatar: {
- height: avatarSize,
- width: avatarSize,
- cornerRadius: avatarSize / 2,
- },
- callerMetadata: {
- margin: { left: 10 },
- },
- callerUsername: {
- ...text(layer, "sans", { size: "sm", weight: "bold" }),
- margin: { top: -3 },
- },
- callerMessage: {
- ...text(layer, "sans", "variant", { size: "xs" }),
- margin: { top: -3 },
- },
- worktreeRoots: {
- ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
- margin: { top: -3 },
- },
- buttonWidth: 96,
- acceptButton: {
- background: background(layer, "accent"),
- border: border(layer, { left: true, bottom: true }),
- ...text(layer, "sans", "positive", { size: "xs", weight: "extra_bold" })
- },
- declineButton: {
- border: border(layer, { left: true }),
- ...text(layer, "sans", "negative", { size: "xs", weight: "extra_bold" })
- },
- };
+export default function incomingCallNotification(
+ colorScheme: ColorScheme
+): Object {
+ let layer = colorScheme.middle
+ const avatarSize = 48
+ return {
+ windowHeight: 74,
+ windowWidth: 380,
+ background: background(layer),
+ callerContainer: {
+ padding: 12,
+ },
+ callerAvatar: {
+ height: avatarSize,
+ width: avatarSize,
+ cornerRadius: avatarSize / 2,
+ },
+ callerMetadata: {
+ margin: { left: 10 },
+ },
+ callerUsername: {
+ ...text(layer, "sans", { size: "sm", weight: "bold" }),
+ margin: { top: -3 },
+ },
+ callerMessage: {
+ ...text(layer, "sans", "variant", { size: "xs" }),
+ margin: { top: -3 },
+ },
+ worktreeRoots: {
+ ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
+ margin: { top: -3 },
+ },
+ buttonWidth: 96,
+ acceptButton: {
+ background: background(layer, "accent"),
+ border: border(layer, { left: true, bottom: true }),
+ ...text(layer, "sans", "positive", {
+ size: "xs",
+ weight: "extra_bold",
+ }),
+ },
+ declineButton: {
+ border: border(layer, { left: true }),
+ ...text(layer, "sans", "negative", {
+ size: "xs",
+ weight: "extra_bold",
+ }),
+ },
+ }
}
@@ -1,78 +1,78 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
export default function picker(colorScheme: ColorScheme) {
- let layer = colorScheme.lowest;
- const container = {
- background: background(layer),
- border: border(layer),
- shadow: colorScheme.modalShadow,
- cornerRadius: 12,
- padding: {
- bottom: 4,
+ let layer = colorScheme.lowest
+ const container = {
+ background: background(layer),
+ border: border(layer),
+ shadow: colorScheme.modalShadow,
+ cornerRadius: 12,
+ padding: {
+ bottom: 4,
+ },
}
- };
- const inputEditor = {
- placeholderText: text(layer, "sans", "on", "disabled"),
- selection: colorScheme.players[0],
- text: text(layer, "mono", "on"),
- border: border(layer, { bottom: true }),
- padding: {
- bottom: 8,
- left: 16,
- right: 16,
- top: 8,
- },
- margin: {
- bottom: 4,
- },
- };
- const emptyInputEditor = { ...inputEditor };
- delete emptyInputEditor.border;
- delete emptyInputEditor.margin;
+ const inputEditor = {
+ placeholderText: text(layer, "sans", "on", "disabled"),
+ selection: colorScheme.players[0],
+ text: text(layer, "mono", "on"),
+ border: border(layer, { bottom: true }),
+ padding: {
+ bottom: 8,
+ left: 16,
+ right: 16,
+ top: 8,
+ },
+ margin: {
+ bottom: 4,
+ },
+ }
+ const emptyInputEditor = { ...inputEditor }
+ delete emptyInputEditor.border
+ delete emptyInputEditor.margin
- return {
- ...container,
- emptyContainer: {
- ...container,
- padding: {}
- },
- item: {
- padding: {
- bottom: 4,
- left: 12,
- right: 12,
- top: 4,
- },
- margin: {
- top: 1,
- left: 4,
- right: 4,
- },
- cornerRadius: 8,
- text: text(layer, "sans", "variant"),
- highlightText: text(layer, "sans", "accent", { weight: "bold" }),
- active: {
- background: background(layer, "base", "active"),
- text: text(layer, "sans", "base", "active"),
- highlightText: text(layer, "sans", "accent", {
- weight: "bold",
- }),
- },
- hover: {
- background: background(layer, "hovered"),
- },
- },
- inputEditor,
- emptyInputEditor,
- noMatches: {
- text: text(layer, "sans", "variant"),
- padding: {
- bottom: 8,
- left: 16,
- right: 16,
- top: 8,
- },
- },
- };
+ return {
+ ...container,
+ emptyContainer: {
+ ...container,
+ padding: {},
+ },
+ item: {
+ padding: {
+ bottom: 4,
+ left: 12,
+ right: 12,
+ top: 4,
+ },
+ margin: {
+ top: 1,
+ left: 4,
+ right: 4,
+ },
+ cornerRadius: 8,
+ text: text(layer, "sans", "variant"),
+ highlightText: text(layer, "sans", "accent", { weight: "bold" }),
+ active: {
+ background: background(layer, "base", "active"),
+ text: text(layer, "sans", "base", "active"),
+ highlightText: text(layer, "sans", "accent", {
+ weight: "bold",
+ }),
+ },
+ hover: {
+ background: background(layer, "hovered"),
+ },
+ },
+ inputEditor,
+ emptyInputEditor,
+ noMatches: {
+ text: text(layer, "sans", "variant"),
+ padding: {
+ bottom: 8,
+ left: 16,
+ right: 16,
+ top: 8,
+ },
+ },
+ }
}
@@ -1,13 +1,13 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, text } from "./components"
export default function projectDiagnostics(colorScheme: ColorScheme) {
- let layer = colorScheme.highest;
- return {
- background: background(layer),
- tabIconSpacing: 4,
- tabIconWidth: 13,
- tabSummarySpacing: 10,
- emptyMessage: text(layer, "sans", "variant", { size: "md" }),
- };
+ let layer = colorScheme.highest
+ return {
+ background: background(layer),
+ tabIconSpacing: 4,
+ tabIconWidth: 13,
+ tabSummarySpacing: 10,
+ emptyMessage: text(layer, "sans", "variant", { size: "md" }),
+ }
}
@@ -1,60 +1,60 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { withOpacity } from "../utils/color";
-import { background, border, foreground, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { withOpacity } from "../utils/color"
+import { background, border, foreground, text } from "./components"
export default function projectPanel(colorScheme: ColorScheme) {
- let layer = colorScheme.middle;
-
- let baseEntry = {
- height: 24,
- iconColor: foreground(layer, "variant"),
- iconSize: 8,
- iconSpacing: 8,
- }
+ let layer = colorScheme.middle
- let entry = {
- ...baseEntry,
- text: text(layer, "mono", "variant", { size: "sm" }),
- hover: {
- background: background(layer, "variant", "hovered"),
- },
- active: {
- background: background(layer, "active"),
- text: text(layer, "mono", "active", { size: "sm" }),
- },
- activeHover: {
- background: background(layer, "active"),
- text: text(layer, "mono", "active", { size: "sm" }),
- },
- };
+ let baseEntry = {
+ height: 24,
+ iconColor: foreground(layer, "variant"),
+ iconSize: 8,
+ iconSpacing: 8,
+ }
- return {
- background: background(layer),
- padding: { left: 12, right: 12, top: 6, bottom: 6 },
- indentWidth: 8,
- entry,
- draggedEntry: {
- ...baseEntry,
- text: text(layer, "mono", "on", { size: "sm" }),
- background: withOpacity(background(layer, "on"), 0.9),
- border: border(layer),
- },
- ignoredEntry: {
- ...entry,
- text: text(layer, "mono", "disabled"),
- },
- cutEntry: {
- ...entry,
- text: text(layer, "mono", "disabled"),
- active: {
- background: background(layer, "active"),
- text: text(layer, "mono", "disabled", { size: "sm" }),
- },
- },
- filenameEditor: {
- background: background(layer, "on"),
- text: text(layer, "mono", "on", { size: "sm" }),
- selection: colorScheme.players[0],
- },
- };
+ let entry = {
+ ...baseEntry,
+ text: text(layer, "mono", "variant", { size: "sm" }),
+ hover: {
+ background: background(layer, "variant", "hovered"),
+ },
+ active: {
+ background: background(layer, "active"),
+ text: text(layer, "mono", "active", { size: "sm" }),
+ },
+ activeHover: {
+ background: background(layer, "active"),
+ text: text(layer, "mono", "active", { size: "sm" }),
+ },
+ }
+
+ return {
+ background: background(layer),
+ padding: { left: 12, right: 12, top: 6, bottom: 6 },
+ indentWidth: 8,
+ entry,
+ draggedEntry: {
+ ...baseEntry,
+ text: text(layer, "mono", "on", { size: "sm" }),
+ background: withOpacity(background(layer, "on"), 0.9),
+ border: border(layer),
+ },
+ ignoredEntry: {
+ ...entry,
+ text: text(layer, "mono", "disabled"),
+ },
+ cutEntry: {
+ ...entry,
+ text: text(layer, "mono", "disabled"),
+ active: {
+ background: background(layer, "active"),
+ text: text(layer, "mono", "disabled", { size: "sm" }),
+ },
+ },
+ filenameEditor: {
+ background: background(layer, "on"),
+ text: text(layer, "mono", "on", { size: "sm" }),
+ selection: colorScheme.players[0],
+ },
+ }
}
@@ -1,46 +1,54 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
-export default function projectSharedNotification(colorScheme: ColorScheme): Object {
- let layer = colorScheme.middle;
+export default function projectSharedNotification(
+ colorScheme: ColorScheme
+): Object {
+ let layer = colorScheme.middle
- const avatarSize = 48;
- return {
- windowHeight: 74,
- windowWidth: 380,
- background: background(layer),
- ownerContainer: {
- padding: 12,
- },
- ownerAvatar: {
- height: avatarSize,
- width: avatarSize,
- cornerRadius: avatarSize / 2,
- },
- ownerMetadata: {
- margin: { left: 10 },
- },
- ownerUsername: {
- ...text(layer, "sans", { size: "sm", weight: "bold" }),
- margin: { top: -3 },
- },
- message: {
- ...text(layer, "sans", "variant", { size: "xs" }),
- margin: { top: -3 },
- },
- worktreeRoots: {
- ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
- margin: { top: -3 },
- },
- buttonWidth: 96,
- openButton: {
- background: background(layer, "accent"),
- border: border(layer, { left: true, bottom: true, }),
- ...text(layer, "sans", "accent", { size: "xs", weight: "extra_bold" })
- },
- dismissButton: {
- border: border(layer, { left: true }),
- ...text(layer, "sans", "variant", { size: "xs", weight: "extra_bold" })
- },
- };
+ const avatarSize = 48
+ return {
+ windowHeight: 74,
+ windowWidth: 380,
+ background: background(layer),
+ ownerContainer: {
+ padding: 12,
+ },
+ ownerAvatar: {
+ height: avatarSize,
+ width: avatarSize,
+ cornerRadius: avatarSize / 2,
+ },
+ ownerMetadata: {
+ margin: { left: 10 },
+ },
+ ownerUsername: {
+ ...text(layer, "sans", { size: "sm", weight: "bold" }),
+ margin: { top: -3 },
+ },
+ message: {
+ ...text(layer, "sans", "variant", { size: "xs" }),
+ margin: { top: -3 },
+ },
+ worktreeRoots: {
+ ...text(layer, "sans", "variant", { size: "xs", weight: "bold" }),
+ margin: { top: -3 },
+ },
+ buttonWidth: 96,
+ openButton: {
+ background: background(layer, "accent"),
+ border: border(layer, { left: true, bottom: true }),
+ ...text(layer, "sans", "accent", {
+ size: "xs",
+ weight: "extra_bold",
+ }),
+ },
+ dismissButton: {
+ border: border(layer, { left: true }),
+ ...text(layer, "sans", "variant", {
+ size: "xs",
+ weight: "extra_bold",
+ }),
+ },
+ }
}
@@ -1,96 +1,94 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { withOpacity } from "../utils/color";
-import { background, border, foreground, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { withOpacity } from "../utils/color"
+import { background, border, foreground, text } from "./components"
export default function search(colorScheme: ColorScheme) {
- let layer = colorScheme.highest;
+ let layer = colorScheme.highest
- // Search input
- const editor = {
- background: background(layer),
- cornerRadius: 8,
- minWidth: 200,
- maxWidth: 500,
- placeholderText: text(layer, "mono", "disabled"),
- selection: colorScheme.players[0],
- text: text(layer, "mono", "default"),
- border: border(layer),
- margin: {
- right: 12,
- },
- padding: {
- top: 3,
- bottom: 3,
- left: 12,
- right: 8,
- },
- };
+ // Search input
+ const editor = {
+ background: background(layer),
+ cornerRadius: 8,
+ minWidth: 200,
+ maxWidth: 500,
+ placeholderText: text(layer, "mono", "disabled"),
+ selection: colorScheme.players[0],
+ text: text(layer, "mono", "default"),
+ border: border(layer),
+ margin: {
+ right: 12,
+ },
+ padding: {
+ top: 3,
+ bottom: 3,
+ left: 12,
+ right: 8,
+ },
+ }
- return {
- // TODO: Add an activeMatchBackground on the rust side to differenciate between active and inactive
- matchBackground: withOpacity(foreground(layer, "accent"), 0.4),
- tabIconSpacing: 8,
- tabIconWidth: 14,
- optionButton: {
- ...text(layer, "mono", "on"),
- background: background(layer, "on"),
- cornerRadius: 6,
- border: border(layer, "on"),
- margin: {
- right: 4,
- },
- padding: {
- bottom: 2,
- left: 10,
- right: 10,
- top: 2,
- },
- active: {
- ...text(layer, "mono", "on", "inverted"),
- background: background(layer, "on", "inverted"),
- border: border(layer, "on", "inverted"),
- },
- clicked: {
- ...text(layer, "mono", "on", "pressed"),
- background: background(layer, "on", "pressed"),
- border: border(layer, "on", "pressed"),
- },
- hover: {
- ...text(layer, "mono", "on", "hovered"),
- background: background(layer, "on", "hovered"),
- border: border(layer, "on", "hovered"),
- },
- },
- editor,
- invalidEditor: {
- ...editor,
- border: border(layer, "negative"),
- },
- matchIndex: {
- ...text(layer, "mono", "variant"),
- padding: 6,
- },
- optionButtonGroup: {
- padding: {
- left: 12,
- right: 12,
- },
- },
- resultsStatus: {
- ...text(layer, "mono", "on"),
- size: 18,
- },
- dismissButton: {
- color: foreground(layer, "variant"),
- iconWidth: 12,
- buttonWidth: 14,
- padding: {
- left: 10,
- right: 10,
- },
- hover: {
- color: foreground(layer, "hovered"),
- },
- },
- };
+ return {
+ // TODO: Add an activeMatchBackground on the rust side to differenciate between active and inactive
+ matchBackground: withOpacity(foreground(layer, "accent"), 0.4),
+ optionButton: {
+ ...text(layer, "mono", "on"),
+ background: background(layer, "on"),
+ cornerRadius: 6,
+ border: border(layer, "on"),
+ margin: {
+ right: 4,
+ },
+ padding: {
+ bottom: 2,
+ left: 10,
+ right: 10,
+ top: 2,
+ },
+ active: {
+ ...text(layer, "mono", "on", "inverted"),
+ background: background(layer, "on", "inverted"),
+ border: border(layer, "on", "inverted"),
+ },
+ clicked: {
+ ...text(layer, "mono", "on", "pressed"),
+ background: background(layer, "on", "pressed"),
+ border: border(layer, "on", "pressed"),
+ },
+ hover: {
+ ...text(layer, "mono", "on", "hovered"),
+ background: background(layer, "on", "hovered"),
+ border: border(layer, "on", "hovered"),
+ },
+ },
+ editor,
+ invalidEditor: {
+ ...editor,
+ border: border(layer, "negative"),
+ },
+ matchIndex: {
+ ...text(layer, "mono", "variant"),
+ padding: 6,
+ },
+ optionButtonGroup: {
+ padding: {
+ left: 12,
+ right: 12,
+ },
+ },
+ resultsStatus: {
+ ...text(layer, "mono", "on"),
+ size: 18,
+ },
+ dismissButton: {
+ color: foreground(layer, "variant"),
+ iconWidth: 12,
+ buttonWidth: 14,
+ padding: {
+ left: 10,
+ right: 10,
+ },
+ hover: {
+ color: foreground(layer, "hovered"),
+ },
+ },
+ }
}
@@ -1,9 +1,9 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background } from "./components"
export default function sharedScreen(colorScheme: ColorScheme) {
- let layer = colorScheme.highest;
- return {
- background: background(layer)
- }
+ let layer = colorScheme.highest
+ return {
+ background: background(layer),
+ }
}
@@ -1,31 +1,33 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { foreground, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { foreground, text } from "./components"
-const headerPadding = 8;
+const headerPadding = 8
-export default function simpleMessageNotification(colorScheme: ColorScheme): Object {
- let layer = colorScheme.middle;
- return {
- message: {
- ...text(layer, "sans", { size: "xs" }),
- margin: { left: headerPadding, right: headerPadding },
- },
- actionMessage: {
- ...text(layer, "sans", { size: "xs" }),
- margin: { left: headerPadding, top: 6, bottom: 6 },
- hover: {
- color: foreground(layer, "hovered"),
- },
- },
- dismissButton: {
- color: foreground(layer),
- iconWidth: 8,
- iconHeight: 8,
- buttonWidth: 8,
- buttonHeight: 8,
- hover: {
- color: foreground(layer, "hovered"),
- },
- },
- };
+export default function simpleMessageNotification(
+ colorScheme: ColorScheme
+): Object {
+ let layer = colorScheme.middle
+ return {
+ message: {
+ ...text(layer, "sans", { size: "xs" }),
+ margin: { left: headerPadding, right: headerPadding },
+ },
+ actionMessage: {
+ ...text(layer, "sans", { size: "xs" }),
+ margin: { left: headerPadding, top: 6, bottom: 6 },
+ hover: {
+ color: foreground(layer, "hovered"),
+ },
+ },
+ dismissButton: {
+ color: foreground(layer),
+ iconWidth: 8,
+ iconHeight: 8,
+ buttonWidth: 8,
+ buttonHeight: 8,
+ hover: {
+ color: foreground(layer, "hovered"),
+ },
+ },
+ }
}
@@ -1,118 +1,118 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, foreground, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, foreground, text } from "./components"
export default function statusBar(colorScheme: ColorScheme) {
- let layer = colorScheme.lowest;
+ let layer = colorScheme.lowest
- const statusContainer = {
- cornerRadius: 6,
- padding: { top: 3, bottom: 3, left: 6, right: 6 },
- };
-
- const diagnosticStatusContainer = {
- cornerRadius: 6,
- padding: { top: 1, bottom: 1, left: 6, right: 6 },
- };
+ const statusContainer = {
+ cornerRadius: 6,
+ padding: { top: 3, bottom: 3, left: 6, right: 6 },
+ }
- return {
- height: 30,
- itemSpacing: 8,
- padding: {
- top: 1,
- bottom: 1,
- left: 6,
- right: 6,
- },
- border: border(layer, { top: true, overlay: true }),
- cursorPosition: text(layer, "sans", "variant"),
- autoUpdateProgressMessage: text(layer, "sans", "variant"),
- autoUpdateDoneMessage: text(layer, "sans", "variant"),
- lspStatus: {
- ...diagnosticStatusContainer,
- iconSpacing: 4,
- iconWidth: 14,
- height: 18,
- message: text(layer, "sans"),
- iconColor: foreground(layer),
- hover: {
- message: text(layer, "sans"),
- iconColor: foreground(layer),
- background: background(layer),
- },
- },
- diagnosticMessage: {
- ...text(layer, "sans"),
- hover: text(layer, "sans", "hovered"),
- },
- feedback: {
- ...text(layer, "sans", "variant"),
- hover: text(layer, "sans", "hovered"),
- },
- diagnosticSummary: {
- height: 20,
- iconWidth: 16,
- iconSpacing: 2,
- summarySpacing: 6,
- text: text(layer, "sans", { size: "sm" }),
- iconColorOk: foreground(layer, "variant"),
- iconColorWarning: foreground(layer, "warning"),
- iconColorError: foreground(layer, "negative"),
- containerOk: {
+ const diagnosticStatusContainer = {
cornerRadius: 6,
- padding: { top: 3, bottom: 3, left: 7, right: 7 },
- },
- containerWarning: {
- ...diagnosticStatusContainer,
- background: background(layer, "warning"),
- border: border(layer, "warning"),
- },
- containerError: {
- ...diagnosticStatusContainer,
- background: background(layer, "negative"),
- border: border(layer, "negative"),
- },
- hover: {
- iconColorOk: foreground(layer, "on"),
- containerOk: {
- cornerRadius: 6,
- padding: { top: 3, bottom: 3, left: 7, right: 7 },
- background: background(layer, "on", "hovered"),
+ padding: { top: 1, bottom: 1, left: 6, right: 6 },
+ }
+
+ return {
+ height: 30,
+ itemSpacing: 8,
+ padding: {
+ top: 1,
+ bottom: 1,
+ left: 6,
+ right: 6,
+ },
+ border: border(layer, { top: true, overlay: true }),
+ cursorPosition: text(layer, "sans", "variant"),
+ autoUpdateProgressMessage: text(layer, "sans", "variant"),
+ autoUpdateDoneMessage: text(layer, "sans", "variant"),
+ lspStatus: {
+ ...diagnosticStatusContainer,
+ iconSpacing: 4,
+ iconWidth: 14,
+ height: 18,
+ message: text(layer, "sans"),
+ iconColor: foreground(layer),
+ hover: {
+ message: text(layer, "sans"),
+ iconColor: foreground(layer),
+ background: background(layer),
+ },
},
- containerWarning: {
- ...diagnosticStatusContainer,
- background: background(layer, "warning", "hovered"),
- border: border(layer, "warning", "hovered"),
+ diagnosticMessage: {
+ ...text(layer, "sans"),
+ hover: text(layer, "sans", "hovered"),
},
- containerError: {
- ...diagnosticStatusContainer,
- background: background(layer, "negative", "hovered"),
- border: border(layer, "negative", "hovered"),
+ feedback: {
+ ...text(layer, "sans", "variant"),
+ hover: text(layer, "sans", "hovered"),
},
- },
- },
- sidebarButtons: {
- groupLeft: {},
- groupRight: {},
- item: {
- ...statusContainer,
- iconSize: 16,
- iconColor: foreground(layer, "variant"),
- hover: {
- iconColor: foreground(layer, "hovered"),
- background: background(layer, "variant"),
+ diagnosticSummary: {
+ height: 20,
+ iconWidth: 16,
+ iconSpacing: 2,
+ summarySpacing: 6,
+ text: text(layer, "sans", { size: "sm" }),
+ iconColorOk: foreground(layer, "variant"),
+ iconColorWarning: foreground(layer, "warning"),
+ iconColorError: foreground(layer, "negative"),
+ containerOk: {
+ cornerRadius: 6,
+ padding: { top: 3, bottom: 3, left: 7, right: 7 },
+ },
+ containerWarning: {
+ ...diagnosticStatusContainer,
+ background: background(layer, "warning"),
+ border: border(layer, "warning"),
+ },
+ containerError: {
+ ...diagnosticStatusContainer,
+ background: background(layer, "negative"),
+ border: border(layer, "negative"),
+ },
+ hover: {
+ iconColorOk: foreground(layer, "on"),
+ containerOk: {
+ cornerRadius: 6,
+ padding: { top: 3, bottom: 3, left: 7, right: 7 },
+ background: background(layer, "on", "hovered"),
+ },
+ containerWarning: {
+ ...diagnosticStatusContainer,
+ background: background(layer, "warning", "hovered"),
+ border: border(layer, "warning", "hovered"),
+ },
+ containerError: {
+ ...diagnosticStatusContainer,
+ background: background(layer, "negative", "hovered"),
+ border: border(layer, "negative", "hovered"),
+ },
+ },
},
- active: {
- iconColor: foreground(layer, "active"),
- background: background(layer, "active"),
+ sidebarButtons: {
+ groupLeft: {},
+ groupRight: {},
+ item: {
+ ...statusContainer,
+ iconSize: 16,
+ iconColor: foreground(layer, "variant"),
+ hover: {
+ iconColor: foreground(layer, "hovered"),
+ background: background(layer, "variant"),
+ },
+ active: {
+ iconColor: foreground(layer, "active"),
+ background: background(layer, "active"),
+ },
+ },
+ badge: {
+ cornerRadius: 3,
+ padding: 2,
+ margin: { bottom: -1, right: -1 },
+ border: border(layer),
+ background: background(layer, "accent"),
+ },
},
- },
- badge: {
- cornerRadius: 3,
- padding: 2,
- margin: { bottom: -1, right: -1 },
- border: border(layer),
- background: background(layer, "accent"),
- },
- },
- };
+ }
}
@@ -1,103 +1,103 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { withOpacity } from "../utils/color";
-import { text, border, background, foreground } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { withOpacity } from "../utils/color"
+import { text, border, background, foreground } from "./components"
export default function tabBar(colorScheme: ColorScheme) {
- const height = 32;
+ const height = 32
- let activeLayer = colorScheme.highest;
- let layer = colorScheme.middle;
+ let activeLayer = colorScheme.highest
+ let layer = colorScheme.middle
- const tab = {
- height,
- text: text(layer, "sans", "variant", { size: "sm" }),
- background: background(layer),
- border: border(layer, {
- right: true,
- bottom: true,
- overlay: true,
- }),
- padding: {
- left: 8,
- right: 12,
- },
- spacing: 8,
+ const tab = {
+ height,
+ text: text(layer, "sans", "variant", { size: "sm" }),
+ background: background(layer),
+ border: border(layer, {
+ right: true,
+ bottom: true,
+ overlay: true,
+ }),
+ padding: {
+ left: 8,
+ right: 12,
+ },
+ spacing: 8,
- // Close icons
- iconWidth: 8,
- iconClose: foreground(layer, "variant"),
- iconCloseActive: foreground(layer, "hovered"),
+ // Close icons
+ iconWidth: 14,
+ iconClose: foreground(layer, "variant"),
+ iconCloseActive: foreground(layer, "hovered"),
- // Indicators
- iconConflict: foreground(layer, "warning"),
- iconDirty: foreground(layer, "accent"),
+ // Indicators
+ iconConflict: foreground(layer, "warning"),
+ iconDirty: foreground(layer, "accent"),
- // When two tabs of the same name are open, a label appears next to them
- description: {
- margin: { left: 8 },
- ...text(layer, "sans", "disabled", { size: "2xs" }),
- },
- };
+ // When two tabs of the same name are open, a label appears next to them
+ description: {
+ margin: { left: 8 },
+ ...text(layer, "sans", "disabled", { size: "2xs" }),
+ },
+ }
- const activePaneActiveTab = {
- ...tab,
- background: background(activeLayer),
- text: text(activeLayer, "sans", "active", { size: "sm" }),
- border: {
- ...tab.border,
- bottom: false,
- },
- };
+ const activePaneActiveTab = {
+ ...tab,
+ background: background(activeLayer),
+ text: text(activeLayer, "sans", "active", { size: "sm" }),
+ border: {
+ ...tab.border,
+ bottom: false,
+ },
+ }
- const inactivePaneInactiveTab = {
- ...tab,
- background: background(layer),
- text: text(layer, "sans", "variant", { size: "sm" }),
- };
+ const inactivePaneInactiveTab = {
+ ...tab,
+ background: background(layer),
+ text: text(layer, "sans", "variant", { size: "sm" }),
+ }
- const inactivePaneActiveTab = {
- ...tab,
- background: background(activeLayer),
- text: text(layer, "sans", "variant", { size: "sm" }),
- border: {
- ...tab.border,
- bottom: false,
- },
- };
+ const inactivePaneActiveTab = {
+ ...tab,
+ background: background(activeLayer),
+ text: text(layer, "sans", "variant", { size: "sm" }),
+ border: {
+ ...tab.border,
+ bottom: false,
+ },
+ }
- const draggedTab = {
- ...activePaneActiveTab,
- background: withOpacity(tab.background, 0.9),
- border: undefined as any,
- shadow: colorScheme.popoverShadow,
- };
+ const draggedTab = {
+ ...activePaneActiveTab,
+ background: withOpacity(tab.background, 0.9),
+ border: undefined as any,
+ shadow: colorScheme.popoverShadow,
+ }
- return {
- height,
- background: background(layer),
- activePane: {
- activeTab: activePaneActiveTab,
- inactiveTab: tab,
- },
- inactivePane: {
- activeTab: inactivePaneActiveTab,
- inactiveTab: inactivePaneInactiveTab,
- },
- draggedTab,
- paneButton: {
- color: foreground(layer, "variant"),
- iconWidth: 12,
- buttonWidth: activePaneActiveTab.height,
- hover: {
- color: foreground(layer, "hovered"),
- },
- },
- paneButtonContainer: {
- background: tab.background,
- border: {
- ...tab.border,
- right: false,
- },
- },
- };
+ return {
+ height,
+ background: background(layer),
+ activePane: {
+ activeTab: activePaneActiveTab,
+ inactiveTab: tab,
+ },
+ inactivePane: {
+ activeTab: inactivePaneActiveTab,
+ inactiveTab: inactivePaneInactiveTab,
+ },
+ draggedTab,
+ paneButton: {
+ color: foreground(layer, "variant"),
+ iconWidth: 12,
+ buttonWidth: activePaneActiveTab.height,
+ hover: {
+ color: foreground(layer, "hovered"),
+ },
+ },
+ paneButtonContainer: {
+ background: tab.background,
+ border: {
+ ...tab.border,
+ right: false,
+ },
+ },
+ }
}
@@ -1,52 +1,52 @@
-import { ColorScheme } from "../themes/common/colorScheme";
+import { ColorScheme } from "../themes/common/colorScheme"
export default function terminal(colorScheme: ColorScheme) {
- /**
- * Colors are controlled per-cell in the terminal grid.
- * Cells can be set to any of these more 'theme-capable' colors
- * or can be set directly with RGB values.
- * Here are the common interpretations of these names:
- * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
- */
- return {
- black: colorScheme.ramps.neutral(0).hex(),
- red: colorScheme.ramps.red(0.5).hex(),
- green: colorScheme.ramps.green(0.5).hex(),
- yellow: colorScheme.ramps.yellow(0.5).hex(),
- blue: colorScheme.ramps.blue(0.5).hex(),
- magenta: colorScheme.ramps.magenta(0.5).hex(),
- cyan: colorScheme.ramps.cyan(0.5).hex(),
- white: colorScheme.ramps.neutral(1).hex(),
- brightBlack: colorScheme.ramps.neutral(0.4).hex(),
- brightRed: colorScheme.ramps.red(0.25).hex(),
- brightGreen: colorScheme.ramps.green(0.25).hex(),
- brightYellow: colorScheme.ramps.yellow(0.25).hex(),
- brightBlue: colorScheme.ramps.blue(0.25).hex(),
- brightMagenta: colorScheme.ramps.magenta(0.25).hex(),
- brightCyan: colorScheme.ramps.cyan(0.25).hex(),
- brightWhite: colorScheme.ramps.neutral(1).hex(),
/**
- * Default color for characters
+ * Colors are controlled per-cell in the terminal grid.
+ * Cells can be set to any of these more 'theme-capable' colors
+ * or can be set directly with RGB values.
+ * Here are the common interpretations of these names:
+ * https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
*/
- foreground: colorScheme.ramps.neutral(1).hex(),
- /**
- * Default color for the rectangle background of a cell
- */
- background: colorScheme.ramps.neutral(0).hex(),
- modalBackground: colorScheme.ramps.neutral(0.1).hex(),
- /**
- * Default color for the cursor
- */
- cursor: colorScheme.players[0].cursor,
- dimBlack: colorScheme.ramps.neutral(1).hex(),
- dimRed: colorScheme.ramps.red(0.75).hex(),
- dimGreen: colorScheme.ramps.green(0.75).hex(),
- dimYellow: colorScheme.ramps.yellow(0.75).hex(),
- dimBlue: colorScheme.ramps.blue(0.75).hex(),
- dimMagenta: colorScheme.ramps.magenta(0.75).hex(),
- dimCyan: colorScheme.ramps.cyan(0.75).hex(),
- dimWhite: colorScheme.ramps.neutral(0.6).hex(),
- brightForeground: colorScheme.ramps.neutral(1).hex(),
- dimForeground: colorScheme.ramps.neutral(0).hex(),
- };
+ return {
+ black: colorScheme.ramps.neutral(0).hex(),
+ red: colorScheme.ramps.red(0.5).hex(),
+ green: colorScheme.ramps.green(0.5).hex(),
+ yellow: colorScheme.ramps.yellow(0.5).hex(),
+ blue: colorScheme.ramps.blue(0.5).hex(),
+ magenta: colorScheme.ramps.magenta(0.5).hex(),
+ cyan: colorScheme.ramps.cyan(0.5).hex(),
+ white: colorScheme.ramps.neutral(1).hex(),
+ brightBlack: colorScheme.ramps.neutral(0.4).hex(),
+ brightRed: colorScheme.ramps.red(0.25).hex(),
+ brightGreen: colorScheme.ramps.green(0.25).hex(),
+ brightYellow: colorScheme.ramps.yellow(0.25).hex(),
+ brightBlue: colorScheme.ramps.blue(0.25).hex(),
+ brightMagenta: colorScheme.ramps.magenta(0.25).hex(),
+ brightCyan: colorScheme.ramps.cyan(0.25).hex(),
+ brightWhite: colorScheme.ramps.neutral(1).hex(),
+ /**
+ * Default color for characters
+ */
+ foreground: colorScheme.ramps.neutral(1).hex(),
+ /**
+ * Default color for the rectangle background of a cell
+ */
+ background: colorScheme.ramps.neutral(0).hex(),
+ modalBackground: colorScheme.ramps.neutral(0.1).hex(),
+ /**
+ * Default color for the cursor
+ */
+ cursor: colorScheme.players[0].cursor,
+ dimBlack: colorScheme.ramps.neutral(1).hex(),
+ dimRed: colorScheme.ramps.red(0.75).hex(),
+ dimGreen: colorScheme.ramps.green(0.75).hex(),
+ dimYellow: colorScheme.ramps.yellow(0.75).hex(),
+ dimBlue: colorScheme.ramps.blue(0.75).hex(),
+ dimMagenta: colorScheme.ramps.magenta(0.75).hex(),
+ dimCyan: colorScheme.ramps.cyan(0.75).hex(),
+ dimWhite: colorScheme.ramps.neutral(0.6).hex(),
+ brightForeground: colorScheme.ramps.neutral(1).hex(),
+ dimForeground: colorScheme.ramps.neutral(0).hex(),
+ }
}
@@ -1,23 +1,23 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { background, border, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { background, border, text } from "./components"
export default function tooltip(colorScheme: ColorScheme) {
- let layer = colorScheme.middle;
- return {
- background: background(layer),
- border: border(layer),
- padding: { top: 4, bottom: 4, left: 8, right: 8 },
- margin: { top: 6, left: 6 },
- shadow: colorScheme.popoverShadow,
- cornerRadius: 6,
- text: text(layer, "sans", { size: "xs" }),
- keystroke: {
- background: background(layer, "on"),
- cornerRadius: 4,
- margin: { left: 6 },
- padding: { left: 4, right: 4 },
- ...text(layer, "mono", "on", { size: "xs", weight: "bold" }),
- },
- maxTextWidth: 200,
- };
+ let layer = colorScheme.middle
+ return {
+ background: background(layer),
+ border: border(layer),
+ padding: { top: 4, bottom: 4, left: 8, right: 8 },
+ margin: { top: 6, left: 6 },
+ shadow: colorScheme.popoverShadow,
+ cornerRadius: 6,
+ text: text(layer, "sans", { size: "xs" }),
+ keystroke: {
+ background: background(layer, "on"),
+ cornerRadius: 4,
+ margin: { left: 6 },
+ padding: { left: 4, right: 4 },
+ ...text(layer, "mono", "on", { size: "xs", weight: "bold" }),
+ },
+ maxTextWidth: 200,
+ }
}
@@ -1,31 +1,31 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { foreground, text } from "./components";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { foreground, text } from "./components"
-const headerPadding = 8;
+const headerPadding = 8
export default function updateNotification(colorScheme: ColorScheme): Object {
- let layer = colorScheme.middle;
- return {
- message: {
- ...text(layer, "sans", { size: "xs" }),
- margin: { left: headerPadding, right: headerPadding },
- },
- actionMessage: {
- ...text(layer, "sans", { size: "xs" }),
- margin: { left: headerPadding, top: 6, bottom: 6 },
- hover: {
- color: foreground(layer, "hovered"),
- },
- },
- dismissButton: {
- color: foreground(layer),
- iconWidth: 8,
- iconHeight: 8,
- buttonWidth: 8,
- buttonHeight: 8,
- hover: {
- color: foreground(layer, "hovered"),
- },
- },
- };
+ let layer = colorScheme.middle
+ return {
+ message: {
+ ...text(layer, "sans", { size: "xs" }),
+ margin: { left: headerPadding, right: headerPadding },
+ },
+ actionMessage: {
+ ...text(layer, "sans", { size: "xs" }),
+ margin: { left: headerPadding, top: 6, bottom: 6 },
+ hover: {
+ color: foreground(layer, "hovered"),
+ },
+ },
+ dismissButton: {
+ color: foreground(layer),
+ iconWidth: 8,
+ iconHeight: 8,
+ buttonWidth: 8,
+ buttonHeight: 8,
+ hover: {
+ color: foreground(layer, "hovered"),
+ },
+ },
+ }
}
@@ -1,235 +1,264 @@
-import { ColorScheme } from "../themes/common/colorScheme";
-import { withOpacity } from "../utils/color";
-import {
- background,
- border,
- borderColor,
- foreground,
- text,
-} from "./components";
-import statusBar from "./statusBar";
-import tabBar from "./tabBar";
+import { ColorScheme } from "../themes/common/colorScheme"
+import { withOpacity } from "../utils/color"
+import { background, border, borderColor, foreground, text } from "./components"
+import statusBar from "./statusBar"
+import tabBar from "./tabBar"
export default function workspace(colorScheme: ColorScheme) {
- const layer = colorScheme.lowest;
- const titlebarPadding = 6;
- const titlebarButton = {
- cornerRadius: 6,
- padding: {
- top: 1,
- bottom: 1,
- left: 8,
- right: 8,
- },
- ...text(layer, "sans", "variant", { size: "xs" }),
- background: background(layer, "variant"),
- border: border(layer),
- hover: {
- ...text(layer, "sans", "variant", "hovered", { size: "xs" }),
- background: background(layer, "variant", "hovered"),
- border: border(layer, "variant", "hovered"),
- },
- };
- const avatarWidth = 18;
+ const layer = colorScheme.lowest
+ const itemSpacing = 8
+ const titlebarButton = {
+ cornerRadius: 6,
+ padding: {
+ top: 1,
+ bottom: 1,
+ left: 8,
+ right: 8,
+ },
+ ...text(layer, "sans", "variant", { size: "xs" }),
+ background: background(layer, "variant"),
+ border: border(layer),
+ hover: {
+ ...text(layer, "sans", "variant", "hovered", { size: "xs" }),
+ background: background(layer, "variant", "hovered"),
+ border: border(layer, "variant", "hovered"),
+ },
+ clicked: {
+ ...text(layer, "sans", "variant", "pressed", { size: "xs" }),
+ background: background(layer, "variant", "pressed"),
+ border: border(layer, "variant", "pressed"),
+ },
+ active: {
+ ...text(layer, "sans", "variant", "active", { size: "xs" }),
+ background: background(layer, "variant", "active"),
+ border: border(layer, "variant", "active"),
+ },
+ }
+ const avatarWidth = 18
+ const avatarOuterWidth = avatarWidth + 4
+ const followerAvatarWidth = 14
+ const followerAvatarOuterWidth = followerAvatarWidth + 4
- return {
- background: background(layer),
- joiningProjectAvatar: {
- cornerRadius: 40,
- width: 80,
- },
- joiningProjectMessage: {
- padding: 12,
- ...text(layer, "sans", { size: "lg" }),
- },
- externalLocationMessage: {
- background: background(colorScheme.middle, "accent"),
- border: border(colorScheme.middle, "accent"),
- cornerRadius: 6,
- padding: 12,
- margin: { bottom: 8, right: 8 },
- ...text(colorScheme.middle, "sans", "accent", { size: "xs" }),
- },
- leaderBorderOpacity: 0.7,
- leaderBorderWidth: 2.0,
- tabBar: tabBar(colorScheme),
- modal: {
- margin: {
- bottom: 52,
- top: 52,
- },
- cursor: "Arrow",
- },
- sidebar: {
- initialSize: 240,
- border: border(layer, { left: true, right: true }),
- },
- paneDivider: {
- color: borderColor(layer),
- width: 1,
- },
- statusBar: statusBar(colorScheme),
- titlebar: {
- avatarWidth,
- avatarMargin: 8,
- height: 33, // 32px + 1px for overlaid border
- background: background(layer),
- border: border(layer, { bottom: true, overlay: true }),
- padding: {
- left: 80,
- right: titlebarPadding,
- },
+ return {
+ background: background(layer),
+ joiningProjectAvatar: {
+ cornerRadius: 40,
+ width: 80,
+ },
+ joiningProjectMessage: {
+ padding: 12,
+ ...text(layer, "sans", { size: "lg" }),
+ },
+ externalLocationMessage: {
+ background: background(colorScheme.middle, "accent"),
+ border: border(colorScheme.middle, "accent"),
+ cornerRadius: 6,
+ padding: 12,
+ margin: { bottom: 8, right: 8 },
+ ...text(colorScheme.middle, "sans", "accent", { size: "xs" }),
+ },
+ leaderBorderOpacity: 0.7,
+ leaderBorderWidth: 2.0,
+ tabBar: tabBar(colorScheme),
+ modal: {
+ margin: {
+ bottom: 52,
+ top: 52,
+ },
+ cursor: "Arrow",
+ },
+ sidebar: {
+ initialSize: 240,
+ border: border(layer, { left: true, right: true }),
+ },
+ paneDivider: {
+ color: borderColor(layer),
+ width: 1,
+ },
+ statusBar: statusBar(colorScheme),
+ titlebar: {
+ itemSpacing,
+ facePileSpacing: 2,
+ height: 33, // 32px + 1px for overlaid border
+ background: background(layer),
+ border: border(layer, { bottom: true, overlay: true }),
+ padding: {
+ left: 80,
+ right: itemSpacing,
+ },
- // Project
- title: text(layer, "sans", "variant"),
+ // Project
+ title: text(layer, "sans", "variant"),
- // Collaborators
- avatar: {
- cornerRadius: avatarWidth / 2,
- border: {
- color: "#00000088",
- width: 1,
- },
- },
- inactiveAvatar: {
- cornerRadius: avatarWidth / 2,
- border: {
- color: "#00000088",
- width: 1,
- },
- grayscale: true,
- },
- avatarRibbon: {
- height: 3,
- width: 12,
- // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
- },
+ // Collaborators
+ leaderAvatar: {
+ width: avatarWidth,
+ outerWidth: avatarOuterWidth,
+ cornerRadius: avatarWidth / 2,
+ outerCornerRadius: avatarOuterWidth / 2,
+ },
+ followerAvatar: {
+ width: followerAvatarWidth,
+ outerWidth: followerAvatarOuterWidth,
+ cornerRadius: followerAvatarWidth / 2,
+ outerCornerRadius: followerAvatarOuterWidth / 2,
+ },
+ inactiveAvatarGrayscale: true,
+ followerAvatarOverlap: 8,
+ leaderSelection: {
+ margin: {
+ top: 4,
+ bottom: 4,
+ },
+ padding: {
+ left: 2,
+ right: 2,
+ top: 4,
+ bottom: 4,
+ },
+ cornerRadius: 6,
+ },
+ avatarRibbon: {
+ height: 3,
+ width: 12,
+ // TODO: Chore: Make avatarRibbon colors driven by the theme rather than being hard coded.
+ },
- // Sign in buttom
- // FlatButton, Variant
- signInPrompt: {
- ...titlebarButton
- },
+ // Sign in buttom
+ // FlatButton, Variant
+ signInPrompt: {
+ ...titlebarButton,
+ },
- // Offline Indicator
- offlineIcon: {
- color: foreground(layer, "variant"),
- width: 16,
- margin: {
- left: titlebarPadding,
- },
- padding: {
- right: 4,
+ // Offline Indicator
+ offlineIcon: {
+ color: foreground(layer, "variant"),
+ width: 16,
+ margin: {
+ left: itemSpacing,
+ },
+ padding: {
+ right: 4,
+ },
+ },
+
+ // Notice that the collaboration server is out of date
+ outdatedWarning: {
+ ...text(layer, "sans", "warning", { size: "xs" }),
+ background: withOpacity(background(layer, "warning"), 0.3),
+ border: border(layer, "warning"),
+ margin: {
+ left: itemSpacing,
+ },
+ padding: {
+ left: 8,
+ right: 8,
+ },
+ cornerRadius: 6,
+ },
+ callControl: {
+ cornerRadius: 6,
+ color: foreground(layer, "variant"),
+ iconWidth: 12,
+ buttonWidth: 20,
+ hover: {
+ background: background(layer, "variant", "hovered"),
+ color: foreground(layer, "variant", "hovered"),
+ },
+ },
+ toggleContactsButton: {
+ margin: { left: itemSpacing },
+ cornerRadius: 6,
+ color: foreground(layer, "variant"),
+ iconWidth: 8,
+ buttonWidth: 20,
+ active: {
+ background: background(layer, "variant", "active"),
+ color: foreground(layer, "variant", "active"),
+ },
+ clicked: {
+ background: background(layer, "variant", "pressed"),
+ color: foreground(layer, "variant", "pressed"),
+ },
+ hover: {
+ background: background(layer, "variant", "hovered"),
+ color: foreground(layer, "variant", "hovered"),
+ },
+ },
+ userMenuButton: {
+ buttonWidth: 20,
+ iconWidth: 12,
+ ...titlebarButton,
+ },
+ toggleContactsBadge: {
+ cornerRadius: 3,
+ padding: 2,
+ margin: { top: 3, left: 3 },
+ border: border(layer),
+ background: foreground(layer, "accent"),
+ },
+ shareButton: {
+ ...titlebarButton,
+ },
},
- },
- // Notice that the collaboration server is out of date
- outdatedWarning: {
- ...text(layer, "sans", "warning", { size: "xs" }),
- background: withOpacity(background(layer, "warning"), 0.3),
- border: border(layer, "warning"),
- margin: {
- left: titlebarPadding,
+ toolbar: {
+ height: 34,
+ background: background(colorScheme.highest),
+ border: border(colorScheme.highest, { bottom: true }),
+ itemSpacing: 8,
+ navButton: {
+ color: foreground(colorScheme.highest, "on"),
+ iconWidth: 12,
+ buttonWidth: 24,
+ cornerRadius: 6,
+ hover: {
+ color: foreground(colorScheme.highest, "on", "hovered"),
+ background: background(
+ colorScheme.highest,
+ "on",
+ "hovered"
+ ),
+ },
+ disabled: {
+ color: foreground(colorScheme.highest, "on", "disabled"),
+ },
+ },
+ padding: { left: 8, right: 8, top: 4, bottom: 4 },
},
- padding: {
- left: 8,
- right: 8,
+ breadcrumbs: {
+ ...text(layer, "mono", "variant"),
+ padding: { left: 6 },
},
- cornerRadius: 6,
- },
- callControl: {
- cornerRadius: 6,
- color: foreground(layer, "variant"),
- iconWidth: 12,
- buttonWidth: 20,
- hover: {
- background: background(layer, "variant", "hovered"),
- color: foreground(layer, "variant", "hovered"),
+ disconnectedOverlay: {
+ ...text(layer, "sans"),
+ background: withOpacity(background(layer), 0.8),
},
- },
- toggleContactsButton: {
- margin: { left: 6 },
- cornerRadius: 6,
- color: foreground(layer, "variant"),
- iconWidth: 8,
- buttonWidth: 20,
- active: {
- background: background(layer, "variant", "active"),
- color: foreground(layer, "variant", "active"),
+ notification: {
+ margin: { top: 10 },
+ background: background(colorScheme.middle),
+ cornerRadius: 6,
+ padding: 12,
+ border: border(colorScheme.middle),
+ shadow: colorScheme.popoverShadow,
},
- hover: {
- background: background(layer, "variant", "hovered"),
- color: foreground(layer, "variant", "hovered"),
- },
- },
- toggleContactsBadge: {
- cornerRadius: 3,
- padding: 2,
- margin: { top: 3, left: 3 },
- border: border(layer),
- background: foreground(layer, "accent"),
- },
- shareButton: {
- ...titlebarButton
- }
- },
-
- toolbar: {
- height: 34,
- background: background(colorScheme.highest),
- border: border(colorScheme.highest, { bottom: true }),
- itemSpacing: 8,
- navButton: {
- color: foreground(colorScheme.highest, "on"),
- iconWidth: 12,
- buttonWidth: 24,
- cornerRadius: 6,
- hover: {
- color: foreground(colorScheme.highest, "on", "hovered"),
- background: background(colorScheme.highest, "on", "hovered"),
- },
- disabled: {
- color: foreground(colorScheme.highest, "on", "disabled"),
- },
- },
- padding: { left: 8, right: 8, top: 4, bottom: 4 },
- },
- breadcrumbs: {
- ...text(layer, "mono", "variant"),
- padding: { left: 6 },
- },
- disconnectedOverlay: {
- ...text(layer, "sans"),
- background: withOpacity(background(layer), 0.8),
- },
- notification: {
- margin: { top: 10 },
- background: background(colorScheme.middle),
- cornerRadius: 6,
- padding: 12,
- border: border(colorScheme.middle),
- shadow: colorScheme.popoverShadow,
- },
- notifications: {
- width: 400,
- margin: { right: 10, bottom: 10 },
- },
- dock: {
- initialSizeRight: 640,
- initialSizeBottom: 480,
- wash_color: withOpacity(background(colorScheme.highest), 0.5),
- panel: {
- border: border(colorScheme.middle),
- },
- maximized: {
- margin: 32,
- border: border(colorScheme.highest, { overlay: true }),
- shadow: colorScheme.modalShadow,
- },
- },
- dropTargetOverlayColor: withOpacity(
- foreground(layer, "variant"),
- 0.5
- ),
- };
+ notifications: {
+ width: 400,
+ margin: { right: 10, bottom: 10 },
+ },
+ dock: {
+ initialSizeRight: 640,
+ initialSizeBottom: 480,
+ wash_color: withOpacity(background(colorScheme.highest), 0.5),
+ panel: {
+ border: border(colorScheme.middle),
+ },
+ maximized: {
+ margin: 32,
+ border: border(colorScheme.highest, { overlay: true }),
+ shadow: colorScheme.modalShadow,
+ },
+ },
+ dropTargetOverlayColor: withOpacity(foreground(layer, "variant"), 0.5),
+ }
}
@@ -0,0 +1,11 @@
+/** Converts a percentage scale value (0-100) to normalized scale (0-1) value. */
+export function percentageToNormalized(value: number) {
+ const normalized = value / 100
+ return normalized
+}
+
+/** Converts a normalized scale (0-1) value to a percentage scale (0-100) value. */
+export function normalizedToPercetage(value: number) {
+ const percentage = value * 100
+ return percentage
+}
@@ -0,0 +1,26 @@
+import bezier from "bezier-easing"
+import { Curve } from "../ref/curves"
+
+/**
+ * Formats our Curve data structure into a bezier easing function.
+ * @param {Curve} curve - The curve to format.
+ * @param {Boolean} inverted - Whether or not to invert the curve.
+ * @returns {EasingFunction} The formatted easing function.
+ */
+export function curve(curve: Curve, inverted?: Boolean) {
+ if (inverted) {
+ return bezier(
+ curve.value[3],
+ curve.value[2],
+ curve.value[1],
+ curve.value[0]
+ )
+ }
+
+ return bezier(
+ curve.value[0],
+ curve.value[1],
+ curve.value[2],
+ curve.value[3]
+ )
+}
@@ -0,0 +1,159 @@
+import bezier from "bezier-easing"
+import chroma from "chroma-js"
+import { Color, ColorFamily, ColorFamilyConfig, ColorScale } from "../types"
+import { percentageToNormalized } from "./convert"
+import { curve } from "./curve"
+
+// Re-export interface in a more standard format
+export type EasingFunction = bezier.EasingFunction
+
+/**
+ * Generates a color, outputs it in multiple formats, and returns a variety of useful metadata.
+ *
+ * @param {EasingFunction} hueEasing - An easing function for the hue component of the color.
+ * @param {EasingFunction} saturationEasing - An easing function for the saturation component of the color.
+ * @param {EasingFunction} lightnessEasing - An easing function for the lightness component of the color.
+ * @param {ColorFamilyConfig} family - Configuration for the color family.
+ * @param {number} step - The current step.
+ * @param {number} steps - The total number of steps in the color scale.
+ *
+ * @returns {Color} The generated color, with its calculated contrast against black and white, as well as its LCH values, RGBA array, hexadecimal representation, and a flag indicating if it is light or dark.
+ */
+function generateColor(
+ hueEasing: EasingFunction,
+ saturationEasing: EasingFunction,
+ lightnessEasing: EasingFunction,
+ family: ColorFamilyConfig,
+ step: number,
+ steps: number
+) {
+ const { hue, saturation, lightness } = family.color
+
+ const stepHue = hueEasing(step / steps) * (hue.end - hue.start) + hue.start
+ const stepSaturation =
+ saturationEasing(step / steps) * (saturation.end - saturation.start) +
+ saturation.start
+ const stepLightness =
+ lightnessEasing(step / steps) * (lightness.end - lightness.start) +
+ lightness.start
+
+ const color = chroma.hsl(
+ stepHue,
+ percentageToNormalized(stepSaturation),
+ percentageToNormalized(stepLightness)
+ )
+
+ const contrast = {
+ black: {
+ value: chroma.contrast(color, "black"),
+ aaPass: chroma.contrast(color, "black") >= 4.5,
+ aaaPass: chroma.contrast(color, "black") >= 7,
+ },
+ white: {
+ value: chroma.contrast(color, "white"),
+ aaPass: chroma.contrast(color, "white") >= 4.5,
+ aaaPass: chroma.contrast(color, "white") >= 7,
+ },
+ }
+
+ const lch = color.lch()
+ const rgba = color.rgba()
+ const hex = color.hex()
+
+ // 55 is a magic number. It's the lightness value at which we consider a color to be "light".
+ // It was picked by eye with some testing. We might want to use a more scientific approach in the future.
+ const isLight = lch[0] > 55
+
+ const result: Color = {
+ step,
+ lch,
+ hex,
+ rgba,
+ contrast,
+ isLight,
+ }
+
+ return result
+}
+
+/**
+ * Generates a color scale based on a color family configuration.
+ *
+ * @param {ColorFamilyConfig} config - The configuration for the color family.
+ * @param {Boolean} inverted - Specifies whether the color scale should be inverted or not.
+ *
+ * @returns {ColorScale} The generated color scale.
+ *
+ * @example
+ * ```ts
+ * const colorScale = generateColorScale({
+ * name: "blue",
+ * color: {
+ * hue: {
+ * start: 210,
+ * end: 240,
+ * curve: "easeInOut"
+ * },
+ * saturation: {
+ * start: 100,
+ * end: 100,
+ * curve: "easeInOut"
+ * },
+ * lightness: {
+ * start: 50,
+ * end: 50,
+ * curve: "easeInOut"
+ * }
+ * }
+ * });
+ * ```
+ */
+
+export function generateColorScale(
+ config: ColorFamilyConfig,
+ inverted: Boolean = false
+) {
+ const { hue, saturation, lightness } = config.color
+
+ // 101 steps means we get values from 0-100
+ const NUM_STEPS = 101
+
+ const hueEasing = curve(hue.curve, inverted)
+ const saturationEasing = curve(saturation.curve, inverted)
+ const lightnessEasing = curve(lightness.curve, inverted)
+
+ let scale: ColorScale = {
+ colors: [],
+ values: [],
+ }
+
+ for (let i = 0; i < NUM_STEPS; i++) {
+ const color = generateColor(
+ hueEasing,
+ saturationEasing,
+ lightnessEasing,
+ config,
+ i,
+ NUM_STEPS
+ )
+
+ scale.colors.push(color)
+ scale.values.push(color.hex)
+ }
+
+ return scale
+}
+
+/** Generates a color family with a scale and an inverted scale. */
+export function generateColorFamily(config: ColorFamilyConfig) {
+ const scale = generateColorScale(config, false)
+ const invertedScale = generateColorScale(config, true)
+
+ const family: ColorFamily = {
+ name: config.name,
+ scale,
+ invertedScale,
+ }
+
+ return family
+}
@@ -0,0 +1,445 @@
+import { generateColorFamily } from "../lib/generate"
+import { curve } from "./curves"
+
+// These are the source colors for the color scales in the system.
+// These should never directly be used directly in components or themes as they generate thousands of lines of code.
+// Instead, use the outputs from the reference palette which exports a smaller subset of colors.
+
+// Token or user-facing colors should use short, clear names and a 100-900 scale to match the font weight scale.
+
+// Light Gray ======================================== //
+
+export const lightgray = generateColorFamily({
+ name: "lightgray",
+ color: {
+ hue: {
+ start: 210,
+ end: 210,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 10,
+ end: 15,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 50,
+ curve: curve.linear,
+ },
+ },
+})
+
+// Light Dark ======================================== //
+
+export const darkgray = generateColorFamily({
+ name: "darkgray",
+ color: {
+ hue: {
+ start: 210,
+ end: 210,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 15,
+ end: 20,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 55,
+ end: 8,
+ curve: curve.linear,
+ },
+ },
+})
+
+// Red ======================================== //
+
+export const red = generateColorFamily({
+ name: "red",
+ color: {
+ hue: {
+ start: 0,
+ end: 0,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 95,
+ end: 75,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 25,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Sunset ======================================== //
+
+export const sunset = generateColorFamily({
+ name: "sunset",
+ color: {
+ hue: {
+ start: 15,
+ end: 15,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 100,
+ end: 90,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 25,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Orange ======================================== //
+
+export const orange = generateColorFamily({
+ name: "orange",
+ color: {
+ hue: {
+ start: 25,
+ end: 25,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 100,
+ end: 95,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 20,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Amber ======================================== //
+
+export const amber = generateColorFamily({
+ name: "amber",
+ color: {
+ hue: {
+ start: 38,
+ end: 38,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 100,
+ end: 100,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 18,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Yellow ======================================== //
+
+export const yellow = generateColorFamily({
+ name: "yellow",
+ color: {
+ hue: {
+ start: 48,
+ end: 48,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 90,
+ end: 100,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 15,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Lemon ======================================== //
+
+export const lemon = generateColorFamily({
+ name: "lemon",
+ color: {
+ hue: {
+ start: 55,
+ end: 55,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 85,
+ end: 95,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 15,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Citron ======================================== //
+
+export const citron = generateColorFamily({
+ name: "citron",
+ color: {
+ hue: {
+ start: 70,
+ end: 70,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 85,
+ end: 90,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 15,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Lime ======================================== //
+
+export const lime = generateColorFamily({
+ name: "lime",
+ color: {
+ hue: {
+ start: 85,
+ end: 85,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 85,
+ end: 80,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 18,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Green ======================================== //
+
+export const green = generateColorFamily({
+ name: "green",
+ color: {
+ hue: {
+ start: 108,
+ end: 108,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 60,
+ end: 70,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 18,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Mint ======================================== //
+
+export const mint = generateColorFamily({
+ name: "mint",
+ color: {
+ hue: {
+ start: 142,
+ end: 142,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 60,
+ end: 75,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 20,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Cyan ======================================== //
+
+export const cyan = generateColorFamily({
+ name: "cyan",
+ color: {
+ hue: {
+ start: 179,
+ end: 179,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 70,
+ end: 80,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 20,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Sky ======================================== //
+
+export const sky = generateColorFamily({
+ name: "sky",
+ color: {
+ hue: {
+ start: 195,
+ end: 205,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 85,
+ end: 90,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 15,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Blue ======================================== //
+
+export const blue = generateColorFamily({
+ name: "blue",
+ color: {
+ hue: {
+ start: 218,
+ end: 218,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 85,
+ end: 70,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 15,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Indigo ======================================== //
+
+export const indigo = generateColorFamily({
+ name: "indigo",
+ color: {
+ hue: {
+ start: 245,
+ end: 245,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 60,
+ end: 50,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 22,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Purple ======================================== //
+
+export const purple = generateColorFamily({
+ name: "purple",
+ color: {
+ hue: {
+ start: 260,
+ end: 270,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 65,
+ end: 55,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 20,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Pink ======================================== //
+
+export const pink = generateColorFamily({
+ name: "pink",
+ color: {
+ hue: {
+ start: 320,
+ end: 330,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 70,
+ end: 65,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 32,
+ curve: curve.lightness,
+ },
+ },
+})
+
+// Rose ======================================== //
+
+export const rose = generateColorFamily({
+ name: "rose",
+ color: {
+ hue: {
+ start: 345,
+ end: 345,
+ curve: curve.linear,
+ },
+ saturation: {
+ start: 90,
+ end: 70,
+ curve: curve.saturation,
+ },
+ lightness: {
+ start: 97,
+ end: 32,
+ curve: curve.lightness,
+ },
+ },
+})
@@ -0,0 +1,25 @@
+export interface Curve {
+ name: string
+ value: number[]
+}
+
+export interface Curves {
+ lightness: Curve
+ saturation: Curve
+ linear: Curve
+}
+
+export const curve: Curves = {
+ lightness: {
+ name: "lightnessCurve",
+ value: [0.2, 0, 0.75, 1.0],
+ },
+ saturation: {
+ name: "saturationCurve",
+ value: [0.67, 0.6, 0.55, 1.0],
+ },
+ linear: {
+ name: "linear",
+ value: [0.5, 0.5, 0.5, 0.5],
+ },
+}
@@ -0,0 +1,32 @@
+import chroma from "chroma-js"
+import * as colorFamily from "./ref/color"
+
+const color = {
+ lightgray: chroma
+ .scale(colorFamily.lightgray.scale.values)
+ .mode("lch")
+ .colors(9),
+ darkgray: chroma
+ .scale(colorFamily.darkgray.scale.values)
+ .mode("lch")
+ .colors(9),
+ red: chroma.scale(colorFamily.red.scale.values).mode("lch").colors(9),
+ sunset: chroma.scale(colorFamily.sunset.scale.values).mode("lch").colors(9),
+ orange: chroma.scale(colorFamily.orange.scale.values).mode("lch").colors(9),
+ amber: chroma.scale(colorFamily.amber.scale.values).mode("lch").colors(9),
+ yellow: chroma.scale(colorFamily.yellow.scale.values).mode("lch").colors(9),
+ lemon: chroma.scale(colorFamily.lemon.scale.values).mode("lch").colors(9),
+ citron: chroma.scale(colorFamily.citron.scale.values).mode("lch").colors(9),
+ lime: chroma.scale(colorFamily.lime.scale.values).mode("lch").colors(9),
+ green: chroma.scale(colorFamily.green.scale.values).mode("lch").colors(9),
+ mint: chroma.scale(colorFamily.mint.scale.values).mode("lch").colors(9),
+ cyan: chroma.scale(colorFamily.cyan.scale.values).mode("lch").colors(9),
+ sky: chroma.scale(colorFamily.sky.scale.values).mode("lch").colors(9),
+ blue: chroma.scale(colorFamily.blue.scale.values).mode("lch").colors(9),
+ indigo: chroma.scale(colorFamily.indigo.scale.values).mode("lch").colors(9),
+ purple: chroma.scale(colorFamily.purple.scale.values).mode("lch").colors(9),
+ pink: chroma.scale(colorFamily.pink.scale.values).mode("lch").colors(9),
+ rose: chroma.scale(colorFamily.rose.scale.values).mode("lch").colors(9),
+}
+
+export { color }
@@ -0,0 +1,66 @@
+import { Curve } from "./ref/curves"
+
+export interface ColorAccessiblityValue {
+ value: number
+ aaPass: boolean
+ aaaPass: boolean
+}
+
+/**
+ * Calculates the color contrast between a specified color and its corresponding background and foreground colors.
+ *
+ * @note This implementation is currently basic β Currently we only calculate contrasts against black and white, in the future will allow for dynamic color contrast calculation based on the colors present in a given palette.
+ * @note The goal is to align with WCAG3 accessibility standards as they become stabilized. See the [WCAG 3 Introduction](https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/) for more information.
+ */
+export interface ColorAccessiblity {
+ black: ColorAccessiblityValue
+ white: ColorAccessiblityValue
+}
+
+export type Color = {
+ step: number
+ contrast: ColorAccessiblity
+ hex: string
+ lch: number[]
+ rgba: number[]
+ isLight: boolean
+}
+
+export interface ColorScale {
+ colors: Color[]
+ // An array of hex values for each color in the scale
+ values: string[]
+}
+
+export type ColorFamily = {
+ name: string
+ scale: ColorScale
+ invertedScale: ColorScale
+}
+
+export interface ColorFamilyHue {
+ start: number
+ end: number
+ curve: Curve
+}
+
+export interface ColorFamilySaturation {
+ start: number
+ end: number
+ curve: Curve
+}
+
+export interface ColorFamilyLightness {
+ start: number
+ end: number
+ curve: Curve
+}
+
+export interface ColorFamilyConfig {
+ name: string
+ color: {
+ hue: ColorFamilyHue
+ saturation: ColorFamilySaturation
+ lightness: ColorFamilyLightness
+ }
+}
@@ -1,41 +1,43 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "Andromeda";
+const name = "Andromeda"
const ramps = {
- neutral: chroma
- .scale([
- "#1E2025",
- "#23262E",
- "#292E38",
- "#2E323C",
- "#ACA8AE",
- "#CBC9CF",
- "#E1DDE4",
- "#F7F7F8",
- ])
- .domain([0, 0.15, 0.25, 0.35, 0.7, 0.8, 0.9, 1]),
- red: colorRamp(chroma("#F92672")),
- orange: colorRamp(chroma("#F39C12")),
- yellow: colorRamp(chroma("#FFE66D")),
- green: colorRamp(chroma("#96E072")),
- cyan: colorRamp(chroma("#00E8C6")),
- blue: colorRamp(chroma("#0CA793")),
- violet: colorRamp(chroma("#8A3FA6")),
- magenta: colorRamp(chroma("#C74DED")),
-};
+ neutral: chroma
+ .scale([
+ "#1E2025",
+ "#23262E",
+ "#292E38",
+ "#2E323C",
+ "#ACA8AE",
+ "#CBC9CF",
+ "#E1DDE4",
+ "#F7F7F8",
+ ])
+ .domain([0, 0.15, 0.25, 0.35, 0.7, 0.8, 0.9, 1]),
+ red: colorRamp(chroma("#F92672")),
+ orange: colorRamp(chroma("#F39C12")),
+ yellow: colorRamp(chroma("#FFE66D")),
+ green: colorRamp(chroma("#96E072")),
+ cyan: colorRamp(chroma("#00E8C6")),
+ blue: colorRamp(chroma("#0CA793")),
+ violet: colorRamp(chroma("#8A3FA6")),
+ magenta: colorRamp(chroma("#C74DED")),
+}
-export const dark = createColorScheme(`${name}`, false, ramps);
+export const dark = createColorScheme(`${name}`, false, ramps)
export const meta: Meta = {
- name,
- author: "EliverLara",
- license: {
- SPDX: "MIT",
- https_url: "https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md",
- license_checksum: "2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89"
- },
- url: "https://github.com/EliverLara/Andromeda"
-}
+ name,
+ author: "EliverLara",
+ license: {
+ SPDX: "MIT",
+ https_url:
+ "https://raw.githubusercontent.com/EliverLara/Andromeda/master/LICENSE.md",
+ license_checksum:
+ "2f7886f1a05cefc2c26f5e49de1a39fa4466413c1ccb06fc80960e73f5ed4b89",
+ },
+ url: "https://github.com/EliverLara/Andromeda",
+}
@@ -1,63 +1,63 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "Atelier Cave";
+const name = "Atelier Cave"
export const dark = createColorScheme(`${name} Dark`, false, {
- neutral: chroma
- .scale([
- "#19171c",
- "#26232a",
- "#585260",
- "#655f6d",
- "#7e7887",
- "#8b8792",
- "#e2dfe7",
- "#efecf4",
- ])
- .domain([0, 0.15, 0.45, 0.6, 0.65, 0.7, 0.85, 1]),
- red: colorRamp(chroma("#be4678")),
- orange: colorRamp(chroma("#aa573c")),
- yellow: colorRamp(chroma("#a06e3b")),
- green: colorRamp(chroma("#2a9292")),
- cyan: colorRamp(chroma("#398bc6")),
- blue: colorRamp(chroma("#576ddb")),
- violet: colorRamp(chroma("#955ae7")),
- magenta: colorRamp(chroma("#bf40bf")),
-});
+ neutral: chroma
+ .scale([
+ "#19171c",
+ "#26232a",
+ "#585260",
+ "#655f6d",
+ "#7e7887",
+ "#8b8792",
+ "#e2dfe7",
+ "#efecf4",
+ ])
+ .domain([0, 0.15, 0.45, 0.6, 0.65, 0.7, 0.85, 1]),
+ red: colorRamp(chroma("#be4678")),
+ orange: colorRamp(chroma("#aa573c")),
+ yellow: colorRamp(chroma("#a06e3b")),
+ green: colorRamp(chroma("#2a9292")),
+ cyan: colorRamp(chroma("#398bc6")),
+ blue: colorRamp(chroma("#576ddb")),
+ violet: colorRamp(chroma("#955ae7")),
+ magenta: colorRamp(chroma("#bf40bf")),
+})
export const light = createColorScheme(`${name} Light`, true, {
- neutral: chroma
- .scale([
- "#19171c",
- "#26232a",
- "#585260",
- "#655f6d",
- "#7e7887",
- "#8b8792",
- "#e2dfe7",
- "#efecf4",
- ])
- .correctLightness(),
- red: colorRamp(chroma("#be4678")),
- orange: colorRamp(chroma("#aa573c")),
- yellow: colorRamp(chroma("#a06e3b")),
- green: colorRamp(chroma("#2a9292")),
- cyan: colorRamp(chroma("#398bc6")),
- blue: colorRamp(chroma("#576ddb")),
- violet: colorRamp(chroma("#955ae7")),
- magenta: colorRamp(chroma("#bf40bf")),
-});
-
+ neutral: chroma
+ .scale([
+ "#19171c",
+ "#26232a",
+ "#585260",
+ "#655f6d",
+ "#7e7887",
+ "#8b8792",
+ "#e2dfe7",
+ "#efecf4",
+ ])
+ .correctLightness(),
+ red: colorRamp(chroma("#be4678")),
+ orange: colorRamp(chroma("#aa573c")),
+ yellow: colorRamp(chroma("#a06e3b")),
+ green: colorRamp(chroma("#2a9292")),
+ cyan: colorRamp(chroma("#398bc6")),
+ blue: colorRamp(chroma("#576ddb")),
+ violet: colorRamp(chroma("#955ae7")),
+ magenta: colorRamp(chroma("#bf40bf")),
+})
export const meta: Meta = {
- name,
- author: "atelierbram",
- license: {
- SPDX: "MIT",
- https_url: "https://atelierbram.mit-license.org/license.txt",
- license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5"
- },
- url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/"
-}
+ name,
+ author: "atelierbram",
+ license: {
+ SPDX: "MIT",
+ https_url: "https://atelierbram.mit-license.org/license.txt",
+ license_checksum:
+ "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5",
+ },
+ url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/cave/",
+}
@@ -1,42 +1,43 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "Atelier Sulphurpool";
+const name = "Atelier Sulphurpool"
const ramps = {
- neutral: chroma
- .scale([
- "#202746",
- "#293256",
- "#5e6687",
- "#6b7394",
- "#898ea4",
- "#979db4",
- "#dfe2f1",
- "#f5f7ff",
- ])
- .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
- red: colorRamp(chroma("#c94922")),
- orange: colorRamp(chroma("#c76b29")),
- yellow: colorRamp(chroma("#c08b30")),
- green: colorRamp(chroma("#ac9739")),
- cyan: colorRamp(chroma("#22a2c9")),
- blue: colorRamp(chroma("#3d8fd1")),
- violet: colorRamp(chroma("#6679cc")),
- magenta: colorRamp(chroma("#9c637a")),
-};
+ neutral: chroma
+ .scale([
+ "#202746",
+ "#293256",
+ "#5e6687",
+ "#6b7394",
+ "#898ea4",
+ "#979db4",
+ "#dfe2f1",
+ "#f5f7ff",
+ ])
+ .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
+ red: colorRamp(chroma("#c94922")),
+ orange: colorRamp(chroma("#c76b29")),
+ yellow: colorRamp(chroma("#c08b30")),
+ green: colorRamp(chroma("#ac9739")),
+ cyan: colorRamp(chroma("#22a2c9")),
+ blue: colorRamp(chroma("#3d8fd1")),
+ violet: colorRamp(chroma("#6679cc")),
+ magenta: colorRamp(chroma("#9c637a")),
+}
-export const dark = createColorScheme(`${name} Dark`, false, ramps);
-export const light = createColorScheme(`${name} Light`, true, ramps);
+export const dark = createColorScheme(`${name} Dark`, false, ramps)
+export const light = createColorScheme(`${name} Light`, true, ramps)
export const meta: Meta = {
- name,
- author: "atelierbram",
- license: {
- SPDX: "MIT",
- https_url: "https://atelierbram.mit-license.org/license.txt",
- license_checksum: "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5"
- },
- url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool/"
-}
+ name,
+ author: "atelierbram",
+ license: {
+ SPDX: "MIT",
+ https_url: "https://atelierbram.mit-license.org/license.txt",
+ license_checksum:
+ "f95ce526ef4e7eecf7a832bba0e3451cc1000f9ce63eb01ed6f64f8109f5d0a5",
+ },
+ url: "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/sulphurpool/",
+}
@@ -1,296 +0,0 @@
-// NOTE β This should be removed
-// I (Nate) need to come back and check if we are still using this anywhere
-
-import chroma, { Color, Scale } from "chroma-js";
-import { fontWeights } from "../../common";
-import { withOpacity } from "../../utils/color";
-import Theme, { buildPlayer, Syntax } from "./theme";
-
-export function colorRamp(color: Color): Scale {
- let hue = color.hsl()[0];
- let endColor = chroma.hsl(hue, 0.88, 0.96);
- let startColor = chroma.hsl(hue, 0.68, 0.12);
- return chroma.scale([startColor, color, endColor]).mode("hsl");
-}
-
-export function createTheme(
- name: string,
- isLight: boolean,
- color_ramps: { [rampName: string]: Scale }
-): Theme {
- let ramps: typeof color_ramps = {};
- // Chromajs mutates the underlying ramp when you call domain. This causes problems because
- // we now store the ramps object in the theme so that we can pull colors out of them.
- // So instead of calling domain and storing the result, we have to construct new ramps for each
- // theme so that we don't modify the passed in ramps.
- // This combined with an error in the type definitions for chroma js means we have to cast the colors
- // function to any in order to get the colors back out from the original ramps.
- if (isLight) {
- for (var rampName in color_ramps) {
- ramps[rampName] = chroma
- .scale((color_ramps[rampName].colors as any)())
- .domain([1, 0]);
- }
- ramps.neutral = chroma
- .scale((color_ramps.neutral.colors as any)())
- .domain([7, 0]);
- } else {
- for (var rampName in color_ramps) {
- ramps[rampName] = chroma
- .scale((color_ramps[rampName].colors as any)())
- .domain([0, 1]);
- }
- ramps.neutral = chroma
- .scale((color_ramps.neutral.colors as any)())
- .domain([0, 7]);
- }
-
- let blend = isLight ? 0.12 : 0.24;
-
- function sample(ramp: Scale, index: number): string {
- return ramp(index).hex();
- }
- const darkest = ramps.neutral(isLight ? 7 : 0).hex();
-
- const backgroundColor = {
- // Title bar
- 100: {
- base: sample(ramps.neutral, 1.25),
- hovered: sample(ramps.neutral, 1.5),
- active: sample(ramps.neutral, 1.75),
- },
- // Midground (panels, etc)
- 300: {
- base: sample(ramps.neutral, 1),
- hovered: sample(ramps.neutral, 1.25),
- active: sample(ramps.neutral, 1.5),
- },
- // Editor
- 500: {
- base: sample(ramps.neutral, 0),
- hovered: sample(ramps.neutral, 0.25),
- active: sample(ramps.neutral, 0.5),
- },
- on300: {
- base: sample(ramps.neutral, 0),
- hovered: sample(ramps.neutral, 0.5),
- active: sample(ramps.neutral, 1),
- },
- on500: {
- base: sample(ramps.neutral, 1),
- hovered: sample(ramps.neutral, 1.5),
- active: sample(ramps.neutral, 2),
- },
- ok: {
- base: withOpacity(sample(ramps.green, 0.5), 0.15),
- hovered: withOpacity(sample(ramps.green, 0.5), 0.2),
- active: withOpacity(sample(ramps.green, 0.5), 0.25),
- },
- error: {
- base: withOpacity(sample(ramps.red, 0.5), 0.15),
- hovered: withOpacity(sample(ramps.red, 0.5), 0.2),
- active: withOpacity(sample(ramps.red, 0.5), 0.25),
- },
- on500Error: {
- base: sample(ramps.red, 0.05),
- hovered: sample(ramps.red, 0.1),
- active: sample(ramps.red, 0.15),
- },
- warning: {
- base: withOpacity(sample(ramps.yellow, 0.5), 0.15),
- hovered: withOpacity(sample(ramps.yellow, 0.5), 0.2),
- active: withOpacity(sample(ramps.yellow, 0.5), 0.25),
- },
- on500Warning: {
- base: sample(ramps.yellow, 0.05),
- hovered: sample(ramps.yellow, 0.1),
- active: sample(ramps.yellow, 0.15),
- },
- info: {
- base: withOpacity(sample(ramps.blue, 0.5), 0.15),
- hovered: withOpacity(sample(ramps.blue, 0.5), 0.2),
- active: withOpacity(sample(ramps.blue, 0.5), 0.25),
- },
- on500Info: {
- base: sample(ramps.blue, 0.05),
- hovered: sample(ramps.blue, 0.1),
- active: sample(ramps.blue, 0.15),
- },
- on500Ok: {
- base: sample(ramps.green, 0.05),
- hovered: sample(ramps.green, 0.1),
- active: sample(ramps.green, 0.15),
- },
- };
-
- const borderColor = {
- primary: sample(ramps.neutral, isLight ? 1.5 : 0),
- secondary: sample(ramps.neutral, isLight ? 1.25 : 1),
- muted: sample(ramps.neutral, isLight ? 1.25 : 3),
- active: sample(ramps.neutral, isLight ? 4 : 3),
- onMedia: withOpacity(darkest, 0.1),
- ok: sample(ramps.green, 0.3),
- error: sample(ramps.red, 0.3),
- warning: sample(ramps.yellow, 0.3),
- info: sample(ramps.blue, 0.3),
- };
-
- const textColor = {
- primary: sample(ramps.neutral, 6),
- secondary: sample(ramps.neutral, 5),
- muted: sample(ramps.neutral, 4),
- placeholder: sample(ramps.neutral, 3),
- active: sample(ramps.neutral, 7),
- feature: sample(ramps.blue, 0.5),
- ok: sample(ramps.green, 0.5),
- error: sample(ramps.red, 0.5),
- warning: sample(ramps.yellow, 0.5),
- info: sample(ramps.blue, 0.5),
- onMedia: darkest,
- };
-
- const player = {
- 1: buildPlayer(sample(ramps.blue, 0.5)),
- 2: buildPlayer(sample(ramps.green, 0.5)),
- 3: buildPlayer(sample(ramps.magenta, 0.5)),
- 4: buildPlayer(sample(ramps.orange, 0.5)),
- 5: buildPlayer(sample(ramps.violet, 0.5)),
- 6: buildPlayer(sample(ramps.cyan, 0.5)),
- 7: buildPlayer(sample(ramps.red, 0.5)),
- 8: buildPlayer(sample(ramps.yellow, 0.5)),
- };
-
- const editor = {
- background: backgroundColor[500].base,
- indent_guide: borderColor.muted,
- indent_guide_active: borderColor.secondary,
- line: {
- active: sample(ramps.neutral, 1),
- highlighted: sample(ramps.neutral, 1.25), // TODO: Where is this used?
- },
- highlight: {
- selection: player[1].selectionColor,
- occurrence: withOpacity(sample(ramps.neutral, 3.5), blend),
- activeOccurrence: withOpacity(sample(ramps.neutral, 3.5), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
- matchingBracket: backgroundColor[500].active, // TODO: Not hooked up
- match: sample(ramps.violet, 0.15),
- activeMatch: withOpacity(sample(ramps.violet, 0.4), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
- related: backgroundColor[500].hovered,
- },
- gutter: {
- primary: textColor.placeholder,
- active: textColor.active,
- },
- };
-
- const syntax: Syntax = {
- primary: {
- color: sample(ramps.neutral, 7),
- weight: fontWeights.normal,
- },
- "variable.special": {
- color: sample(ramps.blue, 0.8),
- weight: fontWeights.normal,
- },
- comment: {
- color: sample(ramps.neutral, 5),
- weight: fontWeights.normal,
- },
- punctuation: {
- color: sample(ramps.neutral, 6),
- weight: fontWeights.normal,
- },
- constant: {
- color: sample(ramps.neutral, 4),
- weight: fontWeights.normal,
- },
- keyword: {
- color: sample(ramps.blue, 0.5),
- weight: fontWeights.normal,
- },
- function: {
- color: sample(ramps.yellow, 0.5),
- weight: fontWeights.normal,
- },
- type: {
- color: sample(ramps.cyan, 0.5),
- weight: fontWeights.normal,
- },
- constructor: {
- color: sample(ramps.cyan, 0.5),
- weight: fontWeights.normal,
- },
- property: {
- color: sample(ramps.blue, 0.6),
- weight: fontWeights.normal,
- },
- enum: {
- color: sample(ramps.orange, 0.5),
- weight: fontWeights.normal,
- },
- operator: {
- color: sample(ramps.orange, 0.5),
- weight: fontWeights.normal,
- },
- string: {
- color: sample(ramps.orange, 0.5),
- weight: fontWeights.normal,
- },
- number: {
- color: sample(ramps.green, 0.5),
- weight: fontWeights.normal,
- },
- boolean: {
- color: sample(ramps.green, 0.5),
- weight: fontWeights.normal,
- },
- predictive: {
- color: textColor.muted,
- weight: fontWeights.normal,
- },
- title: {
- color: sample(ramps.yellow, 0.5),
- weight: fontWeights.bold,
- },
- emphasis: {
- color: textColor.feature,
- weight: fontWeights.normal,
- },
- "emphasis.strong": {
- color: textColor.feature,
- weight: fontWeights.bold,
- },
- linkUri: {
- color: sample(ramps.green, 0.5),
- weight: fontWeights.normal,
- underline: true,
- },
- linkText: {
- color: sample(ramps.orange, 0.5),
- weight: fontWeights.normal,
- italic: true,
- },
- };
-
- const shadow = withOpacity(
- ramps
- .neutral(isLight ? 7 : 0)
- .darken()
- .hex(),
- blend
- );
-
- return {
- name,
- isLight,
- backgroundColor,
- borderColor,
- textColor,
- iconColor: textColor,
- editor,
- syntax,
- player,
- shadow,
- ramps,
- };
-}
@@ -1,100 +1,138 @@
-import { Scale } from "chroma-js";
+import { Scale } from "chroma-js"
+import { FontWeight } from "../../common"
export interface ColorScheme {
- name: string;
- isLight: boolean;
+ name: string
+ isLight: boolean
- lowest: Layer;
- middle: Layer;
- highest: Layer;
+ lowest: Layer
+ middle: Layer
+ highest: Layer
- ramps: RampSet;
+ ramps: RampSet
- popoverShadow: Shadow;
- modalShadow: Shadow;
+ popoverShadow: Shadow
+ modalShadow: Shadow
- players: Players;
+ players: Players
+ syntax?: Partial<ThemeSyntax>
}
export interface Meta {
- name: string,
- author: string,
- url: string,
- license: License
+ name: string
+ author: string
+ url: string
+ license: License
}
export interface License {
- SPDX: SPDXExpression,
- /// A url where we can download the license's text
- https_url: string,
- license_checksum: string
+ SPDX: SPDXExpression
+ /// A url where we can download the license's text
+ https_url: string
+ license_checksum: string
}
// License name -> License text
export interface Licenses {
- [key: string]: string
+ [key: string]: string
}
// FIXME: Add support for the SPDX expression syntax
-export type SPDXExpression = "MIT";
+export type SPDXExpression = "MIT"
export interface Player {
- cursor: string;
- selection: string;
+ cursor: string
+ selection: string
}
export interface Players {
- "0": Player;
- "1": Player;
- "2": Player;
- "3": Player;
- "4": Player;
- "5": Player;
- "6": Player;
- "7": Player;
+ "0": Player
+ "1": Player
+ "2": Player
+ "3": Player
+ "4": Player
+ "5": Player
+ "6": Player
+ "7": Player
}
export interface Shadow {
- blur: number;
- color: string;
- offset: number[];
+ blur: number
+ color: string
+ offset: number[]
}
-export type StyleSets = keyof Layer;
+export type StyleSets = keyof Layer
export interface Layer {
- base: StyleSet;
- variant: StyleSet;
- on: StyleSet;
- accent: StyleSet;
- positive: StyleSet;
- warning: StyleSet;
- negative: StyleSet;
+ base: StyleSet
+ variant: StyleSet
+ on: StyleSet
+ accent: StyleSet
+ positive: StyleSet
+ warning: StyleSet
+ negative: StyleSet
}
export interface RampSet {
- neutral: Scale;
- red: Scale;
- orange: Scale;
- yellow: Scale;
- green: Scale;
- cyan: Scale;
- blue: Scale;
- violet: Scale;
- magenta: Scale;
+ neutral: Scale
+ red: Scale
+ orange: Scale
+ yellow: Scale
+ green: Scale
+ cyan: Scale
+ blue: Scale
+ violet: Scale
+ magenta: Scale
}
-export type Styles = keyof StyleSet;
+export type Styles = keyof StyleSet
export interface StyleSet {
- default: Style;
- active: Style;
- disabled: Style;
- hovered: Style;
- pressed: Style;
- inverted: Style;
+ default: Style
+ active: Style
+ disabled: Style
+ hovered: Style
+ pressed: Style
+ inverted: Style
}
export interface Style {
- background: string;
- border: string;
- foreground: string;
+ background: string
+ border: string
+ foreground: string
}
+
+export interface SyntaxHighlightStyle {
+ color: string
+ weight?: FontWeight
+ underline?: boolean
+ italic?: boolean
+}
+
+export interface Syntax {
+ primary: SyntaxHighlightStyle
+ "variable.special": SyntaxHighlightStyle
+ comment: SyntaxHighlightStyle
+ punctuation: SyntaxHighlightStyle
+ constant: SyntaxHighlightStyle
+ keyword: SyntaxHighlightStyle
+ function: SyntaxHighlightStyle
+ type: SyntaxHighlightStyle
+ constructor: SyntaxHighlightStyle
+ variant: SyntaxHighlightStyle
+ property: SyntaxHighlightStyle
+ enum: SyntaxHighlightStyle
+ operator: SyntaxHighlightStyle
+ string: SyntaxHighlightStyle
+ number: SyntaxHighlightStyle
+ boolean: SyntaxHighlightStyle
+ predictive: SyntaxHighlightStyle
+ title: SyntaxHighlightStyle
+ emphasis: SyntaxHighlightStyle
+ "emphasis.strong": SyntaxHighlightStyle
+ linkUri: SyntaxHighlightStyle
+ linkText: SyntaxHighlightStyle
+}
+
+// HACK: "constructor" as a key in the syntax interface returns an error when a theme tries to use it.
+// For now hack around it by omiting constructor as a valid key for overrides.
+export type ThemeSyntax = Partial<Omit<Syntax, "constructor">>
@@ -1,210 +1,215 @@
-import chroma, { Color, Scale } from "chroma-js";
+import chroma, { Color, Scale } from "chroma-js"
import {
- ColorScheme,
- Layer,
- Player,
- RampSet,
- Style,
- Styles,
- StyleSet,
-} from "./colorScheme";
+ ColorScheme,
+ Layer,
+ Player,
+ RampSet,
+ Style,
+ Styles,
+ StyleSet,
+ ThemeSyntax,
+} from "./colorScheme"
export function colorRamp(color: Color): Scale {
- let endColor = color.desaturate(1).brighten(5);
- let startColor = color.desaturate(1).darken(4);
- return chroma.scale([startColor, color, endColor]).mode("lab");
+ let endColor = color.desaturate(1).brighten(5)
+ let startColor = color.desaturate(1).darken(4)
+ return chroma.scale([startColor, color, endColor]).mode("lab")
}
export function createColorScheme(
- name: string,
- isLight: boolean,
- colorRamps: { [rampName: string]: Scale }
+ name: string,
+ isLight: boolean,
+ colorRamps: { [rampName: string]: Scale },
+ syntax?: ThemeSyntax
): ColorScheme {
- // Chromajs scales from 0 to 1 flipped if isLight is true
- let ramps: RampSet = {} as any;
-
- // Chromajs mutates the underlying ramp when you call domain. This causes problems because
- // we now store the ramps object in the theme so that we can pull colors out of them.
- // So instead of calling domain and storing the result, we have to construct new ramps for each
- // theme so that we don't modify the passed in ramps.
- // This combined with an error in the type definitions for chroma js means we have to cast the colors
- // function to any in order to get the colors back out from the original ramps.
- if (isLight) {
- for (var rampName in colorRamps) {
- (ramps as any)[rampName] = chroma.scale(
- colorRamps[rampName].colors(100).reverse()
- );
+ // Chromajs scales from 0 to 1 flipped if isLight is true
+ let ramps: RampSet = {} as any
+
+ // Chromajs mutates the underlying ramp when you call domain. This causes problems because
+ // we now store the ramps object in the theme so that we can pull colors out of them.
+ // So instead of calling domain and storing the result, we have to construct new ramps for each
+ // theme so that we don't modify the passed in ramps.
+ // This combined with an error in the type definitions for chroma js means we have to cast the colors
+ // function to any in order to get the colors back out from the original ramps.
+ if (isLight) {
+ for (var rampName in colorRamps) {
+ ;(ramps as any)[rampName] = chroma.scale(
+ colorRamps[rampName].colors(100).reverse()
+ )
+ }
+ ramps.neutral = chroma.scale(colorRamps.neutral.colors(100).reverse())
+ } else {
+ for (var rampName in colorRamps) {
+ ;(ramps as any)[rampName] = chroma.scale(
+ colorRamps[rampName].colors(100)
+ )
+ }
+ ramps.neutral = chroma.scale(colorRamps.neutral.colors(100))
+ }
+
+ let lowest = lowestLayer(ramps)
+ let middle = middleLayer(ramps)
+ let highest = highestLayer(ramps)
+
+ let popoverShadow = {
+ blur: 4,
+ color: ramps
+ .neutral(isLight ? 7 : 0)
+ .darken()
+ .alpha(0.2)
+ .hex(), // TODO used blend previously. Replace with something else
+ offset: [1, 2],
+ }
+
+ let modalShadow = {
+ blur: 16,
+ color: ramps
+ .neutral(isLight ? 7 : 0)
+ .darken()
+ .alpha(0.2)
+ .hex(), // TODO used blend previously. Replace with something else
+ offset: [0, 2],
+ }
+
+ let players = {
+ "0": player(ramps.blue),
+ "1": player(ramps.green),
+ "2": player(ramps.magenta),
+ "3": player(ramps.orange),
+ "4": player(ramps.violet),
+ "5": player(ramps.cyan),
+ "6": player(ramps.red),
+ "7": player(ramps.yellow),
}
- ramps.neutral = chroma.scale(colorRamps.neutral.colors(100).reverse());
- } else {
- for (var rampName in colorRamps) {
- (ramps as any)[rampName] = chroma.scale(colorRamps[rampName].colors(100));
+
+ return {
+ name,
+ isLight,
+
+ ramps,
+
+ lowest,
+ middle,
+ highest,
+
+ popoverShadow,
+ modalShadow,
+
+ players,
+ syntax,
}
- ramps.neutral = chroma.scale(colorRamps.neutral.colors(100));
- }
-
- let lowest = lowestLayer(ramps);
- let middle = middleLayer(ramps);
- let highest = highestLayer(ramps);
-
- let popoverShadow = {
- blur: 4,
- color: ramps
- .neutral(isLight ? 7 : 0)
- .darken()
- .alpha(0.2)
- .hex(), // TODO used blend previously. Replace with something else
- offset: [1, 2],
- };
-
- let modalShadow = {
- blur: 16,
- color: ramps
- .neutral(isLight ? 7 : 0)
- .darken()
- .alpha(0.2)
- .hex(), // TODO used blend previously. Replace with something else
- offset: [0, 2],
- };
-
- let players = {
- "0": player(ramps.blue),
- "1": player(ramps.green),
- "2": player(ramps.magenta),
- "3": player(ramps.orange),
- "4": player(ramps.violet),
- "5": player(ramps.cyan),
- "6": player(ramps.red),
- "7": player(ramps.yellow),
- };
-
- return {
- name,
- isLight,
-
- ramps,
-
- lowest,
- middle,
- highest,
-
- popoverShadow,
- modalShadow,
-
- players,
- };
}
function player(ramp: Scale): Player {
- return {
- selection: ramp(0.5).alpha(0.24).hex(),
- cursor: ramp(0.5).hex(),
- };
+ return {
+ selection: ramp(0.5).alpha(0.24).hex(),
+ cursor: ramp(0.5).hex(),
+ }
}
function lowestLayer(ramps: RampSet): Layer {
- return {
- base: buildStyleSet(ramps.neutral, 0.2, 1),
- variant: buildStyleSet(ramps.neutral, 0.2, 0.7),
- on: buildStyleSet(ramps.neutral, 0.1, 1),
- accent: buildStyleSet(ramps.blue, 0.1, 0.5),
- positive: buildStyleSet(ramps.green, 0.1, 0.5),
- warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
- negative: buildStyleSet(ramps.red, 0.1, 0.5),
- };
+ return {
+ base: buildStyleSet(ramps.neutral, 0.2, 1),
+ variant: buildStyleSet(ramps.neutral, 0.2, 0.7),
+ on: buildStyleSet(ramps.neutral, 0.1, 1),
+ accent: buildStyleSet(ramps.blue, 0.1, 0.5),
+ positive: buildStyleSet(ramps.green, 0.1, 0.5),
+ warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
+ negative: buildStyleSet(ramps.red, 0.1, 0.5),
+ }
}
function middleLayer(ramps: RampSet): Layer {
- return {
- base: buildStyleSet(ramps.neutral, 0.1, 1),
- variant: buildStyleSet(ramps.neutral, 0.1, 0.7),
- on: buildStyleSet(ramps.neutral, 0, 1),
- accent: buildStyleSet(ramps.blue, 0.1, 0.5),
- positive: buildStyleSet(ramps.green, 0.1, 0.5),
- warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
- negative: buildStyleSet(ramps.red, 0.1, 0.5),
- };
+ return {
+ base: buildStyleSet(ramps.neutral, 0.1, 1),
+ variant: buildStyleSet(ramps.neutral, 0.1, 0.7),
+ on: buildStyleSet(ramps.neutral, 0, 1),
+ accent: buildStyleSet(ramps.blue, 0.1, 0.5),
+ positive: buildStyleSet(ramps.green, 0.1, 0.5),
+ warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
+ negative: buildStyleSet(ramps.red, 0.1, 0.5),
+ }
}
function highestLayer(ramps: RampSet): Layer {
- return {
- base: buildStyleSet(ramps.neutral, 0, 1),
- variant: buildStyleSet(ramps.neutral, 0, 0.7),
- on: buildStyleSet(ramps.neutral, 0.1, 1),
- accent: buildStyleSet(ramps.blue, 0.1, 0.5),
- positive: buildStyleSet(ramps.green, 0.1, 0.5),
- warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
- negative: buildStyleSet(ramps.red, 0.1, 0.5),
- };
+ return {
+ base: buildStyleSet(ramps.neutral, 0, 1),
+ variant: buildStyleSet(ramps.neutral, 0, 0.7),
+ on: buildStyleSet(ramps.neutral, 0.1, 1),
+ accent: buildStyleSet(ramps.blue, 0.1, 0.5),
+ positive: buildStyleSet(ramps.green, 0.1, 0.5),
+ warning: buildStyleSet(ramps.yellow, 0.1, 0.5),
+ negative: buildStyleSet(ramps.red, 0.1, 0.5),
+ }
}
function buildStyleSet(
- ramp: Scale,
- backgroundBase: number,
- foregroundBase: number,
- step: number = 0.08
+ ramp: Scale,
+ backgroundBase: number,
+ foregroundBase: number,
+ step: number = 0.08
): StyleSet {
- let styleDefinitions = buildStyleDefinition(
- backgroundBase,
- foregroundBase,
- step
- );
-
- function colorString(indexOrColor: number | Color): string {
- if (typeof indexOrColor === "number") {
- return ramp(indexOrColor).hex();
- } else {
- return indexOrColor.hex();
+ let styleDefinitions = buildStyleDefinition(
+ backgroundBase,
+ foregroundBase,
+ step
+ )
+
+ function colorString(indexOrColor: number | Color): string {
+ if (typeof indexOrColor === "number") {
+ return ramp(indexOrColor).hex()
+ } else {
+ return indexOrColor.hex()
+ }
+ }
+
+ function buildStyle(style: Styles): Style {
+ return {
+ background: colorString(styleDefinitions.background[style]),
+ border: colorString(styleDefinitions.border[style]),
+ foreground: colorString(styleDefinitions.foreground[style]),
+ }
}
- }
- function buildStyle(style: Styles): Style {
return {
- background: colorString(styleDefinitions.background[style]),
- border: colorString(styleDefinitions.border[style]),
- foreground: colorString(styleDefinitions.foreground[style]),
- };
- }
-
- return {
- default: buildStyle("default"),
- hovered: buildStyle("hovered"),
- pressed: buildStyle("pressed"),
- active: buildStyle("active"),
- disabled: buildStyle("disabled"),
- inverted: buildStyle("inverted"),
- };
+ default: buildStyle("default"),
+ hovered: buildStyle("hovered"),
+ pressed: buildStyle("pressed"),
+ active: buildStyle("active"),
+ disabled: buildStyle("disabled"),
+ inverted: buildStyle("inverted"),
+ }
}
function buildStyleDefinition(
- bgBase: number,
- fgBase: number,
- step: number = 0.08
+ bgBase: number,
+ fgBase: number,
+ step: number = 0.08
) {
- return {
- background: {
- default: bgBase,
- hovered: bgBase + step,
- pressed: bgBase + step * 1.5,
- active: bgBase + step * 2.2,
- disabled: bgBase,
- inverted: fgBase + step * 6,
- },
- border: {
- default: bgBase + step * 1,
- hovered: bgBase + step,
- pressed: bgBase + step,
- active: bgBase + step * 3,
- disabled: bgBase + step * 0.5,
- inverted: bgBase - step * 3,
- },
- foreground: {
- default: fgBase,
- hovered: fgBase,
- pressed: fgBase,
- active: fgBase + step * 6,
- disabled: bgBase + step * 4,
- inverted: bgBase + step * 2,
- },
- };
+ return {
+ background: {
+ default: bgBase,
+ hovered: bgBase + step,
+ pressed: bgBase + step * 1.5,
+ active: bgBase + step * 2.2,
+ disabled: bgBase,
+ inverted: fgBase + step * 6,
+ },
+ border: {
+ default: bgBase + step * 1,
+ hovered: bgBase + step,
+ pressed: bgBase + step,
+ active: bgBase + step * 3,
+ disabled: bgBase + step * 0.5,
+ inverted: bgBase - step * 3,
+ },
+ foreground: {
+ default: fgBase,
+ hovered: fgBase,
+ pressed: fgBase,
+ active: fgBase + step * 6,
+ disabled: bgBase + step * 4,
+ inverted: bgBase + step * 2,
+ },
+ }
}
@@ -1,165 +0,0 @@
-import { Scale } from "chroma-js";
-import { FontWeight } from "../../common";
-import { withOpacity } from "../../utils/color";
-
-export interface SyntaxHighlightStyle {
- color: string;
- weight?: FontWeight;
- underline?: boolean;
- italic?: boolean;
-}
-
-export interface Player {
- baseColor: string;
- cursorColor: string;
- selectionColor: string;
- borderColor: string;
-}
-export function buildPlayer(
- color: string,
- cursorOpacity?: number,
- selectionOpacity?: number,
- borderOpacity?: number
-) {
- return {
- baseColor: color,
- cursorColor: withOpacity(color, cursorOpacity || 1.0),
- selectionColor: withOpacity(color, selectionOpacity || 0.24),
- borderColor: withOpacity(color, borderOpacity || 0.8),
- };
-}
-
-export interface BackgroundColorSet {
- base: string;
- hovered: string;
- active: string;
-}
-
-export interface Syntax {
- primary: SyntaxHighlightStyle;
- comment: SyntaxHighlightStyle;
- punctuation: SyntaxHighlightStyle;
- constant: SyntaxHighlightStyle;
- keyword: SyntaxHighlightStyle;
- function: SyntaxHighlightStyle;
- type: SyntaxHighlightStyle;
- constructor: SyntaxHighlightStyle;
- property: SyntaxHighlightStyle;
- enum: SyntaxHighlightStyle;
- operator: SyntaxHighlightStyle;
- string: SyntaxHighlightStyle;
- number: SyntaxHighlightStyle;
- boolean: SyntaxHighlightStyle;
- predictive: SyntaxHighlightStyle;
- title: SyntaxHighlightStyle;
- emphasis: SyntaxHighlightStyle;
- linkUri: SyntaxHighlightStyle;
- linkText: SyntaxHighlightStyle;
-
- [key: string]: SyntaxHighlightStyle;
-}
-
-export default interface Theme {
- name: string;
- isLight: boolean;
- backgroundColor: {
- // Basically just Title Bar
- // Lowest background level
- 100: BackgroundColorSet;
- // Tab bars, panels, popovers
- // Mid-ground
- 300: BackgroundColorSet;
- // The editor
- // Foreground
- 500: BackgroundColorSet;
- // Hacks for elements on top of the midground
- // Buttons in a panel, tab bar, or panel
- on300: BackgroundColorSet;
- // Hacks for elements on top of the editor
- on500: BackgroundColorSet;
- ok: BackgroundColorSet;
- on500Ok: BackgroundColorSet;
- error: BackgroundColorSet;
- on500Error: BackgroundColorSet;
- warning: BackgroundColorSet;
- on500Warning: BackgroundColorSet;
- info: BackgroundColorSet;
- on500Info: BackgroundColorSet;
- };
- borderColor: {
- primary: string;
- secondary: string;
- muted: string;
- active: string;
- /**
- * Used for rendering borders on top of media like avatars, images, video, etc.
- */
- onMedia: string;
- ok: string;
- error: string;
- warning: string;
- info: string;
- };
- textColor: {
- primary: string;
- secondary: string;
- muted: string;
- placeholder: string;
- active: string;
- feature: string;
- ok: string;
- error: string;
- warning: string;
- info: string;
- onMedia: string;
- };
- iconColor: {
- primary: string;
- secondary: string;
- muted: string;
- placeholder: string;
- active: string;
- feature: string;
- ok: string;
- error: string;
- warning: string;
- info: string;
- };
- editor: {
- background: string;
- indent_guide: string;
- indent_guide_active: string;
- line: {
- active: string;
- highlighted: string;
- };
- highlight: {
- selection: string;
- occurrence: string;
- activeOccurrence: string;
- matchingBracket: string;
- match: string;
- activeMatch: string;
- related: string;
- };
- gutter: {
- primary: string;
- active: string;
- };
- };
-
- syntax: Syntax;
-
- player: {
- 1: Player;
- 2: Player;
- 3: Player;
- 4: Player;
- 5: Player;
- 6: Player;
- 7: Player;
- 8: Player;
- };
- shadow: string;
- ramps: { [rampName: string]: Scale };
-}
@@ -1,40 +1,69 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta, ThemeSyntax } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "One Dark";
+const name = "One Dark"
-export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma
- .scale([
- "#282c34",
- "#353b45",
- "#3e4451",
- "#545862",
- "#565c64",
- "#abb2bf",
- "#b6bdca",
- "#c8ccd4",
- ])
- .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
+const color = {
+ white: "#ACB2BE",
+ grey: "#5D636F",
+ red: "#D07277",
+ orange: "#C0966B",
+ yellow: "#DFC184",
+ green: "#A1C181",
+ teal: "#6FB4C0",
+ blue: "#74ADE9",
+ purple: "#B478CF",
+}
+
+const ramps = {
+ neutral: chroma
+ .scale([
+ "#282c34",
+ "#353b45",
+ "#3e4451",
+ "#545862",
+ "#565c64",
+ "#abb2bf",
+ "#b6bdca",
+ "#c8ccd4",
+ ])
+ .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
+ red: colorRamp(chroma(color.red)),
+ orange: colorRamp(chroma(color.orange)),
+ yellow: colorRamp(chroma(color.yellow)),
+ green: colorRamp(chroma(color.green)),
+ cyan: colorRamp(chroma(color.teal)),
+ blue: colorRamp(chroma(color.blue)),
+ violet: colorRamp(chroma(color.purple)),
+ magenta: colorRamp(chroma("#be5046")),
+}
+
+const syntax: ThemeSyntax = {
+ primary: { color: color.white },
+ comment: { color: color.grey },
+ function: { color: color.blue },
+ type: { color: color.teal },
+ property: { color: color.red },
+ number: { color: color.orange },
+ string: { color: color.green },
+ keyword: { color: color.purple },
+ boolean: { color: color.orange },
+ punctuation: { color: color.white },
+ operator: { color: color.teal },
+}
- red: colorRamp(chroma("#e06c75")),
- orange: colorRamp(chroma("#d19a66")),
- yellow: colorRamp(chroma("#e5c07b")),
- green: colorRamp(chroma("#98c379")),
- cyan: colorRamp(chroma("#56b6c2")),
- blue: colorRamp(chroma("#61afef")),
- violet: colorRamp(chroma("#c678dd")),
- magenta: colorRamp(chroma("#be5046")),
-});
+export const dark = createColorScheme(name, false, ramps, syntax)
export const meta: Meta = {
- name,
- author: "simurai",
- license: {
- SPDX: "MIT",
- 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"
+ name,
+ author: "simurai",
+ license: {
+ SPDX: "MIT",
+ 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",
}
@@ -1,39 +1,80 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { fontWeights } from "../common"
+import { Meta, ThemeSyntax } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "One Light";
+const name = "One Light"
-export const light = createColorScheme(`${name}`, true, {
- neutral: chroma.scale([
- "#090a0b",
- "#202227",
- "#383a42",
- "#696c77",
- "#a0a1a7",
- "#e5e5e6",
- "#f0f0f1",
- "#fafafa",
- ])
- .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
+const color = {
+ black: "#383A41",
+ grey: "#A2A3A7",
+ red: "#D36050",
+ orange: "#AD6F26",
+ yellow: "#DFC184",
+ green: "#659F58",
+ teal: "#3982B7",
+ blue: "#5B79E3",
+ purple: "#A449AB",
+ magenta: "#994EA6",
+}
+
+const ramps = {
+ neutral: chroma
+ .scale([
+ "#383A41",
+ "#535456",
+ "#696c77",
+ "#9D9D9F",
+ "#A9A9A9",
+ "#DBDBDC",
+ "#EAEAEB",
+ "#FAFAFA",
+ ])
+ .domain([0.05, 0.22, 0.25, 0.45, 0.62, 0.8, 0.9, 1]),
+ red: colorRamp(chroma(color.red)),
+ orange: colorRamp(chroma(color.orange)),
+ yellow: colorRamp(chroma(color.yellow)),
+ green: colorRamp(chroma(color.green)),
+ cyan: colorRamp(chroma(color.teal)),
+ blue: colorRamp(chroma(color.blue)),
+ violet: colorRamp(chroma(color.purple)),
+ magenta: colorRamp(chroma(color.magenta)),
+}
+
+const syntax: ThemeSyntax = {
+ primary: { color: color.black },
+ "variable.special": { color: color.orange },
+ comment: { color: color.grey },
+ punctuation: { color: color.black },
+ keyword: { color: color.purple },
+ function: { color: color.blue },
+ type: { color: color.teal },
+ variant: { color: color.blue },
+ property: { color: color.red },
+ enum: { color: color.red },
+ operator: { color: color.teal },
+ string: { color: color.green },
+ number: { color: color.orange },
+ boolean: { color: color.orange },
+ title: { color: color.red, weight: fontWeights.normal },
+ "emphasis.strong": {
+ color: color.orange,
+ },
+ linkText: { color: color.blue },
+ linkUri: { color: color.teal },
+}
- red: colorRamp(chroma("#ca1243")),
- orange: colorRamp(chroma("#d75f00")),
- yellow: colorRamp(chroma("#c18401")),
- green: colorRamp(chroma("#50a14f")),
- cyan: colorRamp(chroma("#0184bc")),
- blue: colorRamp(chroma("#4078f2")),
- violet: colorRamp(chroma("#a626a4")),
- magenta: colorRamp(chroma("#986801")),
-});
+export const light = createColorScheme(name, true, ramps, syntax)
export const meta: Meta = {
- name,
- author: "simurai",
- license: {
- SPDX: "MIT",
- 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"
+ name,
+ author: "simurai",
+ license: {
+ SPDX: "MIT",
+ 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",
}
@@ -1,41 +1,43 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "RosΓ© Pine Dawn";
+const name = "RosΓ© Pine Dawn"
const ramps = {
- neutral: chroma
- .scale([
- "#575279",
- "#797593",
- "#9893A5",
- "#B5AFB8",
- "#D3CCCC",
- "#F2E9E1",
- "#FFFAF3",
- "#FAF4ED",
- ])
- .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]),
- red: colorRamp(chroma("#B4637A")),
- orange: colorRamp(chroma("#D7827E")),
- yellow: colorRamp(chroma("#EA9D34")),
- green: colorRamp(chroma("#679967")),
- cyan: colorRamp(chroma("#286983")),
- blue: colorRamp(chroma("#56949F")),
- violet: colorRamp(chroma("#907AA9")),
- magenta: colorRamp(chroma("#79549F")),
-};
+ neutral: chroma
+ .scale([
+ "#575279",
+ "#797593",
+ "#9893A5",
+ "#B5AFB8",
+ "#D3CCCC",
+ "#F2E9E1",
+ "#FFFAF3",
+ "#FAF4ED",
+ ])
+ .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]),
+ red: colorRamp(chroma("#B4637A")),
+ orange: colorRamp(chroma("#D7827E")),
+ yellow: colorRamp(chroma("#EA9D34")),
+ green: colorRamp(chroma("#679967")),
+ cyan: colorRamp(chroma("#286983")),
+ blue: colorRamp(chroma("#56949F")),
+ violet: colorRamp(chroma("#907AA9")),
+ magenta: colorRamp(chroma("#79549F")),
+}
-export const light = createColorScheme(`${name}`, true, ramps);
+export const light = createColorScheme(`${name}`, true, ramps)
export const meta: Meta = {
- name,
- author: "edunfelt",
- license: {
- SPDX: "MIT",
- 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"
-}
+ name,
+ author: "edunfelt",
+ license: {
+ SPDX: "MIT",
+ 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",
+}
@@ -1,41 +1,43 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "RosΓ© Pine Moon";
+const name = "RosΓ© Pine Moon"
const ramps = {
- neutral: chroma
- .scale([
- "#232136",
- "#2A273F",
- "#393552",
- "#3E3A53",
- "#56526C",
- "#6E6A86",
- "#908CAA",
- "#E0DEF4",
- ])
- .domain([0, 0.3, 0.55, 1]),
- red: colorRamp(chroma("#EB6F92")),
- orange: colorRamp(chroma("#EBBCBA")),
- yellow: colorRamp(chroma("#F6C177")),
- green: colorRamp(chroma("#8DBD8D")),
- cyan: colorRamp(chroma("#409BBE")),
- blue: colorRamp(chroma("#9CCFD8")),
- violet: colorRamp(chroma("#C4A7E7")),
- magenta: colorRamp(chroma("#AB6FE9")),
-};
+ neutral: chroma
+ .scale([
+ "#232136",
+ "#2A273F",
+ "#393552",
+ "#3E3A53",
+ "#56526C",
+ "#6E6A86",
+ "#908CAA",
+ "#E0DEF4",
+ ])
+ .domain([0, 0.3, 0.55, 1]),
+ red: colorRamp(chroma("#EB6F92")),
+ orange: colorRamp(chroma("#EBBCBA")),
+ yellow: colorRamp(chroma("#F6C177")),
+ green: colorRamp(chroma("#8DBD8D")),
+ cyan: colorRamp(chroma("#409BBE")),
+ blue: colorRamp(chroma("#9CCFD8")),
+ violet: colorRamp(chroma("#C4A7E7")),
+ magenta: colorRamp(chroma("#AB6FE9")),
+}
-export const dark = createColorScheme(`${name}`, false, ramps);
+export const dark = createColorScheme(`${name}`, false, ramps)
export const meta: Meta = {
- name,
- author: "edunfelt",
- license: {
- SPDX: "MIT",
- 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"
-}
+ name,
+ author: "edunfelt",
+ license: {
+ SPDX: "MIT",
+ 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",
+}
@@ -1,39 +1,41 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "RosΓ© Pine";
+const name = "RosΓ© Pine"
const ramps = {
- neutral: chroma.scale([
- "#191724",
- "#1f1d2e",
- "#26233A",
- "#3E3A53",
- "#56526C",
- "#6E6A86",
- "#908CAA",
- "#E0DEF4",
- ]),
- red: colorRamp(chroma("#EB6F92")),
- orange: colorRamp(chroma("#EBBCBA")),
- yellow: colorRamp(chroma("#F6C177")),
- green: colorRamp(chroma("#8DBD8D")),
- cyan: colorRamp(chroma("#409BBE")),
- blue: colorRamp(chroma("#9CCFD8")),
- violet: colorRamp(chroma("#C4A7E7")),
- magenta: colorRamp(chroma("#AB6FE9")),
-};
+ neutral: chroma.scale([
+ "#191724",
+ "#1f1d2e",
+ "#26233A",
+ "#3E3A53",
+ "#56526C",
+ "#6E6A86",
+ "#908CAA",
+ "#E0DEF4",
+ ]),
+ red: colorRamp(chroma("#EB6F92")),
+ orange: colorRamp(chroma("#EBBCBA")),
+ yellow: colorRamp(chroma("#F6C177")),
+ green: colorRamp(chroma("#8DBD8D")),
+ cyan: colorRamp(chroma("#409BBE")),
+ blue: colorRamp(chroma("#9CCFD8")),
+ violet: colorRamp(chroma("#C4A7E7")),
+ magenta: colorRamp(chroma("#AB6FE9")),
+}
-export const dark = createColorScheme(`${name}`, false, ramps);
+export const dark = createColorScheme(`${name}`, false, ramps)
export const meta: Meta = {
- name,
- author: "edunfelt",
- license: {
- SPDX: "MIT",
- 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"
-}
+ name,
+ author: "edunfelt",
+ license: {
+ SPDX: "MIT",
+ 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",
+}
@@ -1,40 +1,41 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "Sandcastle";
+const name = "Sandcastle"
const ramps = {
- neutral: chroma.scale([
- "#282c34",
- "#2c323b",
- "#3e4451",
- "#665c54",
- "#928374",
- "#a89984",
- "#d5c4a1",
- "#fdf4c1",
- ]),
- red: colorRamp(chroma("#B4637A")),
- orange: colorRamp(chroma("#a07e3b")),
- yellow: colorRamp(chroma("#a07e3b")),
- green: colorRamp(chroma("#83a598")),
- cyan: colorRamp(chroma("#83a598")),
- blue: colorRamp(chroma("#528b8b")),
- violet: colorRamp(chroma("#d75f5f")),
- magenta: colorRamp(chroma("#a87322")),
-};
+ neutral: chroma.scale([
+ "#282c34",
+ "#2c323b",
+ "#3e4451",
+ "#665c54",
+ "#928374",
+ "#a89984",
+ "#d5c4a1",
+ "#fdf4c1",
+ ]),
+ red: colorRamp(chroma("#B4637A")),
+ orange: colorRamp(chroma("#a07e3b")),
+ yellow: colorRamp(chroma("#a07e3b")),
+ green: colorRamp(chroma("#83a598")),
+ cyan: colorRamp(chroma("#83a598")),
+ blue: colorRamp(chroma("#528b8b")),
+ violet: colorRamp(chroma("#d75f5f")),
+ magenta: colorRamp(chroma("#a87322")),
+}
-export const dark = createColorScheme(`${name}`, false, ramps);
+export const dark = createColorScheme(`${name}`, false, ramps)
export const meta: Meta = {
- name,
- author: "gessig",
- license: {
- SPDX: "MIT",
- https_url: "https://raw.githubusercontent.com/gessig/base16-sandcastle-scheme/master/LICENSE",
- license_checksum: "8399d44b4d935b60be9fee0a76d7cc9a817b4f3f11574c9d6d1e8fd57e72ffdc"
- },
- url: "https://github.com/gessig/base16-sandcastle-scheme"
+ name,
+ author: "gessig",
+ license: {
+ SPDX: "MIT",
+ https_url:
+ "https://raw.githubusercontent.com/gessig/base16-sandcastle-scheme/master/LICENSE",
+ license_checksum:
+ "8399d44b4d935b60be9fee0a76d7cc9a817b4f3f11574c9d6d1e8fd57e72ffdc",
+ },
+ url: "https://github.com/gessig/base16-sandcastle-scheme",
}
-
@@ -1,43 +1,44 @@
-import chroma from "chroma-js";
-import { Meta as Metadata } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta as Metadata } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "Solarized";
+const name = "Solarized"
const ramps = {
- neutral: chroma
- .scale([
- "#002b36",
- "#073642",
- "#586e75",
- "#657b83",
- "#839496",
- "#93a1a1",
- "#eee8d5",
- "#fdf6e3",
- ])
- .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
- red: colorRamp(chroma("#dc322f")),
- orange: colorRamp(chroma("#cb4b16")),
- yellow: colorRamp(chroma("#b58900")),
- green: colorRamp(chroma("#859900")),
- cyan: colorRamp(chroma("#2aa198")),
- blue: colorRamp(chroma("#268bd2")),
- violet: colorRamp(chroma("#6c71c4")),
- magenta: colorRamp(chroma("#d33682")),
-};
+ neutral: chroma
+ .scale([
+ "#002b36",
+ "#073642",
+ "#586e75",
+ "#657b83",
+ "#839496",
+ "#93a1a1",
+ "#eee8d5",
+ "#fdf6e3",
+ ])
+ .domain([0, 0.2, 0.38, 0.45, 0.65, 0.7, 0.85, 1]),
+ red: colorRamp(chroma("#dc322f")),
+ orange: colorRamp(chroma("#cb4b16")),
+ yellow: colorRamp(chroma("#b58900")),
+ green: colorRamp(chroma("#859900")),
+ cyan: colorRamp(chroma("#2aa198")),
+ blue: colorRamp(chroma("#268bd2")),
+ violet: colorRamp(chroma("#6c71c4")),
+ magenta: colorRamp(chroma("#d33682")),
+}
-export const dark = createColorScheme(`${name} Dark`, false, ramps);
-export const light = createColorScheme(`${name} Light`, true, ramps);
+export const dark = createColorScheme(`${name} Dark`, false, ramps)
+export const light = createColorScheme(`${name} Light`, true, ramps)
export const meta: Metadata = {
- name,
- author: "Ethan Schoonover",
- license: {
- SPDX: "MIT",
- https_url: "https://raw.githubusercontent.com/altercation/solarized/master/LICENSE",
- license_checksum: "494aefdabf86acce06bd63001ad8aedad4ee38da23509d3f917d95aa3368b9a6"
- },
- url: "https://github.com/altercation/solarized"
+ name,
+ author: "Ethan Schoonover",
+ license: {
+ SPDX: "MIT",
+ https_url:
+ "https://raw.githubusercontent.com/altercation/solarized/master/LICENSE",
+ license_checksum:
+ "494aefdabf86acce06bd63001ad8aedad4ee38da23509d3f917d95aa3368b9a6",
+ },
+ url: "https://github.com/altercation/solarized",
}
-
@@ -1,31 +1,31 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Abruzzo";
-const author = "slightknack <hey@isaac.sh>";
-const url = "https://github.com/slightknack";
+const name = "Abruzzo"
+const author = "slightknack <hey@isaac.sh>"
+const url = "https://github.com/slightknack"
const license = {
- type: "",
- url: ""
+ type: "",
+ url: "",
}
export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma.scale([
- "#1b0d05",
- "#2c1e18",
- "#654035",
- "#9d5e4a",
- "#b37354",
- "#c1825a",
- "#dda66e",
- "#fbf3e2",
- ]),
- red: colorRamp(chroma("#e594c4")),
- orange: colorRamp(chroma("#d9e87e")),
- yellow: colorRamp(chroma("#fd9d83")),
- green: colorRamp(chroma("#96adf7")),
- cyan: colorRamp(chroma("#fc798f")),
- blue: colorRamp(chroma("#BCD0F5")),
- violet: colorRamp(chroma("#dac5eb")),
- magenta: colorRamp(chroma("#c1a3ef")),
-});
+ neutral: chroma.scale([
+ "#1b0d05",
+ "#2c1e18",
+ "#654035",
+ "#9d5e4a",
+ "#b37354",
+ "#c1825a",
+ "#dda66e",
+ "#fbf3e2",
+ ]),
+ red: colorRamp(chroma("#e594c4")),
+ orange: colorRamp(chroma("#d9e87e")),
+ yellow: colorRamp(chroma("#fd9d83")),
+ green: colorRamp(chroma("#96adf7")),
+ cyan: colorRamp(chroma("#fc798f")),
+ blue: colorRamp(chroma("#BCD0F5")),
+ violet: colorRamp(chroma("#dac5eb")),
+ magenta: colorRamp(chroma("#c1a3ef")),
+})
@@ -1,34 +1,35 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Atelier Dune";
-const author = "atelierbram";
-const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/";
+const name = "Atelier Dune"
+const author = "atelierbram"
+const url =
+ "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune/"
const license = {
- type: "MIT",
- url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE",
-};
+ type: "MIT",
+ url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE",
+}
const ramps = {
- neutral: chroma.scale([
- "#20201d",
- "#292824",
- "#6e6b5e",
- "#7d7a68",
- "#999580",
- "#a6a28c",
- "#e8e4cf",
- "#fefbec",
- ]),
- red: colorRamp(chroma("#d73737")),
- orange: colorRamp(chroma("#b65611")),
- yellow: colorRamp(chroma("#ae9513")),
- green: colorRamp(chroma("#60ac39")),
- cyan: colorRamp(chroma("#1fad83")),
- blue: colorRamp(chroma("#6684e1")),
- violet: colorRamp(chroma("#b854d4")),
- magenta: colorRamp(chroma("#d43552")),
-};
+ neutral: chroma.scale([
+ "#20201d",
+ "#292824",
+ "#6e6b5e",
+ "#7d7a68",
+ "#999580",
+ "#a6a28c",
+ "#e8e4cf",
+ "#fefbec",
+ ]),
+ red: colorRamp(chroma("#d73737")),
+ orange: colorRamp(chroma("#b65611")),
+ yellow: colorRamp(chroma("#ae9513")),
+ green: colorRamp(chroma("#60ac39")),
+ cyan: colorRamp(chroma("#1fad83")),
+ blue: colorRamp(chroma("#6684e1")),
+ violet: colorRamp(chroma("#b854d4")),
+ magenta: colorRamp(chroma("#d43552")),
+}
-export const dark = createColorScheme(`${name} Dark`, false, ramps);
-export const light = createColorScheme(`${name} Light`, true, ramps);
+export const dark = createColorScheme(`${name} Dark`, false, ramps)
+export const light = createColorScheme(`${name} Light`, true, ramps)
@@ -1,53 +1,54 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Atelier Heath";
-const author = "atelierbram";
-const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath/";
+const name = "Atelier Heath"
+const author = "atelierbram"
+const url =
+ "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/heath/"
const license = {
- type: "MIT",
- url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE",
-};
+ type: "MIT",
+ url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE",
+}
// `name-[light|dark]`, isLight, color ramps
export const dark = createColorScheme(`${name} Dark`, false, {
- neutral: chroma.scale([
- "#1b181b",
- "#292329",
- "#695d69",
- "#776977",
- "#9e8f9e",
- "#ab9bab",
- "#d8cad8",
- "#f7f3f7",
- ]),
- red: colorRamp(chroma("#ca402b")),
- orange: colorRamp(chroma("#a65926")),
- yellow: colorRamp(chroma("#bb8a35")),
- green: colorRamp(chroma("#918b3b")),
- cyan: colorRamp(chroma("#159393")),
- blue: colorRamp(chroma("#516aec")),
- violet: colorRamp(chroma("#7b59c0")),
- magenta: colorRamp(chroma("#cc33cc")),
-});
+ neutral: chroma.scale([
+ "#1b181b",
+ "#292329",
+ "#695d69",
+ "#776977",
+ "#9e8f9e",
+ "#ab9bab",
+ "#d8cad8",
+ "#f7f3f7",
+ ]),
+ red: colorRamp(chroma("#ca402b")),
+ orange: colorRamp(chroma("#a65926")),
+ yellow: colorRamp(chroma("#bb8a35")),
+ green: colorRamp(chroma("#918b3b")),
+ cyan: colorRamp(chroma("#159393")),
+ blue: colorRamp(chroma("#516aec")),
+ violet: colorRamp(chroma("#7b59c0")),
+ magenta: colorRamp(chroma("#cc33cc")),
+})
export const light = createColorScheme(`${name} Light`, true, {
- neutral: chroma.scale([
- "#161b1d",
- "#1f292e",
- "#516d7b",
- "#5a7b8c",
- "#7195a8",
- "#7ea2b4",
- "#c1e4f6",
- "#ebf8ff",
- ]),
- red: colorRamp(chroma("#d22d72")),
- orange: colorRamp(chroma("#935c25")),
- yellow: colorRamp(chroma("#8a8a0f")),
- green: colorRamp(chroma("#568c3b")),
- cyan: colorRamp(chroma("#2d8f6f")),
- blue: colorRamp(chroma("#257fad")),
- violet: colorRamp(chroma("#6b6bb8")),
- magenta: colorRamp(chroma("#b72dd2")),
-});
+ neutral: chroma.scale([
+ "#161b1d",
+ "#1f292e",
+ "#516d7b",
+ "#5a7b8c",
+ "#7195a8",
+ "#7ea2b4",
+ "#c1e4f6",
+ "#ebf8ff",
+ ]),
+ red: colorRamp(chroma("#d22d72")),
+ orange: colorRamp(chroma("#935c25")),
+ yellow: colorRamp(chroma("#8a8a0f")),
+ green: colorRamp(chroma("#568c3b")),
+ cyan: colorRamp(chroma("#2d8f6f")),
+ blue: colorRamp(chroma("#257fad")),
+ violet: colorRamp(chroma("#6b6bb8")),
+ magenta: colorRamp(chroma("#b72dd2")),
+})
@@ -1,34 +1,35 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Atelier Seaside";
-const author = "atelierbram";
-const url = "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside/";
+const name = "Atelier Seaside"
+const author = "atelierbram"
+const url =
+ "https://atelierbram.github.io/syntax-highlighting/atelier-schemes/seaside/"
const license = {
- type: "MIT",
- url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE",
-};
+ type: "MIT",
+ url: "https://github.com/atelierbram/syntax-highlighting/blob/master/LICENSE",
+}
const ramps = {
- neutral: chroma.scale([
- "#131513",
- "#242924",
- "#5e6e5e",
- "#687d68",
- "#809980",
- "#8ca68c",
- "#cfe8cf",
- "#f4fbf4",
- ]),
- red: colorRamp(chroma("#e6193c")),
- orange: colorRamp(chroma("#87711d")),
- yellow: colorRamp(chroma("#98981b")),
- green: colorRamp(chroma("#29a329")),
- cyan: colorRamp(chroma("#1999b3")),
- blue: colorRamp(chroma("#3d62f5")),
- violet: colorRamp(chroma("#ad2bee")),
- magenta: colorRamp(chroma("#e619c3")),
-};
+ neutral: chroma.scale([
+ "#131513",
+ "#242924",
+ "#5e6e5e",
+ "#687d68",
+ "#809980",
+ "#8ca68c",
+ "#cfe8cf",
+ "#f4fbf4",
+ ]),
+ red: colorRamp(chroma("#e6193c")),
+ orange: colorRamp(chroma("#87711d")),
+ yellow: colorRamp(chroma("#98981b")),
+ green: colorRamp(chroma("#29a329")),
+ cyan: colorRamp(chroma("#1999b3")),
+ blue: colorRamp(chroma("#3d62f5")),
+ violet: colorRamp(chroma("#ad2bee")),
+ magenta: colorRamp(chroma("#e619c3")),
+}
-export const dark = createColorScheme(`${name} Dark`, false, ramps);
-export const light = createColorScheme(`${name} Light`, true, ramps);
+export const dark = createColorScheme(`${name} Dark`, false, ramps)
+export const light = createColorScheme(`${name} Light`, true, ramps)
@@ -1,31 +1,31 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Ayu";
-const author = "Konstantin Pschera <me@kons.ch>";
-const url = "https://github.com/ayu-theme/ayu-colors";
+const name = "Ayu"
+const author = "Konstantin Pschera <me@kons.ch>"
+const url = "https://github.com/ayu-theme/ayu-colors"
const license = {
- type: "MIT",
- url: "https://github.com/ayu-theme/ayu-colors/blob/master/license"
+ type: "MIT",
+ url: "https://github.com/ayu-theme/ayu-colors/blob/master/license",
}
export const dark = createColorScheme(`${name} Mirage`, false, {
- neutral: chroma.scale([
- "#171B24",
- "#1F2430",
- "#242936",
- "#707A8C",
- "#8A9199",
- "#CCCAC2",
- "#D9D7CE",
- "#F3F4F5",
- ]),
- red: colorRamp(chroma("#F28779")),
- orange: colorRamp(chroma("#FFAD66")),
- yellow: colorRamp(chroma("#FFD173")),
- green: colorRamp(chroma("#D5FF80")),
- cyan: colorRamp(chroma("#95E6CB")),
- blue: colorRamp(chroma("#5CCFE6")),
- violet: colorRamp(chroma("#D4BFFF")),
- magenta: colorRamp(chroma("#F29E74")),
-});
+ neutral: chroma.scale([
+ "#171B24",
+ "#1F2430",
+ "#242936",
+ "#707A8C",
+ "#8A9199",
+ "#CCCAC2",
+ "#D9D7CE",
+ "#F3F4F5",
+ ]),
+ red: colorRamp(chroma("#F28779")),
+ orange: colorRamp(chroma("#FFAD66")),
+ yellow: colorRamp(chroma("#FFD173")),
+ green: colorRamp(chroma("#D5FF80")),
+ cyan: colorRamp(chroma("#95E6CB")),
+ blue: colorRamp(chroma("#5CCFE6")),
+ violet: colorRamp(chroma("#D4BFFF")),
+ magenta: colorRamp(chroma("#F29E74")),
+})
@@ -1,52 +1,52 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Ayu";
-const author = "Konstantin Pschera <me@kons.ch>";
-const url = "https://github.com/ayu-theme/ayu-colors";
+const name = "Ayu"
+const author = "Konstantin Pschera <me@kons.ch>"
+const url = "https://github.com/ayu-theme/ayu-colors"
const license = {
- type: "MIT",
- url: "https://github.com/ayu-theme/ayu-colors/blob/master/license"
+ type: "MIT",
+ url: "https://github.com/ayu-theme/ayu-colors/blob/master/license",
}
export const dark = createColorScheme(`${name} Dark`, false, {
- neutral: chroma.scale([
- "#0F1419",
- "#131721",
- "#272D38",
- "#3E4B59",
- "#BFBDB6",
- "#E6E1CF",
- "#E6E1CF",
- "#F3F4F5",
- ]),
- red: colorRamp(chroma("#F07178")),
- orange: colorRamp(chroma("#FF8F40")),
- yellow: colorRamp(chroma("#FFB454")),
- green: colorRamp(chroma("#B8CC52")),
- cyan: colorRamp(chroma("#95E6CB")),
- blue: colorRamp(chroma("#59C2FF")),
- violet: colorRamp(chroma("#D2A6FF")),
- magenta: colorRamp(chroma("#E6B673")),
-});
+ neutral: chroma.scale([
+ "#0F1419",
+ "#131721",
+ "#272D38",
+ "#3E4B59",
+ "#BFBDB6",
+ "#E6E1CF",
+ "#E6E1CF",
+ "#F3F4F5",
+ ]),
+ red: colorRamp(chroma("#F07178")),
+ orange: colorRamp(chroma("#FF8F40")),
+ yellow: colorRamp(chroma("#FFB454")),
+ green: colorRamp(chroma("#B8CC52")),
+ cyan: colorRamp(chroma("#95E6CB")),
+ blue: colorRamp(chroma("#59C2FF")),
+ violet: colorRamp(chroma("#D2A6FF")),
+ magenta: colorRamp(chroma("#E6B673")),
+})
export const light = createColorScheme(`${name} Light`, true, {
- neutral: chroma.scale([
- "#1A1F29",
- "#242936",
- "#5C6773",
- "#828C99",
- "#ABB0B6",
- "#F8F9FA",
- "#F3F4F5",
- "#FAFAFA",
- ]),
- red: colorRamp(chroma("#F07178")),
- orange: colorRamp(chroma("#FA8D3E")),
- yellow: colorRamp(chroma("#F2AE49")),
- green: colorRamp(chroma("#86B300")),
- cyan: colorRamp(chroma("#4CBF99")),
- blue: colorRamp(chroma("#36A3D9")),
- violet: colorRamp(chroma("#A37ACC")),
- magenta: colorRamp(chroma("#E6BA7E")),
-});
+ neutral: chroma.scale([
+ "#1A1F29",
+ "#242936",
+ "#5C6773",
+ "#828C99",
+ "#ABB0B6",
+ "#F8F9FA",
+ "#F3F4F5",
+ "#FAFAFA",
+ ]),
+ red: colorRamp(chroma("#F07178")),
+ orange: colorRamp(chroma("#FA8D3E")),
+ yellow: colorRamp(chroma("#F2AE49")),
+ green: colorRamp(chroma("#86B300")),
+ cyan: colorRamp(chroma("#4CBF99")),
+ blue: colorRamp(chroma("#36A3D9")),
+ violet: colorRamp(chroma("#A37ACC")),
+ magenta: colorRamp(chroma("#E6BA7E")),
+})
@@ -1,73 +1,73 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Brush Trees";
-const author = "Abraham White <abelincoln.white@gmail.com>";
-const url = "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme";
+const name = "Brush Trees"
+const author = "Abraham White <abelincoln.white@gmail.com>"
+const url = "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme"
const license = {
- type: "MIT",
- url: "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme/blob/master/LICENSE"
+ type: "MIT",
+ url: "https://github.com/WhiteAbeLincoln/base16-brushtrees-scheme/blob/master/LICENSE",
}
export const dark = createColorScheme(`${name} Dark`, false, {
- neutral: chroma.scale([
- "#485867",
- "#5A6D7A",
- "#6D828E",
- "#8299A1",
- "#98AFB5",
- "#B0C5C8",
- "#C9DBDC",
- "#E3EFEF",
- ]),
- red: colorRamp(chroma("#b38686")),
- orange: colorRamp(chroma("#d8bba2")),
- yellow: colorRamp(chroma("#aab386")),
- green: colorRamp(chroma("#87b386")),
- cyan: colorRamp(chroma("#86b3b3")),
- blue: colorRamp(chroma("#868cb3")),
- violet: colorRamp(chroma("#b386b2")),
- magenta: colorRamp(chroma("#b39f9f")),
-});
+ neutral: chroma.scale([
+ "#485867",
+ "#5A6D7A",
+ "#6D828E",
+ "#8299A1",
+ "#98AFB5",
+ "#B0C5C8",
+ "#C9DBDC",
+ "#E3EFEF",
+ ]),
+ red: colorRamp(chroma("#b38686")),
+ orange: colorRamp(chroma("#d8bba2")),
+ yellow: colorRamp(chroma("#aab386")),
+ green: colorRamp(chroma("#87b386")),
+ cyan: colorRamp(chroma("#86b3b3")),
+ blue: colorRamp(chroma("#868cb3")),
+ violet: colorRamp(chroma("#b386b2")),
+ magenta: colorRamp(chroma("#b39f9f")),
+})
export const mirage = createColorScheme(`${name} Mirage`, false, {
- neutral: chroma.scale([
- "#485867",
- "#5A6D7A",
- "#6D828E",
- "#8299A1",
- "#98AFB5",
- "#B0C5C8",
- "#C9DBDC",
- "#E3EFEF",
- ]),
- red: colorRamp(chroma("#F28779")),
- orange: colorRamp(chroma("#FFAD66")),
- yellow: colorRamp(chroma("#FFD173")),
- green: colorRamp(chroma("#D5FF80")),
- cyan: colorRamp(chroma("#95E6CB")),
- blue: colorRamp(chroma("#5CCFE6")),
- violet: colorRamp(chroma("#D4BFFF")),
- magenta: colorRamp(chroma("#F29E74")),
-});
+ neutral: chroma.scale([
+ "#485867",
+ "#5A6D7A",
+ "#6D828E",
+ "#8299A1",
+ "#98AFB5",
+ "#B0C5C8",
+ "#C9DBDC",
+ "#E3EFEF",
+ ]),
+ red: colorRamp(chroma("#F28779")),
+ orange: colorRamp(chroma("#FFAD66")),
+ yellow: colorRamp(chroma("#FFD173")),
+ green: colorRamp(chroma("#D5FF80")),
+ cyan: colorRamp(chroma("#95E6CB")),
+ blue: colorRamp(chroma("#5CCFE6")),
+ violet: colorRamp(chroma("#D4BFFF")),
+ magenta: colorRamp(chroma("#F29E74")),
+})
export const light = createColorScheme(`${name} Light`, true, {
- neutral: chroma.scale([
- "#1A1F29",
- "#242936",
- "#5C6773",
- "#828C99",
- "#ABB0B6",
- "#F8F9FA",
- "#F3F4F5",
- "#FAFAFA",
- ]),
- red: colorRamp(chroma("#b38686")),
- orange: colorRamp(chroma("#d8bba2")),
- yellow: colorRamp(chroma("#aab386")),
- green: colorRamp(chroma("#87b386")),
- cyan: colorRamp(chroma("#86b3b3")),
- blue: colorRamp(chroma("#868cb3")),
- violet: colorRamp(chroma("#b386b2")),
- magenta: colorRamp(chroma("#b39f9f")),
-});
+ neutral: chroma.scale([
+ "#1A1F29",
+ "#242936",
+ "#5C6773",
+ "#828C99",
+ "#ABB0B6",
+ "#F8F9FA",
+ "#F3F4F5",
+ "#FAFAFA",
+ ]),
+ red: colorRamp(chroma("#b38686")),
+ orange: colorRamp(chroma("#d8bba2")),
+ yellow: colorRamp(chroma("#aab386")),
+ green: colorRamp(chroma("#87b386")),
+ cyan: colorRamp(chroma("#86b3b3")),
+ blue: colorRamp(chroma("#868cb3")),
+ violet: colorRamp(chroma("#b386b2")),
+ magenta: colorRamp(chroma("#b39f9f")),
+})
@@ -1,31 +1,31 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Dracula";
-const author = "zenorocha";
-const url = "https://github.com/dracula/dracula-theme";
+const name = "Dracula"
+const author = "zenorocha"
+const url = "https://github.com/dracula/dracula-theme"
const license = {
- type: "MIT",
- url: "https://github.com/dracula/dracula-theme/blob/master/LICENSE",
-};
+ type: "MIT",
+ url: "https://github.com/dracula/dracula-theme/blob/master/LICENSE",
+}
export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma.scale([
- "#282A36",
- "#3a3c4e",
- "#4d4f68",
- "#626483",
- "#62d6e8",
- "#e9e9f4",
- "#f1f2f8",
- "#f8f8f2",
- ]),
- red: colorRamp(chroma("#ff5555")),
- orange: colorRamp(chroma("#ffb86c")),
- yellow: colorRamp(chroma("#f1fa8c")),
- green: colorRamp(chroma("#50fa7b")),
- cyan: colorRamp(chroma("#8be9fd")),
- blue: colorRamp(chroma("#6272a4")),
- violet: colorRamp(chroma("#bd93f9")),
- magenta: colorRamp(chroma("#00f769")),
-});
+ neutral: chroma.scale([
+ "#282A36",
+ "#3a3c4e",
+ "#4d4f68",
+ "#626483",
+ "#62d6e8",
+ "#e9e9f4",
+ "#f1f2f8",
+ "#f8f8f2",
+ ]),
+ red: colorRamp(chroma("#ff5555")),
+ orange: colorRamp(chroma("#ffb86c")),
+ yellow: colorRamp(chroma("#f1fa8c")),
+ green: colorRamp(chroma("#50fa7b")),
+ cyan: colorRamp(chroma("#8be9fd")),
+ blue: colorRamp(chroma("#6272a4")),
+ violet: colorRamp(chroma("#bd93f9")),
+ magenta: colorRamp(chroma("#00f769")),
+})
@@ -1,138 +1,138 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Gruvbox";
-const author = "Dawid Kurek (dawikur@gmail.com)";
-const url = "https://github.com/morhetz/gruvbox";
+const name = "Gruvbox"
+const author = "Dawid Kurek (dawikur@gmail.com)"
+const url = "https://github.com/morhetz/gruvbox"
const license = {
- type: "MIT/X11",
- url: "https://en.wikipedia.org/wiki/MIT_License",
-};
+ type: "MIT/X11",
+ url: "https://en.wikipedia.org/wiki/MIT_License",
+}
export const dark = createColorScheme(`${name} Dark Medium`, false, {
- neutral: chroma.scale([
- "#282828",
- "#3c3836",
- "#504945",
- "#665c54",
- "#7C6F64",
- "#928374",
- "#A89984",
- "#BDAE93",
- "#D5C4A1",
- "#EBDBB2",
- "#FBF1C7",
- ]),
- red: chroma.scale([
- "#4D150F",
- "#7D241A",
- "#A31C17",
- "#CC241D",
- "#C83A29",
- "#FB4934",
- "#F06D61",
- "#E6928E",
- "#FFFFFF",
- ]),
- orange: chroma.scale([
- "#462307",
- "#7F400C",
- "#AB4A0B",
- "#D65D0E",
- "#CB6614",
- "#FE8019",
- "#F49750",
- "#EBAE87",
- "#FFFFFF",
- ]),
- yellow: chroma.scale([
- "#3D2C05",
- "#7D5E17",
- "#AC7A1A",
- "#D79921",
- "#E8AB28",
- "#FABD2F",
- "#F2C45F",
- "#EBCC90",
- "#FFFFFF",
- ]),
- green: chroma.scale([
- "#32330A",
- "#5C5D13",
- "#797814",
- "#98971A",
- "#93951E",
- "#B8BB26",
- "#C2C359",
- "#CCCB8D",
- "#FFFFFF",
- ]),
- cyan: chroma.scale([
- "#283D20",
- "#47603E",
- "#537D54",
- "#689D6A",
- "#719963",
- "#8EC07C",
- "#A1C798",
- "#B4CEB5",
- "#FFFFFF",
- ]),
- blue: chroma.scale([
- "#103738",
- "#214C4D",
- "#376A6C",
- "#458588",
- "#688479",
- "#83A598",
- "#92B3AE",
- "#A2C2C4",
- "#FFFFFF",
- ]),
- violet: chroma.scale([
- "#392228",
- "#69434D",
- "#8D4E6B",
- "#B16286",
- "#A86B7C",
- "#D3869B",
- "#D59BAF",
- "#D8B1C3",
- "#FFFFFF",
- ]),
- magenta: chroma.scale([
- "#48402C",
- "#756D59",
- "#867A69",
- "#A89984",
- "#BCAF8E",
- "#EBDBB2",
- "#DFD3BA",
- "#D4CCC2",
- "#FFFFFF",
- ]),
-});
+ neutral: chroma.scale([
+ "#282828",
+ "#3c3836",
+ "#504945",
+ "#665c54",
+ "#7C6F64",
+ "#928374",
+ "#A89984",
+ "#BDAE93",
+ "#D5C4A1",
+ "#EBDBB2",
+ "#FBF1C7",
+ ]),
+ red: chroma.scale([
+ "#4D150F",
+ "#7D241A",
+ "#A31C17",
+ "#CC241D",
+ "#C83A29",
+ "#FB4934",
+ "#F06D61",
+ "#E6928E",
+ "#FFFFFF",
+ ]),
+ orange: chroma.scale([
+ "#462307",
+ "#7F400C",
+ "#AB4A0B",
+ "#D65D0E",
+ "#CB6614",
+ "#FE8019",
+ "#F49750",
+ "#EBAE87",
+ "#FFFFFF",
+ ]),
+ yellow: chroma.scale([
+ "#3D2C05",
+ "#7D5E17",
+ "#AC7A1A",
+ "#D79921",
+ "#E8AB28",
+ "#FABD2F",
+ "#F2C45F",
+ "#EBCC90",
+ "#FFFFFF",
+ ]),
+ green: chroma.scale([
+ "#32330A",
+ "#5C5D13",
+ "#797814",
+ "#98971A",
+ "#93951E",
+ "#B8BB26",
+ "#C2C359",
+ "#CCCB8D",
+ "#FFFFFF",
+ ]),
+ cyan: chroma.scale([
+ "#283D20",
+ "#47603E",
+ "#537D54",
+ "#689D6A",
+ "#719963",
+ "#8EC07C",
+ "#A1C798",
+ "#B4CEB5",
+ "#FFFFFF",
+ ]),
+ blue: chroma.scale([
+ "#103738",
+ "#214C4D",
+ "#376A6C",
+ "#458588",
+ "#688479",
+ "#83A598",
+ "#92B3AE",
+ "#A2C2C4",
+ "#FFFFFF",
+ ]),
+ violet: chroma.scale([
+ "#392228",
+ "#69434D",
+ "#8D4E6B",
+ "#B16286",
+ "#A86B7C",
+ "#D3869B",
+ "#D59BAF",
+ "#D8B1C3",
+ "#FFFFFF",
+ ]),
+ magenta: chroma.scale([
+ "#48402C",
+ "#756D59",
+ "#867A69",
+ "#A89984",
+ "#BCAF8E",
+ "#EBDBB2",
+ "#DFD3BA",
+ "#D4CCC2",
+ "#FFFFFF",
+ ]),
+})
export const light = createColorScheme(`${name} Light Medium`, true, {
- neutral: chroma.scale([
- "#282828",
- "#3c3836",
- "#504945",
- "#665c54",
- "#7C6F64",
- "#928374",
- "#A89984",
- "#BDAE93",
- "#D5C4A1",
- "#EBDBB2",
- "#FBF1C7",
- ]),
- red: colorRamp(chroma("#9d0006")),
- orange: colorRamp(chroma("#af3a03")),
- yellow: colorRamp(chroma("#b57614")),
- green: colorRamp(chroma("#79740e")),
- cyan: colorRamp(chroma("#427b58")),
- blue: colorRamp(chroma("#076678")),
- violet: colorRamp(chroma("#8f3f71")),
- magenta: colorRamp(chroma("#d65d0e")),
-});
+ neutral: chroma.scale([
+ "#282828",
+ "#3c3836",
+ "#504945",
+ "#665c54",
+ "#7C6F64",
+ "#928374",
+ "#A89984",
+ "#BDAE93",
+ "#D5C4A1",
+ "#EBDBB2",
+ "#FBF1C7",
+ ]),
+ red: colorRamp(chroma("#9d0006")),
+ orange: colorRamp(chroma("#af3a03")),
+ yellow: colorRamp(chroma("#b57614")),
+ green: colorRamp(chroma("#79740e")),
+ cyan: colorRamp(chroma("#427b58")),
+ blue: colorRamp(chroma("#076678")),
+ violet: colorRamp(chroma("#8f3f71")),
+ magenta: colorRamp(chroma("#d65d0e")),
+})
@@ -1,32 +1,32 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Monokai";
-const author = "Wimer Hazenberg (http://www.monokai.nl)";
-const url = "https://base16.netlify.app/previews/base16-monokai.html";
+const name = "Monokai"
+const author = "Wimer Hazenberg (http://www.monokai.nl)"
+const url = "https://base16.netlify.app/previews/base16-monokai.html"
const license = {
- type: "?",
- url: "?",
-};
+ type: "?",
+ url: "?",
+}
// `name-[light|dark]`, isLight, color ramps
export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma.scale([
- "#272822",
- "#383830",
- "#49483e",
- "#75715e",
- "#a59f85",
- "#f8f8f2",
- "#f5f4f1",
- "#f9f8f5",
- ]),
- red: colorRamp(chroma("#f92672")),
- orange: colorRamp(chroma("#fd971f")),
- yellow: colorRamp(chroma("#f4bf75")),
- green: colorRamp(chroma("#a6e22e")),
- cyan: colorRamp(chroma("#a1efe4")),
- blue: colorRamp(chroma("#66d9ef")),
- violet: colorRamp(chroma("#ae81ff")),
- magenta: colorRamp(chroma("#cc6633")),
-});
+ neutral: chroma.scale([
+ "#272822",
+ "#383830",
+ "#49483e",
+ "#75715e",
+ "#a59f85",
+ "#f8f8f2",
+ "#f5f4f1",
+ "#f9f8f5",
+ ]),
+ red: colorRamp(chroma("#f92672")),
+ orange: colorRamp(chroma("#fd971f")),
+ yellow: colorRamp(chroma("#f4bf75")),
+ green: colorRamp(chroma("#a6e22e")),
+ cyan: colorRamp(chroma("#a1efe4")),
+ blue: colorRamp(chroma("#66d9ef")),
+ violet: colorRamp(chroma("#ae81ff")),
+ magenta: colorRamp(chroma("#cc6633")),
+})
@@ -1,32 +1,32 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Nord";
-const author = "arcticicestudio";
-const url = "https://www.nordtheme.com/";
+const name = "Nord"
+const author = "arcticicestudio"
+const url = "https://www.nordtheme.com/"
const license = {
- type: "MIT",
- url: "https://github.com/arcticicestudio/nord/blob/develop/LICENSE.md",
-};
+ type: "MIT",
+ url: "https://github.com/arcticicestudio/nord/blob/develop/LICENSE.md",
+}
// `name-[light|dark]`, isLight, color ramps
export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma.scale([
- "#2E3440",
- "#3B4252",
- "#434C5E",
- "#4C566A",
- "#D8DEE9",
- "#E5E9F0",
- "#ECEFF4",
- "#8FBCBB",
- ]),
- red: colorRamp(chroma("#88C0D0")),
- orange: colorRamp(chroma("#81A1C1")),
- yellow: colorRamp(chroma("#5E81AC")),
- green: colorRamp(chroma("#BF616A")),
- cyan: colorRamp(chroma("#D08770")),
- blue: colorRamp(chroma("#EBCB8B")),
- violet: colorRamp(chroma("#A3BE8C")),
- magenta: colorRamp(chroma("#B48EAD")),
-});
+ neutral: chroma.scale([
+ "#2E3440",
+ "#3B4252",
+ "#434C5E",
+ "#4C566A",
+ "#D8DEE9",
+ "#E5E9F0",
+ "#ECEFF4",
+ "#8FBCBB",
+ ]),
+ red: colorRamp(chroma("#88C0D0")),
+ orange: colorRamp(chroma("#81A1C1")),
+ yellow: colorRamp(chroma("#5E81AC")),
+ green: colorRamp(chroma("#BF616A")),
+ cyan: colorRamp(chroma("#D08770")),
+ blue: colorRamp(chroma("#EBCB8B")),
+ violet: colorRamp(chroma("#A3BE8C")),
+ magenta: colorRamp(chroma("#B48EAD")),
+})
@@ -1,32 +1,32 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Seti UI";
-const author = "jesseweed";
-const url = "https://github.com/jesseweed/seti-ui";
+const name = "Seti UI"
+const author = "jesseweed"
+const url = "https://github.com/jesseweed/seti-ui"
const license = {
- type: "MIT",
- url: "https://github.com/jesseweed/seti-ui/blob/master/LICENSE.md",
-};
+ type: "MIT",
+ url: "https://github.com/jesseweed/seti-ui/blob/master/LICENSE.md",
+}
// `name-[light|dark]`, isLight, color ramps
export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma.scale([
- "#151718",
- "#262B30",
- "#1E2326",
- "#41535B",
- "#43a5d5",
- "#d6d6d6",
- "#eeeeee",
- "#ffffff",
- ]),
- red: colorRamp(chroma("#Cd3f45")),
- orange: colorRamp(chroma("#db7b55")),
- yellow: colorRamp(chroma("#e6cd69")),
- green: colorRamp(chroma("#9fca56")),
- cyan: colorRamp(chroma("#55dbbe")),
- blue: colorRamp(chroma("#55b5db")),
- violet: colorRamp(chroma("#a074c4")),
- magenta: colorRamp(chroma("#8a553f")),
-});
+ neutral: chroma.scale([
+ "#151718",
+ "#262B30",
+ "#1E2326",
+ "#41535B",
+ "#43a5d5",
+ "#d6d6d6",
+ "#eeeeee",
+ "#ffffff",
+ ]),
+ red: colorRamp(chroma("#Cd3f45")),
+ orange: colorRamp(chroma("#db7b55")),
+ yellow: colorRamp(chroma("#e6cd69")),
+ green: colorRamp(chroma("#9fca56")),
+ cyan: colorRamp(chroma("#55dbbe")),
+ blue: colorRamp(chroma("#55b5db")),
+ violet: colorRamp(chroma("#a074c4")),
+ magenta: colorRamp(chroma("#8a553f")),
+})
@@ -1,32 +1,32 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Tokyo Night Storm";
-const author = "folke";
-const url = "https://github.com/folke/tokyonight.nvim";
+const name = "Tokyo Night Storm"
+const author = "folke"
+const url = "https://github.com/folke/tokyonight.nvim"
const license = {
- type: "MIT",
- url: "https://github.com/ghifarit53/tokyonight-vim/blob/master/LICENSE",
-};
+ type: "MIT",
+ url: "https://github.com/ghifarit53/tokyonight-vim/blob/master/LICENSE",
+}
// `name-[light|dark]`, isLight, color ramps
export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma.scale([
- "#24283B",
- "#16161E",
- "#343A52",
- "#444B6A",
- "#787C99",
- "#A9B1D6",
- "#CBCCD1",
- "#D5D6DB",
- ]),
- red: colorRamp(chroma("#C0CAF5")),
- orange: colorRamp(chroma("#A9B1D6")),
- yellow: colorRamp(chroma("#0DB9D7")),
- green: colorRamp(chroma("#9ECE6A")),
- cyan: colorRamp(chroma("#B4F9F8")),
- blue: colorRamp(chroma("#2AC3DE")),
- violet: colorRamp(chroma("#BB9AF7")),
- magenta: colorRamp(chroma("#F7768E")),
-});
+ neutral: chroma.scale([
+ "#24283B",
+ "#16161E",
+ "#343A52",
+ "#444B6A",
+ "#787C99",
+ "#A9B1D6",
+ "#CBCCD1",
+ "#D5D6DB",
+ ]),
+ red: colorRamp(chroma("#C0CAF5")),
+ orange: colorRamp(chroma("#A9B1D6")),
+ yellow: colorRamp(chroma("#0DB9D7")),
+ green: colorRamp(chroma("#9ECE6A")),
+ cyan: colorRamp(chroma("#B4F9F8")),
+ blue: colorRamp(chroma("#2AC3DE")),
+ violet: colorRamp(chroma("#BB9AF7")),
+ magenta: colorRamp(chroma("#F7768E")),
+})
@@ -1,53 +1,53 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Tokyo";
-const author = "folke";
-const url = "https://github.com/folke/tokyonight.nvim";
+const name = "Tokyo"
+const author = "folke"
+const url = "https://github.com/folke/tokyonight.nvim"
const license = {
- type: "Apache License 2.0",
- url: "https://github.com/folke/tokyonight.nvim/blob/main/LICENSE",
-};
+ type: "Apache License 2.0",
+ url: "https://github.com/folke/tokyonight.nvim/blob/main/LICENSE",
+}
// `name-[light|dark]`, isLight, color ramps
export const dark = createColorScheme(`${name} Night`, false, {
- neutral: chroma.scale([
- "#1A1B26",
- "#16161E",
- "#2F3549",
- "#444B6A",
- "#787C99",
- "#A9B1D6",
- "#CBCCD1",
- "#D5D6DB",
- ]),
- red: colorRamp(chroma("#C0CAF5")),
- orange: colorRamp(chroma("#A9B1D6")),
- yellow: colorRamp(chroma("#0DB9D7")),
- green: colorRamp(chroma("#9ECE6A")),
- cyan: colorRamp(chroma("#B4F9F8")),
- blue: colorRamp(chroma("#2AC3DE")),
- violet: colorRamp(chroma("#BB9AF7")),
- magenta: colorRamp(chroma("#F7768E")),
-});
+ neutral: chroma.scale([
+ "#1A1B26",
+ "#16161E",
+ "#2F3549",
+ "#444B6A",
+ "#787C99",
+ "#A9B1D6",
+ "#CBCCD1",
+ "#D5D6DB",
+ ]),
+ red: colorRamp(chroma("#C0CAF5")),
+ orange: colorRamp(chroma("#A9B1D6")),
+ yellow: colorRamp(chroma("#0DB9D7")),
+ green: colorRamp(chroma("#9ECE6A")),
+ cyan: colorRamp(chroma("#B4F9F8")),
+ blue: colorRamp(chroma("#2AC3DE")),
+ violet: colorRamp(chroma("#BB9AF7")),
+ magenta: colorRamp(chroma("#F7768E")),
+})
export const light = createColorScheme(`${name} Day`, true, {
- neutral: chroma.scale([
- "#1A1B26",
- "#1A1B26",
- "#343B59",
- "#4C505E",
- "#9699A3",
- "#DFE0E5",
- "#CBCCD1",
- "#D5D6DB",
- ]),
- red: colorRamp(chroma("#343B58")),
- orange: colorRamp(chroma("#965027")),
- yellow: colorRamp(chroma("#166775")),
- green: colorRamp(chroma("#485E30")),
- cyan: colorRamp(chroma("#3E6968")),
- blue: colorRamp(chroma("#34548A")),
- violet: colorRamp(chroma("#5A4A78")),
- magenta: colorRamp(chroma("#8C4351")),
-});
+ neutral: chroma.scale([
+ "#1A1B26",
+ "#1A1B26",
+ "#343B59",
+ "#4C505E",
+ "#9699A3",
+ "#DFE0E5",
+ "#CBCCD1",
+ "#D5D6DB",
+ ]),
+ red: colorRamp(chroma("#343B58")),
+ orange: colorRamp(chroma("#965027")),
+ yellow: colorRamp(chroma("#166775")),
+ green: colorRamp(chroma("#485E30")),
+ cyan: colorRamp(chroma("#3E6968")),
+ blue: colorRamp(chroma("#34548A")),
+ violet: colorRamp(chroma("#5A4A78")),
+ magenta: colorRamp(chroma("#8C4351")),
+})
@@ -1,36 +1,36 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Zed Pro";
+const name = "Zed Pro"
const author = "Nate Butler"
const url = "https://github.com/iamnbutler"
const license = {
- type: "?",
- url: "?",
-};
+ type: "?",
+ url: "?",
+}
const ramps = {
- neutral: chroma
- .scale([
- "#101010",
- "#1C1C1C",
- "#212121",
- "#2D2D2D",
- "#B9B9B9",
- "#DADADA",
- "#E6E6E6",
- "#FFFFFF",
- ])
- .domain([0, 0.1, 0.2, 0.3, 0.7, 0.8, 0.9, 1]),
- red: colorRamp(chroma("#DC604F")),
- orange: colorRamp(chroma("#DE782F")),
- yellow: colorRamp(chroma("#E0B750")),
- green: colorRamp(chroma("#2A643D")),
- cyan: colorRamp(chroma("#215050")),
- blue: colorRamp(chroma("#2F6DB7")),
- violet: colorRamp(chroma("#5874C1")),
- magenta: colorRamp(chroma("#DE9AB8")),
-};
+ neutral: chroma
+ .scale([
+ "#101010",
+ "#1C1C1C",
+ "#212121",
+ "#2D2D2D",
+ "#B9B9B9",
+ "#DADADA",
+ "#E6E6E6",
+ "#FFFFFF",
+ ])
+ .domain([0, 0.1, 0.2, 0.3, 0.7, 0.8, 0.9, 1]),
+ red: colorRamp(chroma("#DC604F")),
+ orange: colorRamp(chroma("#DE782F")),
+ yellow: colorRamp(chroma("#E0B750")),
+ green: colorRamp(chroma("#2A643D")),
+ cyan: colorRamp(chroma("#215050")),
+ blue: colorRamp(chroma("#2F6DB7")),
+ violet: colorRamp(chroma("#5874C1")),
+ magenta: colorRamp(chroma("#DE9AB8")),
+}
-export const dark = createColorScheme(`${name} Dark`, false, ramps);
-export const light = createColorScheme(`${name} Light`, true, ramps);
+export const dark = createColorScheme(`${name} Dark`, false, ramps)
+export const light = createColorScheme(`${name} Light`, true, ramps)
@@ -1,32 +1,32 @@
-import chroma from "chroma-js";
-import { colorRamp, createColorScheme } from "../common/ramps";
+import chroma from "chroma-js"
+import { colorRamp, createColorScheme } from "../common/ramps"
-const name = "Zenburn";
-const author = "elnawe";
-const url = "https://github.com/elnawe/base16-zenburn-scheme";
+const name = "Zenburn"
+const author = "elnawe"
+const url = "https://github.com/elnawe/base16-zenburn-scheme"
const license = {
- type: "None",
- url: "",
-};
+ type: "None",
+ url: "",
+}
// `name-[light|dark]`, isLight, color ramps
export const dark = createColorScheme(`${name}`, false, {
- neutral: chroma.scale([
- "#383838",
- "#404040",
- "#606060",
- "#6f6f6f",
- "#808080",
- "#dcdccc",
- "#c0c0c0",
- "#ffffff",
- ]),
- red: colorRamp(chroma("#dca3a3")),
- orange: colorRamp(chroma("#dfaf8f")),
- yellow: colorRamp(chroma("#e0cf9f")),
- green: colorRamp(chroma("#5f7f5f")),
- cyan: colorRamp(chroma("#93e0e3")),
- blue: colorRamp(chroma("#7cb8bb")),
- violet: colorRamp(chroma("#dc8cc3")),
- magenta: colorRamp(chroma("#000000")),
-});
+ neutral: chroma.scale([
+ "#383838",
+ "#404040",
+ "#606060",
+ "#6f6f6f",
+ "#808080",
+ "#dcdccc",
+ "#c0c0c0",
+ "#ffffff",
+ ]),
+ red: colorRamp(chroma("#dca3a3")),
+ orange: colorRamp(chroma("#dfaf8f")),
+ yellow: colorRamp(chroma("#e0cf9f")),
+ green: colorRamp(chroma("#5f7f5f")),
+ cyan: colorRamp(chroma("#93e0e3")),
+ blue: colorRamp(chroma("#7cb8bb")),
+ violet: colorRamp(chroma("#dc8cc3")),
+ magenta: colorRamp(chroma("#000000")),
+})
@@ -1,40 +1,42 @@
-import chroma from "chroma-js";
-import { Meta } from "./common/colorScheme";
-import { colorRamp, createColorScheme } from "./common/ramps";
+import chroma from "chroma-js"
+import { Meta } from "./common/colorScheme"
+import { colorRamp, createColorScheme } from "./common/ramps"
-const name = "Summercamp";
+const name = "Summercamp"
const ramps = {
- neutral: chroma
- .scale([
- "#1c1810",
- "#2a261c",
- "#3a3527",
- "#3a3527",
- "#5f5b45",
- "#736e55",
- "#bab696",
- "#f8f5de",
- ])
- .domain([0, 0.2, 0.38, 0.4, 0.65, 0.7, 0.85, 1]),
- red: colorRamp(chroma("#e35142")),
- orange: colorRamp(chroma("#fba11b")),
- yellow: colorRamp(chroma("#f2ff27")),
- green: colorRamp(chroma("#5ceb5a")),
- cyan: colorRamp(chroma("#5aebbc")),
- blue: colorRamp(chroma("#489bf0")),
- violet: colorRamp(chroma("#FF8080")),
- magenta: colorRamp(chroma("#F69BE7")),
-};
+ neutral: chroma
+ .scale([
+ "#1c1810",
+ "#2a261c",
+ "#3a3527",
+ "#3a3527",
+ "#5f5b45",
+ "#736e55",
+ "#bab696",
+ "#f8f5de",
+ ])
+ .domain([0, 0.2, 0.38, 0.4, 0.65, 0.7, 0.85, 1]),
+ red: colorRamp(chroma("#e35142")),
+ orange: colorRamp(chroma("#fba11b")),
+ yellow: colorRamp(chroma("#f2ff27")),
+ green: colorRamp(chroma("#5ceb5a")),
+ cyan: colorRamp(chroma("#5aebbc")),
+ blue: colorRamp(chroma("#489bf0")),
+ violet: colorRamp(chroma("#FF8080")),
+ magenta: colorRamp(chroma("#F69BE7")),
+}
-export const dark = createColorScheme(`${name}`, false, ramps);
+export const dark = createColorScheme(`${name}`, false, ramps)
export const meta: Meta = {
- name,
- author: "zoefiri",
- url: "https://github.com/zoefiri/base16-sc",
- license: {
- SPDX: "MIT",
- https_url: "https://raw.githubusercontent.com/zoefiri/base16-sc/master/LICENSE",
- license_checksum: "fadcc834b7eaf2943800956600e8aeea4b495ecf6490f4c4b6c91556a90accaf"
- }
-}
+ name,
+ author: "zoefiri",
+ url: "https://github.com/zoefiri/base16-sc",
+ license: {
+ SPDX: "MIT",
+ https_url:
+ "https://raw.githubusercontent.com/zoefiri/base16-sc/master/LICENSE",
+ license_checksum:
+ "fadcc834b7eaf2943800956600e8aeea4b495ecf6490f4c4b6c91556a90accaf",
+ },
+}
@@ -1,5 +1,5 @@
-import chroma from "chroma-js";
+import chroma from "chroma-js"
export function withOpacity(color: string, opacity: number): string {
- return chroma(color).alpha(opacity).hex();
+ return chroma(color).alpha(opacity).hex()
}
@@ -1,35 +1,35 @@
-import { snakeCase } from "case-anything";
+import { snakeCase } from "case-anything"
// https://stackoverflow.com/questions/60269936/typescript-convert-generic-object-from-snake-to-camel-case
// Typescript magic to convert any string from camelCase to snake_case at compile time
type SnakeCase<S> = S extends string
- ? S extends `${infer T}${infer U}`
- ? `${T extends Capitalize<T> ? "_" : ""}${Lowercase<T>}${SnakeCase<U>}`
+ ? S extends `${infer T}${infer U}`
+ ? `${T extends Capitalize<T> ? "_" : ""}${Lowercase<T>}${SnakeCase<U>}`
+ : S
: S
- : S;
type SnakeCased<Type> = {
- [Property in keyof Type as SnakeCase<Property>]: SnakeCased<Type[Property]>;
-};
+ [Property in keyof Type as SnakeCase<Property>]: SnakeCased<Type[Property]>
+}
export default function snakeCaseTree<T>(object: T): SnakeCased<T> {
- const snakeObject: any = {};
- for (const key in object) {
- snakeObject[snakeCase(key, { keepSpecialCharacters: true })] =
- snakeCaseValue(object[key]);
- }
- return snakeObject;
+ const snakeObject: any = {}
+ for (const key in object) {
+ snakeObject[snakeCase(key, { keepSpecialCharacters: true })] =
+ snakeCaseValue(object[key])
+ }
+ return snakeObject
}
function snakeCaseValue(value: any): any {
- if (typeof value === "object") {
- if (Array.isArray(value)) {
- return value.map(snakeCaseValue);
+ if (typeof value === "object") {
+ if (Array.isArray(value)) {
+ return value.map(snakeCaseValue)
+ } else {
+ return snakeCaseTree(value)
+ }
} else {
- return snakeCaseTree(value);
+ return value
}
- } else {
- return value;
- }
}
@@ -1,12 +1,12 @@
{
- "compilerOptions": {
- "target": "es2015",
- "module": "commonjs",
- "esModuleInterop": true,
- "noImplicitAny": true,
- "removeComments": true,
- "preserveConstEnums": true,
- "sourceMap": true
- },
- "exclude": ["node_modules"]
+ "compilerOptions": {
+ "target": "es2015",
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "noImplicitAny": true,
+ "removeComments": true,
+ "preserveConstEnums": true,
+ "sourceMap": true
+ },
+ "exclude": ["node_modules"]
}