From 08e9b2e8481bb87f3ae76843d1f24f4d5a29f0bc Mon Sep 17 00:00:00 2001 From: KCaverly Date: Mon, 30 Oct 2023 13:32:47 -0400 Subject: [PATCH 01/15] added parsing support for <|S| |E|> spans --- crates/assistant/src/codegen.rs | 110 +++++++++++++++++++++++++------- crates/assistant/src/prompts.rs | 18 +++--- 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index b6ef6b5cfa..b26b1713b2 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -117,7 +117,7 @@ impl Codegen { let (mut hunks_tx, mut hunks_rx) = mpsc::channel(1); let diff = cx.background().spawn(async move { - let chunks = strip_markdown_codeblock(response.await?); + let chunks = strip_invalid_spans_from_codeblock(response.await?); futures::pin_mut!(chunks); let mut diff = StreamingDiff::new(selected_text.to_string()); @@ -278,12 +278,13 @@ impl Codegen { } } -fn strip_markdown_codeblock( +fn strip_invalid_spans_from_codeblock( stream: impl Stream>, ) -> impl Stream> { let mut first_line = true; let mut buffer = String::new(); - let mut starts_with_fenced_code_block = false; + let mut starts_with_markdown_codeblock = false; + let mut includes_start_or_end_span = false; stream.filter_map(move |chunk| { let chunk = match chunk { Ok(chunk) => chunk, @@ -291,11 +292,31 @@ fn strip_markdown_codeblock( }; buffer.push_str(&chunk); + if buffer.len() > "<|S|".len() && buffer.starts_with("<|S|") { + includes_start_or_end_span = true; + + buffer = buffer + .strip_prefix("<|S|>") + .or_else(|| buffer.strip_prefix("<|S|")) + .unwrap_or(&buffer) + .to_string(); + } else if buffer.ends_with("|E|>") { + includes_start_or_end_span = true; + } else if buffer.starts_with("<|") + || buffer.starts_with("<|S") + || buffer.starts_with("<|S|") + || buffer.ends_with("|") + || buffer.ends_with("|E") + || buffer.ends_with("|E|") + { + return future::ready(None); + } + if first_line { if buffer == "" || buffer == "`" || buffer == "``" { return future::ready(None); } else if buffer.starts_with("```") { - starts_with_fenced_code_block = true; + starts_with_markdown_codeblock = true; if let Some(newline_ix) = buffer.find('\n') { buffer.replace_range(..newline_ix + 1, ""); first_line = false; @@ -305,16 +326,26 @@ fn strip_markdown_codeblock( } } - let text = if starts_with_fenced_code_block { - buffer + let mut text = buffer.to_string(); + if starts_with_markdown_codeblock { + text = text .strip_suffix("\n```\n") - .or_else(|| buffer.strip_suffix("\n```")) - .or_else(|| buffer.strip_suffix("\n``")) - .or_else(|| buffer.strip_suffix("\n`")) - .or_else(|| buffer.strip_suffix('\n')) - .unwrap_or(&buffer) - } else { - &buffer + .or_else(|| text.strip_suffix("\n```")) + .or_else(|| text.strip_suffix("\n``")) + .or_else(|| text.strip_suffix("\n`")) + .or_else(|| text.strip_suffix('\n')) + .unwrap_or(&text) + .to_string(); + } + + if includes_start_or_end_span { + text = text + .strip_suffix("|E|>") + .or_else(|| text.strip_suffix("E|>")) + .or_else(|| text.strip_prefix("|>")) + .or_else(|| text.strip_prefix(">")) + .unwrap_or(&text) + .to_string(); }; if text.contains('\n') { @@ -327,6 +358,7 @@ fn strip_markdown_codeblock( } else { Some(Ok(buffer.clone())) }; + buffer = remainder; future::ready(result) }) @@ -537,50 +569,82 @@ mod tests { } #[gpui::test] - async fn test_strip_markdown_codeblock() { + async fn test_strip_invalid_spans_from_codeblock() { assert_eq!( - strip_markdown_codeblock(chunks("Lorem ipsum dolor", 2)) + strip_invalid_spans_from_codeblock(chunks("Lorem ipsum dolor", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```\nLorem ipsum dolor", 2)) + strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```\nLorem ipsum dolor\n```", 2)) + strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2)) + strip_invalid_spans_from_codeblock(chunks("```\nLorem ipsum dolor\n```\n", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "Lorem ipsum dolor" ); assert_eq!( - strip_markdown_codeblock(chunks("```html\n```js\nLorem ipsum dolor\n```\n```", 2)) - .map(|chunk| chunk.unwrap()) - .collect::() - .await, + strip_invalid_spans_from_codeblock(chunks( + "```html\n```js\nLorem ipsum dolor\n```\n```", + 2 + )) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, "```js\nLorem ipsum dolor\n```" ); assert_eq!( - strip_markdown_codeblock(chunks("``\nLorem ipsum dolor\n```", 2)) + strip_invalid_spans_from_codeblock(chunks("``\nLorem ipsum dolor\n```", 2)) .map(|chunk| chunk.unwrap()) .collect::() .await, "``\nLorem ipsum dolor\n```" ); + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("<|S|Lorem ipsum|E|>", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("<|S|>Lorem ipsum", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); + + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("```\n<|S|>Lorem ipsum\n```", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); + assert_eq!( + strip_invalid_spans_from_codeblock(chunks("```\n<|S|Lorem ipsum|E|>\n```", 2)) + .map(|chunk| chunk.unwrap()) + .collect::() + .await, + "Lorem ipsum" + ); fn chunks(text: &str, size: usize) -> impl Stream> { stream::iter( text.chars() diff --git a/crates/assistant/src/prompts.rs b/crates/assistant/src/prompts.rs index dffcbc2923..66425d31f7 100644 --- a/crates/assistant/src/prompts.rs +++ b/crates/assistant/src/prompts.rs @@ -79,12 +79,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> S if !flushed_selection { // The collapsed node ends after the selection starts, so we'll flush the selection first. summary.extend(buffer.text_for_range(offset..selected_range.start)); - summary.push_str("<|START|"); + summary.push_str("<|S|"); if selected_range.end == selected_range.start { summary.push_str(">"); } else { summary.extend(buffer.text_for_range(selected_range.clone())); - summary.push_str("|END|>"); + summary.push_str("|E|>"); } offset = selected_range.end; flushed_selection = true; @@ -106,12 +106,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range) -> S // Flush selection if we haven't already done so. if !flushed_selection && offset <= selected_range.start { summary.extend(buffer.text_for_range(offset..selected_range.start)); - summary.push_str("<|START|"); + summary.push_str("<|S|"); if selected_range.end == selected_range.start { summary.push_str(">"); } else { summary.extend(buffer.text_for_range(selected_range.clone())); - summary.push_str("|END|>"); + summary.push_str("|E|>"); } offset = selected_range.end; } @@ -259,7 +259,7 @@ pub(crate) mod tests { summarize(&snapshot, Point::new(1, 4)..Point::new(1, 4)), indoc! {" struct X { - <|START|>a: usize, + <|S|>a: usize, b: usize, } @@ -285,7 +285,7 @@ pub(crate) mod tests { impl X { fn new() -> Self { - let <|START|a |END|>= 1; + let <|S|a |E|>= 1; let b = 2; Self { a, b } } @@ -306,7 +306,7 @@ pub(crate) mod tests { } impl X { - <|START|> + <|S|> fn new() -> Self {} pub fn a(&self, param: bool) -> usize {} @@ -332,7 +332,7 @@ pub(crate) mod tests { pub fn b(&self) -> usize {} } - <|START|>"} + <|S|>"} ); // Ensure nested functions get collapsed properly. @@ -368,7 +368,7 @@ pub(crate) mod tests { assert_eq!( summarize(&snapshot, Point::new(0, 0)..Point::new(0, 0)), indoc! {" - <|START|>struct X { + <|S|>struct X { a: usize, b: usize, } From 244e8ce101cedb56d8c073c5acff99b0cf344b70 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 31 Oct 2023 14:04:03 -0700 Subject: [PATCH 02/15] WIP - make livekit work in GPUI2 --- Cargo.lock | 35 +- crates/call2/Cargo.toml | 6 +- crates/call2/src/participant.rs | 4 +- crates/call2/src/room.rs | 225 +++-- .../LiveKitBridge/Package.resolved | 4 +- crates/live_kit_client2/.cargo/config.toml | 2 + crates/live_kit_client2/Cargo.toml | 71 ++ .../LiveKitBridge/Package.resolved | 52 + .../LiveKitBridge/Package.swift | 27 + .../live_kit_client2/LiveKitBridge/README.md | 3 + .../Sources/LiveKitBridge/LiveKitBridge.swift | 327 ++++++ crates/live_kit_client2/build.rs | 172 ++++ crates/live_kit_client2/examples/test_app.rs | 175 ++++ .../live_kit_client2/src/live_kit_client2.rs | 11 + crates/live_kit_client2/src/prod.rs | 943 ++++++++++++++++++ crates/live_kit_client2/src/test.rs | 647 ++++++++++++ 16 files changed, 2586 insertions(+), 118 deletions(-) create mode 100644 crates/live_kit_client2/.cargo/config.toml create mode 100644 crates/live_kit_client2/Cargo.toml create mode 100644 crates/live_kit_client2/LiveKitBridge/Package.resolved create mode 100644 crates/live_kit_client2/LiveKitBridge/Package.swift create mode 100644 crates/live_kit_client2/LiveKitBridge/README.md create mode 100644 crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift create mode 100644 crates/live_kit_client2/build.rs create mode 100644 crates/live_kit_client2/examples/test_app.rs create mode 100644 crates/live_kit_client2/src/live_kit_client2.rs create mode 100644 crates/live_kit_client2/src/prod.rs create mode 100644 crates/live_kit_client2/src/test.rs diff --git a/Cargo.lock b/Cargo.lock index 3aca27106c..bb72f5d6ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1169,7 +1169,7 @@ dependencies = [ "futures 0.3.28", "gpui2", "language2", - "live_kit_client", + "live_kit_client2", "log", "media", "postage", @@ -4589,6 +4589,39 @@ dependencies = [ "simplelog", ] +[[package]] +name = "live_kit_client2" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-broadcast", + "async-trait", + "block", + "byteorder", + "bytes 1.5.0", + "cocoa", + "collections", + "core-foundation", + "core-graphics", + "foreign-types", + "futures 0.3.28", + "gpui2", + "hmac 0.12.1", + "jwt", + "live_kit_server", + "log", + "media", + "nanoid", + "objc", + "parking_lot 0.11.2", + "postage", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.7", + "simplelog", +] + [[package]] name = "live_kit_server" version = "0.1.0" diff --git a/crates/call2/Cargo.toml b/crates/call2/Cargo.toml index f0e47832ed..e918ada3e8 100644 --- a/crates/call2/Cargo.toml +++ b/crates/call2/Cargo.toml @@ -13,7 +13,7 @@ test-support = [ "client2/test-support", "collections/test-support", "gpui2/test-support", - "live_kit_client/test-support", + "live_kit_client2/test-support", "project2/test-support", "util/test-support" ] @@ -24,7 +24,7 @@ client2 = { path = "../client2" } collections = { path = "../collections" } gpui2 = { path = "../gpui2" } log.workspace = true -live_kit_client = { path = "../live_kit_client" } +live_kit_client2 = { path = "../live_kit_client2" } fs2 = { path = "../fs2" } language2 = { path = "../language2" } media = { path = "../media" } @@ -47,6 +47,6 @@ fs2 = { path = "../fs2", features = ["test-support"] } language2 = { path = "../language2", features = ["test-support"] } collections = { path = "../collections", features = ["test-support"] } gpui2 = { path = "../gpui2", features = ["test-support"] } -live_kit_client = { path = "../live_kit_client", features = ["test-support"] } +live_kit_client2 = { path = "../live_kit_client2", features = ["test-support"] } project2 = { path = "../project2", features = ["test-support"] } util = { path = "../util", features = ["test-support"] } diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index 7f3e91dbba..a1837e3ad0 100644 --- a/crates/call2/src/participant.rs +++ b/crates/call2/src/participant.rs @@ -2,7 +2,7 @@ use anyhow::{anyhow, Result}; use client2::ParticipantIndex; use client2::{proto, User}; use gpui2::WeakModel; -pub use live_kit_client::Frame; +pub use live_kit_client2::Frame; use project2::Project; use std::{fmt, sync::Arc}; @@ -51,7 +51,7 @@ pub struct RemoteParticipant { #[derive(Clone)] pub struct RemoteVideoTrack { - pub(crate) live_kit_track: Arc, + pub(crate) live_kit_track: Arc, } unsafe impl Send for RemoteVideoTrack {} diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index b7bac52a8b..f0e0b8de17 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -19,7 +19,7 @@ use gpui2::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language2::LanguageRegistry; -use live_kit_client::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate}; +use live_kit_client2::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate}; use postage::{sink::Sink, stream::Stream, watch}; use project2::Project; use settings2::Settings; @@ -59,7 +59,7 @@ pub enum Event { pub struct Room { id: u64, channel_id: Option, - // live_kit: Option, + live_kit: Option, status: RoomStatus, shared_projects: HashSet>, joined_projects: HashSet>, @@ -114,125 +114,130 @@ impl Room { user_store: Model, cx: &mut ModelContext, ) -> Self { - todo!() - // let _live_kit_room = if let Some(connection_info) = live_kit_connection_info { - // let room = live_kit_client::Room::new(); - // let mut status = room.status(); - // // Consume the initial status of the room. - // let _ = status.try_recv(); - // let _maintain_room = cx.spawn(|this, mut cx| async move { - // while let Some(status) = status.next().await { - // let this = if let Some(this) = this.upgrade() { - // this - // } else { - // break; - // }; + let live_kit_room = if let Some(connection_info) = live_kit_connection_info { + let room = live_kit_client2::Room::new(); + let mut status = room.status(); + // Consume the initial status of the room. + let _ = status.try_recv(); + let _maintain_room = cx.spawn(|this, mut cx| async move { + while let Some(status) = status.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; - // if status == live_kit_client::ConnectionState::Disconnected { - // this.update(&mut cx, |this, cx| this.leave(cx).log_err()) - // .ok(); - // break; - // } - // } - // }); + if status == live_kit_client2::ConnectionState::Disconnected { + this.update(&mut cx, |this, cx| this.leave(cx).log_err()) + .ok(); + break; + } + } + }); - // let mut track_video_changes = room.remote_video_track_updates(); - // let _maintain_video_tracks = cx.spawn(|this, mut cx| async move { - // while let Some(track_change) = track_video_changes.next().await { - // let this = if let Some(this) = this.upgrade() { - // this - // } else { - // break; - // }; + let _maintain_video_tracks = cx.spawn_on_main({ + let room = room.clone(); + move |this, mut cx| async move { + let mut track_video_changes = room.remote_video_track_updates(); + while let Some(track_change) = track_video_changes.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; - // this.update(&mut cx, |this, cx| { - // this.remote_video_track_updated(track_change, cx).log_err() - // }) - // .ok(); - // } - // }); + this.update(&mut cx, |this, cx| { + this.remote_video_track_updated(track_change, cx).log_err() + }) + .ok(); + } + } + }); - // let mut track_audio_changes = room.remote_audio_track_updates(); - // let _maintain_audio_tracks = cx.spawn(|this, mut cx| async move { - // while let Some(track_change) = track_audio_changes.next().await { - // let this = if let Some(this) = this.upgrade() { - // this - // } else { - // break; - // }; + let _maintain_audio_tracks = cx.spawn_on_main({ + let room = room.clone(); + |this, mut cx| async move { + let mut track_audio_changes = room.remote_audio_track_updates(); + while let Some(track_change) = track_audio_changes.next().await { + let this = if let Some(this) = this.upgrade() { + this + } else { + break; + }; - // this.update(&mut cx, |this, cx| { - // this.remote_audio_track_updated(track_change, cx).log_err() - // }) - // .ok(); - // } - // }); + this.update(&mut cx, |this, cx| { + this.remote_audio_track_updated(track_change, cx).log_err() + }) + .ok(); + } + } + }); - // let connect = room.connect(&connection_info.server_url, &connection_info.token); - // cx.spawn(|this, mut cx| async move { - // connect.await?; + let connect = room.connect(&connection_info.server_url, &connection_info.token); + cx.spawn(|this, mut cx| async move { + connect.await?; - // if !cx.update(|cx| Self::mute_on_join(cx))? { - // this.update(&mut cx, |this, cx| this.share_microphone(cx))? - // .await?; - // } + if !cx.update(|cx| Self::mute_on_join(cx))? { + this.update(&mut cx, |this, cx| this.share_microphone(cx))? + .await?; + } - // anyhow::Ok(()) - // }) - // .detach_and_log_err(cx); + anyhow::Ok(()) + }) + .detach_and_log_err(cx); - // Some(LiveKitRoom { - // room, - // screen_track: LocalTrack::None, - // microphone_track: LocalTrack::None, - // next_publish_id: 0, - // muted_by_user: false, - // deafened: false, - // speaking: false, - // _maintain_room, - // _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks], - // }) - // } else { - // None - // }; + Some(LiveKitRoom { + room, + screen_track: LocalTrack::None, + microphone_track: LocalTrack::None, + next_publish_id: 0, + muted_by_user: false, + deafened: false, + speaking: false, + _maintain_room, + _maintain_tracks: [_maintain_video_tracks, _maintain_audio_tracks], + }) + } else { + None + }; - // let maintain_connection = cx.spawn({ - // let client = client.clone(); - // move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err() - // }); + let maintain_connection = cx.spawn({ + let client = client.clone(); + move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err() + }); - // Audio::play_sound(Sound::Joined, cx); + Audio::play_sound(Sound::Joined, cx); - // let (room_update_completed_tx, room_update_completed_rx) = watch::channel(); + let (room_update_completed_tx, room_update_completed_rx) = watch::channel(); - // Self { - // id, - // channel_id, - // // live_kit: live_kit_room, - // status: RoomStatus::Online, - // shared_projects: Default::default(), - // joined_projects: Default::default(), - // participant_user_ids: Default::default(), - // local_participant: Default::default(), - // remote_participants: Default::default(), - // pending_participants: Default::default(), - // pending_call_count: 0, - // client_subscriptions: vec![ - // client.add_message_handler(cx.weak_handle(), Self::handle_room_updated) - // ], - // _subscriptions: vec![ - // cx.on_release(Self::released), - // cx.on_app_quit(Self::app_will_quit), - // ], - // leave_when_empty: false, - // pending_room_update: None, - // client, - // user_store, - // follows_by_leader_id_project_id: Default::default(), - // maintain_connection: Some(maintain_connection), - // room_update_completed_tx, - // room_update_completed_rx, - // } + Self { + id, + channel_id, + live_kit: live_kit_room, + status: RoomStatus::Online, + shared_projects: Default::default(), + joined_projects: Default::default(), + participant_user_ids: Default::default(), + local_participant: Default::default(), + remote_participants: Default::default(), + pending_participants: Default::default(), + pending_call_count: 0, + client_subscriptions: vec![ + client.add_message_handler(cx.weak_model(), Self::handle_room_updated) + ], + _subscriptions: vec![ + cx.on_release(Self::released), + cx.on_app_quit(Self::app_will_quit), + ], + leave_when_empty: false, + pending_room_update: None, + client, + user_store, + follows_by_leader_id_project_id: Default::default(), + maintain_connection: Some(maintain_connection), + room_update_completed_tx, + room_update_completed_rx, + } } pub(crate) fn create( @@ -1518,7 +1523,7 @@ impl Room { } #[cfg(any(test, feature = "test-support"))] - pub fn set_display_sources(&self, sources: Vec) { + pub fn set_display_sources(&self, sources: Vec) { todo!() // self.live_kit // .as_ref() @@ -1529,7 +1534,7 @@ impl Room { } struct LiveKitRoom { - room: Arc, + room: Arc, screen_track: LocalTrack, microphone_track: LocalTrack, /// Tracks whether we're currently in a muted state due to auto-mute from deafening or manual mute performed by user. diff --git a/crates/live_kit_client/LiveKitBridge/Package.resolved b/crates/live_kit_client/LiveKitBridge/Package.resolved index b925bc8f0d..85ae088565 100644 --- a/crates/live_kit_client/LiveKitBridge/Package.resolved +++ b/crates/live_kit_client/LiveKitBridge/Package.resolved @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/apple/swift-protobuf.git", "state": { "branch": null, - "revision": "ce20dc083ee485524b802669890291c0d8090170", - "version": "1.22.1" + "revision": "0af9125c4eae12a4973fb66574c53a54962a9e1e", + "version": "1.21.0" } } ] diff --git a/crates/live_kit_client2/.cargo/config.toml b/crates/live_kit_client2/.cargo/config.toml new file mode 100644 index 0000000000..b33fe211bd --- /dev/null +++ b/crates/live_kit_client2/.cargo/config.toml @@ -0,0 +1,2 @@ +[live_kit_client_test] +rustflags = ["-C", "link-args=-ObjC"] diff --git a/crates/live_kit_client2/Cargo.toml b/crates/live_kit_client2/Cargo.toml new file mode 100644 index 0000000000..b5b45a8d45 --- /dev/null +++ b/crates/live_kit_client2/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "live_kit_client2" +version = "0.1.0" +edition = "2021" +description = "Bindings to LiveKit Swift client SDK" +publish = false + +[lib] +path = "src/live_kit_client2.rs" +doctest = false + +[[example]] +name = "test_app" + +[features] +test-support = [ + "async-trait", + "collections/test-support", + "gpui2/test-support", + "live_kit_server", + "nanoid", +] + +[dependencies] +collections = { path = "../collections", optional = true } +gpui2 = { path = "../gpui2", optional = true } +live_kit_server = { path = "../live_kit_server", optional = true } +media = { path = "../media" } + +anyhow.workspace = true +async-broadcast = "0.4" +core-foundation = "0.9.3" +core-graphics = "0.22.3" +futures.workspace = true +log.workspace = true +parking_lot.workspace = true +postage.workspace = true + +async-trait = { workspace = true, optional = true } +nanoid = { version ="0.4", optional = true} + +[dev-dependencies] +collections = { path = "../collections", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +live_kit_server = { path = "../live_kit_server" } +media = { path = "../media" } +nanoid = "0.4" + +anyhow.workspace = true +async-trait.workspace = true +block = "0.1" +bytes = "1.2" +byteorder = "1.4" +cocoa = "0.24" +core-foundation = "0.9.3" +core-graphics = "0.22.3" +foreign-types = "0.3" +futures.workspace = true +hmac = "0.12" +jwt = "0.16" +objc = "0.2" +parking_lot.workspace = true +serde.workspace = true +serde_derive.workspace = true +sha2 = "0.10" +simplelog = "0.9" + +[build-dependencies] +serde.workspace = true +serde_derive.workspace = true +serde_json.workspace = true diff --git a/crates/live_kit_client2/LiveKitBridge/Package.resolved b/crates/live_kit_client2/LiveKitBridge/Package.resolved new file mode 100644 index 0000000000..b925bc8f0d --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/Package.resolved @@ -0,0 +1,52 @@ +{ + "object": { + "pins": [ + { + "package": "LiveKit", + "repositoryURL": "https://github.com/livekit/client-sdk-swift.git", + "state": { + "branch": null, + "revision": "7331b813a5ab8a95cfb81fb2b4ed10519428b9ff", + "version": "1.0.12" + } + }, + { + "package": "Promises", + "repositoryURL": "https://github.com/google/promises.git", + "state": { + "branch": null, + "revision": "ec957ccddbcc710ccc64c9dcbd4c7006fcf8b73a", + "version": "2.2.0" + } + }, + { + "package": "WebRTC", + "repositoryURL": "https://github.com/webrtc-sdk/Specs.git", + "state": { + "branch": null, + "revision": "2f6bab30c8df0fe59ab3e58bc99097f757f85f65", + "version": "104.5112.17" + } + }, + { + "package": "swift-log", + "repositoryURL": "https://github.com/apple/swift-log.git", + "state": { + "branch": null, + "revision": "32e8d724467f8fe623624570367e3d50c5638e46", + "version": "1.5.2" + } + }, + { + "package": "SwiftProtobuf", + "repositoryURL": "https://github.com/apple/swift-protobuf.git", + "state": { + "branch": null, + "revision": "ce20dc083ee485524b802669890291c0d8090170", + "version": "1.22.1" + } + } + ] + }, + "version": 1 +} diff --git a/crates/live_kit_client2/LiveKitBridge/Package.swift b/crates/live_kit_client2/LiveKitBridge/Package.swift new file mode 100644 index 0000000000..d7b5c271b9 --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/Package.swift @@ -0,0 +1,27 @@ +// swift-tools-version: 5.5 + +import PackageDescription + +let package = Package( + name: "LiveKitBridge", + platforms: [ + .macOS(.v10_15) + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "LiveKitBridge", + type: .static, + targets: ["LiveKitBridge"]), + ], + dependencies: [ + .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "LiveKitBridge", + dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]), + ] +) diff --git a/crates/live_kit_client2/LiveKitBridge/README.md b/crates/live_kit_client2/LiveKitBridge/README.md new file mode 100644 index 0000000000..b982c67286 --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/README.md @@ -0,0 +1,3 @@ +# LiveKitBridge + +A description of this package. diff --git a/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift new file mode 100644 index 0000000000..5f22acf581 --- /dev/null +++ b/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift @@ -0,0 +1,327 @@ +import Foundation +import LiveKit +import WebRTC +import ScreenCaptureKit + +class LKRoomDelegate: RoomDelegate { + var data: UnsafeRawPointer + var onDidDisconnect: @convention(c) (UnsafeRawPointer) -> Void + var onDidSubscribeToRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void + var onDidUnsubscribeFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + var onMuteChangedFromRemoteAudioTrack: @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void + var onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void + var onDidSubscribeToRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void + var onDidUnsubscribeFromRemoteVideoTrack: @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void + + init( + data: UnsafeRawPointer, + onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, + onActiveSpeakersChanged: @convention(c) (UnsafeRawPointer, CFArray) -> Void, + onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void) + { + self.data = data + self.onDidDisconnect = onDidDisconnect + self.onDidSubscribeToRemoteAudioTrack = onDidSubscribeToRemoteAudioTrack + self.onDidUnsubscribeFromRemoteAudioTrack = onDidUnsubscribeFromRemoteAudioTrack + self.onDidSubscribeToRemoteVideoTrack = onDidSubscribeToRemoteVideoTrack + self.onDidUnsubscribeFromRemoteVideoTrack = onDidUnsubscribeFromRemoteVideoTrack + self.onMuteChangedFromRemoteAudioTrack = onMuteChangedFromRemoteAudioTrack + self.onActiveSpeakersChanged = onActiveSpeakersChanged + } + + func room(_ room: Room, didUpdate connectionState: ConnectionState, oldValue: ConnectionState) { + if connectionState.isDisconnected { + self.onDidDisconnect(self.data) + } + } + + func room(_ room: Room, participant: RemoteParticipant, didSubscribe publication: RemoteTrackPublication, track: Track) { + if track.kind == .video { + self.onDidSubscribeToRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque()) + } else if track.kind == .audio { + self.onDidSubscribeToRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString, Unmanaged.passUnretained(track).toOpaque(), Unmanaged.passUnretained(publication).toOpaque()) + } + } + + func room(_ room: Room, participant: Participant, didUpdate publication: TrackPublication, muted: Bool) { + if publication.kind == .audio { + self.onMuteChangedFromRemoteAudioTrack(self.data, publication.sid as CFString, muted) + } + } + + func room(_ room: Room, didUpdate speakers: [Participant]) { + guard let speaker_ids = speakers.compactMap({ $0.identity as CFString }) as CFArray? else { return } + self.onActiveSpeakersChanged(self.data, speaker_ids) + } + + func room(_ room: Room, participant: RemoteParticipant, didUnsubscribe publication: RemoteTrackPublication, track: Track) { + if track.kind == .video { + self.onDidUnsubscribeFromRemoteVideoTrack(self.data, participant.identity as CFString, track.sid! as CFString) + } else if track.kind == .audio { + self.onDidUnsubscribeFromRemoteAudioTrack(self.data, participant.identity as CFString, track.sid! as CFString) + } + } +} + +class LKVideoRenderer: NSObject, VideoRenderer { + var data: UnsafeRawPointer + var onFrame: @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool + var onDrop: @convention(c) (UnsafeRawPointer) -> Void + var adaptiveStreamIsEnabled: Bool = false + var adaptiveStreamSize: CGSize = .zero + weak var track: VideoTrack? + + init(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) { + self.data = data + self.onFrame = onFrame + self.onDrop = onDrop + } + + deinit { + self.onDrop(self.data) + } + + func setSize(_ size: CGSize) { + } + + func renderFrame(_ frame: RTCVideoFrame?) { + let buffer = frame?.buffer as? RTCCVPixelBuffer + if let pixelBuffer = buffer?.pixelBuffer { + if !self.onFrame(self.data, pixelBuffer) { + DispatchQueue.main.async { + self.track?.remove(videoRenderer: self) + } + } + } + } +} + +@_cdecl("LKRoomDelegateCreate") +public func LKRoomDelegateCreate( + data: UnsafeRawPointer, + onDidDisconnect: @escaping @convention(c) (UnsafeRawPointer) -> Void, + onDidSubscribeToRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void, + onMuteChangedFromRemoteAudioTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, Bool) -> Void, + onActiveSpeakerChanged: @escaping @convention(c) (UnsafeRawPointer, CFArray) -> Void, + onDidSubscribeToRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString, UnsafeRawPointer) -> Void, + onDidUnsubscribeFromRemoteVideoTrack: @escaping @convention(c) (UnsafeRawPointer, CFString, CFString) -> Void +) -> UnsafeMutableRawPointer { + let delegate = LKRoomDelegate( + data: data, + onDidDisconnect: onDidDisconnect, + onDidSubscribeToRemoteAudioTrack: onDidSubscribeToRemoteAudioTrack, + onDidUnsubscribeFromRemoteAudioTrack: onDidUnsubscribeFromRemoteAudioTrack, + onMuteChangedFromRemoteAudioTrack: onMuteChangedFromRemoteAudioTrack, + onActiveSpeakersChanged: onActiveSpeakerChanged, + onDidSubscribeToRemoteVideoTrack: onDidSubscribeToRemoteVideoTrack, + onDidUnsubscribeFromRemoteVideoTrack: onDidUnsubscribeFromRemoteVideoTrack + ) + return Unmanaged.passRetained(delegate).toOpaque() +} + +@_cdecl("LKRoomCreate") +public func LKRoomCreate(delegate: UnsafeRawPointer) -> UnsafeMutableRawPointer { + let delegate = Unmanaged.fromOpaque(delegate).takeUnretainedValue() + return Unmanaged.passRetained(Room(delegate: delegate)).toOpaque() +} + +@_cdecl("LKRoomConnect") +public func LKRoomConnect(room: UnsafeRawPointer, url: CFString, token: CFString, callback: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + room.connect(url as String, token as String).then { _ in + callback(callback_data, UnsafeRawPointer(nil) as! CFString?) + }.catch { error in + callback(callback_data, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRoomDisconnect") +public func LKRoomDisconnect(room: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + room.disconnect() +} + +@_cdecl("LKRoomPublishVideoTrack") +public func LKRoomPublishVideoTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + room.localParticipant?.publishVideoTrack(track: track).then { publication in + callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) + }.catch { error in + callback(callback_data, nil, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRoomPublishAudioTrack") +public func LKRoomPublishAudioTrack(room: UnsafeRawPointer, track: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, UnsafeMutableRawPointer?, CFString?) -> Void, callback_data: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + room.localParticipant?.publishAudioTrack(track: track).then { publication in + callback(callback_data, Unmanaged.passRetained(publication).toOpaque(), nil) + }.catch { error in + callback(callback_data, nil, error.localizedDescription as CFString) + } +} + + +@_cdecl("LKRoomUnpublishTrack") +public func LKRoomUnpublishTrack(room: UnsafeRawPointer, publication: UnsafeRawPointer) { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + let _ = room.localParticipant?.unpublish(publication: publication) +} + +@_cdecl("LKRoomAudioTracksForRemoteParticipant") +public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.audioTracks.compactMap { $0.track as? RemoteAudioTrack } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKRoomAudioTrackPublicationsForRemoteParticipant") +public func LKRoomAudioTrackPublicationsForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.audioTracks.compactMap { $0 as? RemoteTrackPublication } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKRoomVideoTracksForRemoteParticipant") +public func LKRoomVideoTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? { + let room = Unmanaged.fromOpaque(room).takeUnretainedValue() + + for (_, participant) in room.remoteParticipants { + if participant.identity == participantId as String { + return participant.videoTracks.compactMap { $0.track as? RemoteVideoTrack } as CFArray? + } + } + + return nil; +} + +@_cdecl("LKLocalAudioTrackCreateTrack") +public func LKLocalAudioTrackCreateTrack() -> UnsafeMutableRawPointer { + let track = LocalAudioTrack.createTrack(options: AudioCaptureOptions( + echoCancellation: true, + noiseSuppression: true + )) + + return Unmanaged.passRetained(track).toOpaque() +} + + +@_cdecl("LKCreateScreenShareTrackForDisplay") +public func LKCreateScreenShareTrackForDisplay(display: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer { + let display = Unmanaged.fromOpaque(display).takeUnretainedValue() + let track = LocalVideoTrack.createMacOSScreenShareTrack(source: display, preferredMethod: .legacy) + return Unmanaged.passRetained(track).toOpaque() +} + +@_cdecl("LKVideoRendererCreate") +public func LKVideoRendererCreate(data: UnsafeRawPointer, onFrame: @escaping @convention(c) (UnsafeRawPointer, CVPixelBuffer) -> Bool, onDrop: @escaping @convention(c) (UnsafeRawPointer) -> Void) -> UnsafeMutableRawPointer { + Unmanaged.passRetained(LKVideoRenderer(data: data, onFrame: onFrame, onDrop: onDrop)).toOpaque() +} + +@_cdecl("LKVideoTrackAddRenderer") +public func LKVideoTrackAddRenderer(track: UnsafeRawPointer, renderer: UnsafeRawPointer) { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() as! VideoTrack + let renderer = Unmanaged.fromOpaque(renderer).takeRetainedValue() + renderer.track = track + track.add(videoRenderer: renderer) +} + +@_cdecl("LKRemoteVideoTrackGetSid") +public func LKRemoteVideoTrackGetSid(track: UnsafeRawPointer) -> CFString { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + return track.sid! as CFString +} + +@_cdecl("LKRemoteAudioTrackGetSid") +public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString { + let track = Unmanaged.fromOpaque(track).takeUnretainedValue() + return track.sid! as CFString +} + +@_cdecl("LKDisplaySources") +public func LKDisplaySources(data: UnsafeRawPointer, callback: @escaping @convention(c) (UnsafeRawPointer, CFArray?, CFString?) -> Void) { + MacOSScreenCapturer.sources(for: .display, includeCurrentApplication: false, preferredMethod: .legacy).then { displaySources in + callback(data, displaySources as CFArray, nil) + }.catch { error in + callback(data, nil, error.localizedDescription as CFString) + } +} + +@_cdecl("LKLocalTrackPublicationSetMute") +public func LKLocalTrackPublicationSetMute( + publication: UnsafeRawPointer, + muted: Bool, + on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, + callback_data: UnsafeRawPointer +) { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + if muted { + publication.mute().then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } + } else { + publication.unmute().then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } + } +} + +@_cdecl("LKRemoteTrackPublicationSetEnabled") +public func LKRemoteTrackPublicationSetEnabled( + publication: UnsafeRawPointer, + enabled: Bool, + on_complete: @escaping @convention(c) (UnsafeRawPointer, CFString?) -> Void, + callback_data: UnsafeRawPointer +) { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + publication.set(enabled: enabled).then { + on_complete(callback_data, nil) + }.catch { error in + on_complete(callback_data, error.localizedDescription as CFString) + } +} + +@_cdecl("LKRemoteTrackPublicationIsMuted") +public func LKRemoteTrackPublicationIsMuted( + publication: UnsafeRawPointer +) -> Bool { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.muted +} + +@_cdecl("LKRemoteTrackPublicationGetSid") +public func LKRemoteTrackPublicationGetSid( + publication: UnsafeRawPointer +) -> CFString { + let publication = Unmanaged.fromOpaque(publication).takeUnretainedValue() + + return publication.sid as CFString +} diff --git a/crates/live_kit_client2/build.rs b/crates/live_kit_client2/build.rs new file mode 100644 index 0000000000..1445704b46 --- /dev/null +++ b/crates/live_kit_client2/build.rs @@ -0,0 +1,172 @@ +use serde::Deserialize; +use std::{ + env, + path::{Path, PathBuf}, + process::Command, +}; + +const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SwiftTargetInfo { + pub triple: String, + pub unversioned_triple: String, + pub module_triple: String, + pub swift_runtime_compatibility_version: String, + #[serde(rename = "librariesRequireRPath")] + pub libraries_require_rpath: bool, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SwiftPaths { + pub runtime_library_paths: Vec, + pub runtime_library_import_paths: Vec, + pub runtime_resource_path: String, +} + +#[derive(Debug, Deserialize)] +pub struct SwiftTarget { + pub target: SwiftTargetInfo, + pub paths: SwiftPaths, +} + +const MACOS_TARGET_VERSION: &str = "10.15.7"; + +fn main() { + if cfg!(not(any(test, feature = "test-support"))) { + let swift_target = get_swift_target(); + + build_bridge(&swift_target); + link_swift_stdlib(&swift_target); + link_webrtc_framework(&swift_target); + + // Register exported Objective-C selectors, protocols, etc when building example binaries. + println!("cargo:rustc-link-arg=-Wl,-ObjC"); + } +} + +fn build_bridge(swift_target: &SwiftTarget) { + println!("cargo:rerun-if-env-changed=MACOSX_DEPLOYMENT_TARGET"); + println!("cargo:rerun-if-changed={}/Sources", SWIFT_PACKAGE_NAME); + println!( + "cargo:rerun-if-changed={}/Package.swift", + SWIFT_PACKAGE_NAME + ); + println!( + "cargo:rerun-if-changed={}/Package.resolved", + SWIFT_PACKAGE_NAME + ); + + let swift_package_root = swift_package_root(); + let swift_target_folder = swift_target_folder(); + if !Command::new("swift") + .arg("build") + .arg("--disable-automatic-resolution") + .args(["--configuration", &env::var("PROFILE").unwrap()]) + .args(["--triple", &swift_target.target.triple]) + .args(["--build-path".into(), swift_target_folder]) + .current_dir(&swift_package_root) + .status() + .unwrap() + .success() + { + panic!( + "Failed to compile swift package in {}", + swift_package_root.display() + ); + } + + println!( + "cargo:rustc-link-search=native={}", + swift_target.out_dir_path().display() + ); + println!("cargo:rustc-link-lib=static={}", SWIFT_PACKAGE_NAME); +} + +fn link_swift_stdlib(swift_target: &SwiftTarget) { + for path in &swift_target.paths.runtime_library_paths { + println!("cargo:rustc-link-search=native={}", path); + } +} + +fn link_webrtc_framework(swift_target: &SwiftTarget) { + let swift_out_dir_path = swift_target.out_dir_path(); + println!("cargo:rustc-link-lib=framework=WebRTC"); + println!( + "cargo:rustc-link-search=framework={}", + swift_out_dir_path.display() + ); + // Find WebRTC.framework as a sibling of the executable when running tests. + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path"); + // Find WebRTC.framework in parent directory of the executable when running examples. + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/.."); + + let source_path = swift_out_dir_path.join("WebRTC.framework"); + let deps_dir_path = + PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../deps/WebRTC.framework"); + let target_dir_path = + PathBuf::from(env::var("OUT_DIR").unwrap()).join("../../../WebRTC.framework"); + copy_dir(&source_path, &deps_dir_path); + copy_dir(&source_path, &target_dir_path); +} + +fn get_swift_target() -> SwiftTarget { + let mut arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + if arch == "aarch64" { + arch = "arm64".into(); + } + let target = format!("{}-apple-macosx{}", arch, MACOS_TARGET_VERSION); + + let swift_target_info_str = Command::new("swift") + .args(["-target", &target, "-print-target-info"]) + .output() + .unwrap() + .stdout; + + serde_json::from_slice(&swift_target_info_str).unwrap() +} + +fn swift_package_root() -> PathBuf { + env::current_dir().unwrap().join(SWIFT_PACKAGE_NAME) +} + +fn swift_target_folder() -> PathBuf { + env::current_dir() + .unwrap() + .join(format!("../../target/{SWIFT_PACKAGE_NAME}")) +} + +fn copy_dir(source: &Path, destination: &Path) { + assert!( + Command::new("rm") + .arg("-rf") + .arg(destination) + .status() + .unwrap() + .success(), + "could not remove {:?} before copying", + destination + ); + + assert!( + Command::new("cp") + .arg("-R") + .args([source, destination]) + .status() + .unwrap() + .success(), + "could not copy {:?} to {:?}", + source, + destination + ); +} + +impl SwiftTarget { + fn out_dir_path(&self) -> PathBuf { + swift_target_folder() + .join(&self.target.unversioned_triple) + .join(env::var("PROFILE").unwrap()) + } +} diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs new file mode 100644 index 0000000000..2147f6ab8c --- /dev/null +++ b/crates/live_kit_client2/examples/test_app.rs @@ -0,0 +1,175 @@ +// use std::time::Duration; +// todo!() + +// use futures::StreamExt; +// use gpui2::{actions, keymap_matcher::Binding, Menu, MenuItem}; +// use live_kit_client2::{ +// LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, +// }; +// use live_kit_server::token::{self, VideoGrant}; +// use log::LevelFilter; +// use simplelog::SimpleLogger; + +// actions!(capture, [Quit]); + +fn main() { + // SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); + + // gpui2::App::new(()).unwrap().run(|cx| { + // #[cfg(any(test, feature = "test-support"))] + // println!("USING TEST LIVEKIT"); + + // #[cfg(not(any(test, feature = "test-support")))] + // println!("USING REAL LIVEKIT"); + + // cx.platform().activate(true); + // cx.add_global_action(quit); + + // cx.add_bindings([Binding::new("cmd-q", Quit, None)]); + // cx.set_menus(vec![Menu { + // name: "Zed", + // items: vec![MenuItem::Action { + // name: "Quit", + // action: Box::new(Quit), + // os_action: None, + // }], + // }]); + + // let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); + // let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); + // let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); + + // cx.spawn(|cx| async move { + // let user_a_token = token::create( + // &live_kit_key, + // &live_kit_secret, + // Some("test-participant-1"), + // VideoGrant::to_join("test-room"), + // ) + // .unwrap(); + // let room_a = Room::new(); + // room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); + + // let user2_token = token::create( + // &live_kit_key, + // &live_kit_secret, + // Some("test-participant-2"), + // VideoGrant::to_join("test-room"), + // ) + // .unwrap(); + // let room_b = Room::new(); + // room_b.connect(&live_kit_url, &user2_token).await.unwrap(); + + // let mut audio_track_updates = room_b.remote_audio_track_updates(); + // let audio_track = LocalAudioTrack::create(); + // let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); + + // if let RemoteAudioTrackUpdate::Subscribed(track, _) = + // audio_track_updates.next().await.unwrap() + // { + // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + // assert_eq!(remote_tracks.len(), 1); + // assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); + // assert_eq!(track.publisher_id(), "test-participant-1"); + // } else { + // panic!("unexpected message"); + // } + + // audio_track_publication.set_mute(true).await.unwrap(); + + // println!("waiting for mute changed!"); + // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + // audio_track_updates.next().await.unwrap() + // { + // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + // assert_eq!(remote_tracks[0].sid(), track_id); + // assert_eq!(muted, true); + // } else { + // panic!("unexpected message"); + // } + + // audio_track_publication.set_mute(false).await.unwrap(); + + // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + // audio_track_updates.next().await.unwrap() + // { + // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + // assert_eq!(remote_tracks[0].sid(), track_id); + // assert_eq!(muted, false); + // } else { + // panic!("unexpected message"); + // } + + // println!("Pausing for 5 seconds to test audio, make some noise!"); + // let timer = cx.background().timer(Duration::from_secs(5)); + // timer.await; + // let remote_audio_track = room_b + // .remote_audio_tracks("test-participant-1") + // .pop() + // .unwrap(); + // room_a.unpublish_track(audio_track_publication); + + // // Clear out any active speakers changed messages + // let mut next = audio_track_updates.next().await.unwrap(); + // while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { + // println!("Speakers changed: {:?}", speakers); + // next = audio_track_updates.next().await.unwrap(); + // } + + // if let RemoteAudioTrackUpdate::Unsubscribed { + // publisher_id, + // track_id, + // } = next + // { + // assert_eq!(publisher_id, "test-participant-1"); + // assert_eq!(remote_audio_track.sid(), track_id); + // assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); + // } else { + // panic!("unexpected message"); + // } + + // let mut video_track_updates = room_b.remote_video_track_updates(); + // let displays = room_a.display_sources().await.unwrap(); + // let display = displays.into_iter().next().unwrap(); + + // let local_video_track = LocalVideoTrack::screen_share_for_display(&display); + // let local_video_track_publication = + // room_a.publish_video_track(local_video_track).await.unwrap(); + + // if let RemoteVideoTrackUpdate::Subscribed(track) = + // video_track_updates.next().await.unwrap() + // { + // let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); + // assert_eq!(remote_video_tracks.len(), 1); + // assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); + // assert_eq!(track.publisher_id(), "test-participant-1"); + // } else { + // panic!("unexpected message"); + // } + + // let remote_video_track = room_b + // .remote_video_tracks("test-participant-1") + // .pop() + // .unwrap(); + // room_a.unpublish_track(local_video_track_publication); + // if let RemoteVideoTrackUpdate::Unsubscribed { + // publisher_id, + // track_id, + // } = video_track_updates.next().await.unwrap() + // { + // assert_eq!(publisher_id, "test-participant-1"); + // assert_eq!(remote_video_track.sid(), track_id); + // assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); + // } else { + // panic!("unexpected message"); + // } + + // cx.platform().quit(); + // }) + // .detach(); + // }); +} + +// fn quit(_: &Quit, cx: &mut gpui2::AppContext) { +// cx.platform().quit(); +// } diff --git a/crates/live_kit_client2/src/live_kit_client2.rs b/crates/live_kit_client2/src/live_kit_client2.rs new file mode 100644 index 0000000000..35682382e9 --- /dev/null +++ b/crates/live_kit_client2/src/live_kit_client2.rs @@ -0,0 +1,11 @@ +// #[cfg(not(any(test, feature = "test-support")))] +pub mod prod; + +// #[cfg(not(any(test, feature = "test-support")))] +pub use prod::*; + +// #[cfg(any(test, feature = "test-support"))] +// pub mod test; + +// #[cfg(any(test, feature = "test-support"))] +// pub use test::*; diff --git a/crates/live_kit_client2/src/prod.rs b/crates/live_kit_client2/src/prod.rs new file mode 100644 index 0000000000..65ed8b754f --- /dev/null +++ b/crates/live_kit_client2/src/prod.rs @@ -0,0 +1,943 @@ +use anyhow::{anyhow, Context, Result}; +use core_foundation::{ + array::{CFArray, CFArrayRef}, + base::{CFRelease, CFRetain, TCFType}, + string::{CFString, CFStringRef}, +}; +use futures::{ + channel::{mpsc, oneshot}, + Future, +}; +pub use media::core_video::CVImageBuffer; +use media::core_video::CVImageBufferRef; +use parking_lot::Mutex; +use postage::watch; +use std::{ + ffi::c_void, + sync::{Arc, Weak}, +}; + +// SAFETY: Most live kit types are threadsafe: +// https://github.com/livekit/client-sdk-swift#thread-safety +macro_rules! pointer_type { + ($pointer_name:ident) => { + #[repr(transparent)] + #[derive(Copy, Clone, Debug)] + pub struct $pointer_name(pub *const std::ffi::c_void); + unsafe impl Send for $pointer_name {} + }; +} + +mod swift { + pointer_type!(Room); + pointer_type!(LocalAudioTrack); + pointer_type!(RemoteAudioTrack); + pointer_type!(LocalVideoTrack); + pointer_type!(RemoteVideoTrack); + pointer_type!(LocalTrackPublication); + pointer_type!(RemoteTrackPublication); + pointer_type!(MacOSDisplay); + pointer_type!(RoomDelegate); +} + +extern "C" { + fn LKRoomDelegateCreate( + callback_data: *mut c_void, + on_did_disconnect: extern "C" fn(callback_data: *mut c_void), + on_did_subscribe_to_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + remote_track: swift::RemoteAudioTrack, + remote_publication: swift::RemoteTrackPublication, + ), + on_did_unsubscribe_from_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ), + on_mute_changed_from_remote_audio_track: extern "C" fn( + callback_data: *mut c_void, + track_id: CFStringRef, + muted: bool, + ), + on_active_speakers_changed: extern "C" fn( + callback_data: *mut c_void, + participants: CFArrayRef, + ), + on_did_subscribe_to_remote_video_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + remote_track: swift::RemoteVideoTrack, + ), + on_did_unsubscribe_from_remote_video_track: extern "C" fn( + callback_data: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ), + ) -> swift::RoomDelegate; + + fn LKRoomCreate(delegate: swift::RoomDelegate) -> swift::Room; + fn LKRoomConnect( + room: swift::Room, + url: CFStringRef, + token: CFStringRef, + callback: extern "C" fn(*mut c_void, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomDisconnect(room: swift::Room); + fn LKRoomPublishVideoTrack( + room: swift::Room, + track: swift::LocalVideoTrack, + callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomPublishAudioTrack( + room: swift::Room, + track: swift::LocalAudioTrack, + callback: extern "C" fn(*mut c_void, swift::LocalTrackPublication, CFStringRef), + callback_data: *mut c_void, + ); + fn LKRoomUnpublishTrack(room: swift::Room, publication: swift::LocalTrackPublication); + + fn LKRoomAudioTracksForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKRoomAudioTrackPublicationsForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKRoomVideoTracksForRemoteParticipant( + room: swift::Room, + participant_id: CFStringRef, + ) -> CFArrayRef; + + fn LKVideoRendererCreate( + callback_data: *mut c_void, + on_frame: extern "C" fn(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool, + on_drop: extern "C" fn(callback_data: *mut c_void), + ) -> *const c_void; + + fn LKRemoteAudioTrackGetSid(track: swift::RemoteAudioTrack) -> CFStringRef; + fn LKVideoTrackAddRenderer(track: swift::RemoteVideoTrack, renderer: *const c_void); + fn LKRemoteVideoTrackGetSid(track: swift::RemoteVideoTrack) -> CFStringRef; + + fn LKDisplaySources( + callback_data: *mut c_void, + callback: extern "C" fn( + callback_data: *mut c_void, + sources: CFArrayRef, + error: CFStringRef, + ), + ); + fn LKCreateScreenShareTrackForDisplay(display: swift::MacOSDisplay) -> swift::LocalVideoTrack; + fn LKLocalAudioTrackCreateTrack() -> swift::LocalAudioTrack; + + fn LKLocalTrackPublicationSetMute( + publication: swift::LocalTrackPublication, + muted: bool, + on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), + callback_data: *mut c_void, + ); + + fn LKRemoteTrackPublicationSetEnabled( + publication: swift::RemoteTrackPublication, + enabled: bool, + on_complete: extern "C" fn(callback_data: *mut c_void, error: CFStringRef), + callback_data: *mut c_void, + ); + + fn LKRemoteTrackPublicationIsMuted(publication: swift::RemoteTrackPublication) -> bool; + fn LKRemoteTrackPublicationGetSid(publication: swift::RemoteTrackPublication) -> CFStringRef; +} + +pub type Sid = String; + +#[derive(Clone, Eq, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connected { url: String, token: String }, +} + +pub struct Room { + native_room: Mutex, + connection: Mutex<( + watch::Sender, + watch::Receiver, + )>, + remote_audio_track_subscribers: Mutex>>, + remote_video_track_subscribers: Mutex>>, + _delegate: Mutex, +} + +trait AssertSendSync: Send {} +impl AssertSendSync for Room {} + +impl Room { + pub fn new() -> Arc { + Arc::new_cyclic(|weak_room| { + let delegate = RoomDelegate::new(weak_room.clone()); + Self { + native_room: Mutex::new(unsafe { LKRoomCreate(delegate.native_delegate) }), + connection: Mutex::new(watch::channel_with(ConnectionState::Disconnected)), + remote_audio_track_subscribers: Default::default(), + remote_video_track_subscribers: Default::default(), + _delegate: Mutex::new(delegate), + } + }) + } + + pub fn status(&self) -> watch::Receiver { + self.connection.lock().1.clone() + } + + pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { + let url = CFString::new(url); + let token = CFString::new(token); + let (did_connect, tx, rx) = Self::build_done_callback(); + unsafe { + LKRoomConnect( + *self.native_room.lock(), + url.as_concrete_TypeRef(), + token.as_concrete_TypeRef(), + did_connect, + tx, + ) + } + + let this = self.clone(); + let url = url.to_string(); + let token = token.to_string(); + async move { + rx.await.unwrap().context("error connecting to room")?; + *this.connection.lock().0.borrow_mut() = ConnectionState::Connected { url, token }; + Ok(()) + } + } + + fn did_disconnect(&self) { + *self.connection.lock().0.borrow_mut() = ConnectionState::Disconnected; + } + + pub fn display_sources(self: &Arc) -> impl Future>> { + extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) { + unsafe { + let tx = Box::from_raw(tx as *mut oneshot::Sender>>); + + if sources.is_null() { + let _ = tx.send(Err(anyhow!("{}", CFString::wrap_under_get_rule(error)))); + } else { + let sources = CFArray::wrap_under_get_rule(sources) + .into_iter() + .map(|source| MacOSDisplay::new(swift::MacOSDisplay(*source))) + .collect(); + + let _ = tx.send(Ok(sources)); + } + } + } + + let (tx, rx) = oneshot::channel(); + + unsafe { + LKDisplaySources(Box::into_raw(Box::new(tx)) as *mut _, callback); + } + + async move { rx.await.unwrap() } + } + + pub fn publish_video_track( + self: &Arc, + track: LocalVideoTrack, + ) -> impl Future> { + let (tx, rx) = oneshot::channel::>(); + extern "C" fn callback( + tx: *mut c_void, + publication: swift::LocalTrackPublication, + error: CFStringRef, + ) { + let tx = + unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(LocalTrackPublication::new(publication))); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + unsafe { + LKRoomPublishVideoTrack( + *self.native_room.lock(), + track.0, + callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ); + } + async { rx.await.unwrap().context("error publishing video track") } + } + + pub fn publish_audio_track( + self: &Arc, + track: LocalAudioTrack, + ) -> impl Future> { + let (tx, rx) = oneshot::channel::>(); + extern "C" fn callback( + tx: *mut c_void, + publication: swift::LocalTrackPublication, + error: CFStringRef, + ) { + let tx = + unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(LocalTrackPublication::new(publication))); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + unsafe { + LKRoomPublishAudioTrack( + *self.native_room.lock(), + track.0, + callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ); + } + async { rx.await.unwrap().context("error publishing audio track") } + } + + pub fn unpublish_track(&self, publication: LocalTrackPublication) { + unsafe { + LKRoomUnpublishTrack(*self.native_room.lock(), publication.0); + } + } + + pub fn remote_video_tracks(&self, participant_id: &str) -> Vec> { + unsafe { + let tracks = LKRoomVideoTracksForRemoteParticipant( + *self.native_room.lock(), + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track| { + let native_track = swift::RemoteVideoTrack(*native_track); + let id = + CFString::wrap_under_get_rule(LKRemoteVideoTrackGetSid(native_track)) + .to_string(); + Arc::new(RemoteVideoTrack::new( + native_track, + id, + participant_id.into(), + )) + }) + .collect() + } + } + } + + pub fn remote_audio_tracks(&self, participant_id: &str) -> Vec> { + unsafe { + let tracks = LKRoomAudioTracksForRemoteParticipant( + *self.native_room.lock(), + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track| { + let native_track = swift::RemoteAudioTrack(*native_track); + let id = + CFString::wrap_under_get_rule(LKRemoteAudioTrackGetSid(native_track)) + .to_string(); + Arc::new(RemoteAudioTrack::new( + native_track, + id, + participant_id.into(), + )) + }) + .collect() + } + } + } + + pub fn remote_audio_track_publications( + &self, + participant_id: &str, + ) -> Vec> { + unsafe { + let tracks = LKRoomAudioTrackPublicationsForRemoteParticipant( + *self.native_room.lock(), + CFString::new(participant_id).as_concrete_TypeRef(), + ); + + if tracks.is_null() { + Vec::new() + } else { + let tracks = CFArray::wrap_under_get_rule(tracks); + tracks + .into_iter() + .map(|native_track_publication| { + let native_track_publication = + swift::RemoteTrackPublication(*native_track_publication); + Arc::new(RemoteTrackPublication::new(native_track_publication)) + }) + .collect() + } + } + } + + pub fn remote_audio_track_updates(&self) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded(); + self.remote_audio_track_subscribers.lock().push(tx); + rx + } + + pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded(); + self.remote_video_track_subscribers.lock().push(tx); + rx + } + + fn did_subscribe_to_remote_audio_track( + &self, + track: RemoteAudioTrack, + publication: RemoteTrackPublication, + ) { + let track = Arc::new(track); + let publication = Arc::new(publication); + self.remote_audio_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) + .is_ok() + }); + } + + fn did_unsubscribe_from_remote_audio_track(&self, publisher_id: String, track_id: String) { + self.remote_audio_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::Unsubscribed { + publisher_id: publisher_id.clone(), + track_id: track_id.clone(), + }) + .is_ok() + }); + } + + fn mute_changed_from_remote_audio_track(&self, track_id: String, muted: bool) { + self.remote_audio_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::MuteChanged { + track_id: track_id.clone(), + muted, + }) + .is_ok() + }); + } + + // A vec of publisher IDs + fn active_speakers_changed(&self, speakers: Vec) { + self.remote_audio_track_subscribers + .lock() + .retain(move |tx| { + tx.unbounded_send(RemoteAudioTrackUpdate::ActiveSpeakersChanged { + speakers: speakers.clone(), + }) + .is_ok() + }); + } + + fn did_subscribe_to_remote_video_track(&self, track: RemoteVideoTrack) { + let track = Arc::new(track); + self.remote_video_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .is_ok() + }); + } + + fn did_unsubscribe_from_remote_video_track(&self, publisher_id: String, track_id: String) { + self.remote_video_track_subscribers.lock().retain(|tx| { + tx.unbounded_send(RemoteVideoTrackUpdate::Unsubscribed { + publisher_id: publisher_id.clone(), + track_id: track_id.clone(), + }) + .is_ok() + }); + } + + fn build_done_callback() -> ( + extern "C" fn(*mut c_void, CFStringRef), + *mut c_void, + oneshot::Receiver>, + ) { + let (tx, rx) = oneshot::channel(); + extern "C" fn done_callback(tx: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(tx as *mut oneshot::Sender>) }; + if error.is_null() { + let _ = tx.send(Ok(())); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + let _ = tx.send(Err(anyhow!(error))); + } + } + ( + done_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + rx, + ) + } +} + +impl Drop for Room { + fn drop(&mut self) { + unsafe { + let native_room = &*self.native_room.lock(); + LKRoomDisconnect(*native_room); + CFRelease(native_room.0); + } + } +} + +struct RoomDelegate { + native_delegate: swift::RoomDelegate, + _weak_room: Weak, +} + +impl RoomDelegate { + fn new(weak_room: Weak) -> Self { + let native_delegate = unsafe { + LKRoomDelegateCreate( + weak_room.as_ptr() as *mut c_void, + Self::on_did_disconnect, + Self::on_did_subscribe_to_remote_audio_track, + Self::on_did_unsubscribe_from_remote_audio_track, + Self::on_mute_change_from_remote_audio_track, + Self::on_active_speakers_changed, + Self::on_did_subscribe_to_remote_video_track, + Self::on_did_unsubscribe_from_remote_video_track, + ) + }; + Self { + native_delegate, + _weak_room: weak_room, + } + } + + extern "C" fn on_did_disconnect(room: *mut c_void) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + if let Some(room) = room.upgrade() { + room.did_disconnect(); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_subscribe_to_remote_audio_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + track: swift::RemoteAudioTrack, + publication: swift::RemoteTrackPublication, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + let track = RemoteAudioTrack::new(track, track_id, publisher_id); + let publication = RemoteTrackPublication::new(publication); + if let Some(room) = room.upgrade() { + room.did_subscribe_to_remote_audio_track(track, publication); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_unsubscribe_from_remote_audio_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.did_unsubscribe_from_remote_audio_track(publisher_id, track_id); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_mute_change_from_remote_audio_track( + room: *mut c_void, + track_id: CFStringRef, + muted: bool, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.mute_changed_from_remote_audio_track(track_id, muted); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_active_speakers_changed(room: *mut c_void, participants: CFArrayRef) { + if participants.is_null() { + return; + } + + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let speakers = unsafe { + CFArray::wrap_under_get_rule(participants) + .into_iter() + .map( + |speaker: core_foundation::base::ItemRef<'_, *const c_void>| { + CFString::wrap_under_get_rule(*speaker as CFStringRef).to_string() + }, + ) + .collect() + }; + + if let Some(room) = room.upgrade() { + room.active_speakers_changed(speakers); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_subscribe_to_remote_video_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + track: swift::RemoteVideoTrack, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + let track = RemoteVideoTrack::new(track, track_id, publisher_id); + if let Some(room) = room.upgrade() { + room.did_subscribe_to_remote_video_track(track); + } + let _ = Weak::into_raw(room); + } + + extern "C" fn on_did_unsubscribe_from_remote_video_track( + room: *mut c_void, + publisher_id: CFStringRef, + track_id: CFStringRef, + ) { + let room = unsafe { Weak::from_raw(room as *mut Room) }; + let publisher_id = unsafe { CFString::wrap_under_get_rule(publisher_id).to_string() }; + let track_id = unsafe { CFString::wrap_under_get_rule(track_id).to_string() }; + if let Some(room) = room.upgrade() { + room.did_unsubscribe_from_remote_video_track(publisher_id, track_id); + } + let _ = Weak::into_raw(room); + } +} + +impl Drop for RoomDelegate { + fn drop(&mut self) { + unsafe { + CFRelease(self.native_delegate.0); + } + } +} + +pub struct LocalAudioTrack(swift::LocalAudioTrack); + +impl LocalAudioTrack { + pub fn create() -> Self { + Self(unsafe { LKLocalAudioTrackCreateTrack() }) + } +} + +impl Drop for LocalAudioTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct LocalVideoTrack(swift::LocalVideoTrack); + +impl LocalVideoTrack { + pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { + Self(unsafe { LKCreateScreenShareTrackForDisplay(display.0) }) + } +} + +impl Drop for LocalVideoTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct LocalTrackPublication(swift::LocalTrackPublication); + +impl LocalTrackPublication { + pub fn new(native_track_publication: swift::LocalTrackPublication) -> Self { + unsafe { + CFRetain(native_track_publication.0); + } + Self(native_track_publication) + } + + pub fn set_mute(&self, muted: bool) -> impl Future> { + let (tx, rx) = futures::channel::oneshot::channel(); + + extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; + if error.is_null() { + tx.send(Ok(())).ok(); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + tx.send(Err(anyhow!(error))).ok(); + } + } + + unsafe { + LKLocalTrackPublicationSetMute( + self.0, + muted, + complete_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ) + } + + async move { rx.await.unwrap() } + } +} + +impl Drop for LocalTrackPublication { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +pub struct RemoteTrackPublication { + native_publication: Mutex, +} + +impl RemoteTrackPublication { + pub fn new(native_track_publication: swift::RemoteTrackPublication) -> Self { + unsafe { + CFRetain(native_track_publication.0); + } + Self { + native_publication: Mutex::new(native_track_publication), + } + } + + pub fn sid(&self) -> String { + unsafe { + CFString::wrap_under_get_rule(LKRemoteTrackPublicationGetSid( + *self.native_publication.lock(), + )) + .to_string() + } + } + + pub fn is_muted(&self) -> bool { + unsafe { LKRemoteTrackPublicationIsMuted(*self.native_publication.lock()) } + } + + pub fn set_enabled(&self, enabled: bool) -> impl Future> { + let (tx, rx) = futures::channel::oneshot::channel(); + + extern "C" fn complete_callback(callback_data: *mut c_void, error: CFStringRef) { + let tx = unsafe { Box::from_raw(callback_data as *mut oneshot::Sender>) }; + if error.is_null() { + tx.send(Ok(())).ok(); + } else { + let error = unsafe { CFString::wrap_under_get_rule(error).to_string() }; + tx.send(Err(anyhow!(error))).ok(); + } + } + + unsafe { + LKRemoteTrackPublicationSetEnabled( + *self.native_publication.lock(), + enabled, + complete_callback, + Box::into_raw(Box::new(tx)) as *mut c_void, + ) + } + + async move { rx.await.unwrap() } + } +} + +impl Drop for RemoteTrackPublication { + fn drop(&mut self) { + unsafe { CFRelease((*self.native_publication.lock()).0) } + } +} + +#[derive(Debug)] +pub struct RemoteAudioTrack { + native_track: Mutex, + sid: Sid, + publisher_id: String, +} + +impl RemoteAudioTrack { + fn new(native_track: swift::RemoteAudioTrack, sid: Sid, publisher_id: String) -> Self { + unsafe { + CFRetain(native_track.0); + } + Self { + native_track: Mutex::new(native_track), + sid, + publisher_id, + } + } + + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn enable(&self) -> impl Future> { + async { Ok(()) } + } + + pub fn disable(&self) -> impl Future> { + async { Ok(()) } + } +} + +impl Drop for RemoteAudioTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.native_track.lock().0) } + } +} + +#[derive(Debug)] +pub struct RemoteVideoTrack { + native_track: Mutex, + sid: Sid, + publisher_id: String, +} + +impl RemoteVideoTrack { + fn new(native_track: swift::RemoteVideoTrack, sid: Sid, publisher_id: String) -> Self { + unsafe { + CFRetain(native_track.0); + } + Self { + native_track: Mutex::new(native_track), + sid, + publisher_id, + } + } + + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn frames(&self) -> async_broadcast::Receiver { + extern "C" fn on_frame(callback_data: *mut c_void, frame: CVImageBufferRef) -> bool { + unsafe { + let tx = Box::from_raw(callback_data as *mut async_broadcast::Sender); + let buffer = CVImageBuffer::wrap_under_get_rule(frame); + let result = tx.try_broadcast(Frame(buffer)); + let _ = Box::into_raw(tx); + match result { + Ok(_) => true, + Err(async_broadcast::TrySendError::Closed(_)) + | Err(async_broadcast::TrySendError::Inactive(_)) => { + log::warn!("no active receiver for frame"); + false + } + Err(async_broadcast::TrySendError::Full(_)) => { + log::warn!("skipping frame as receiver is not keeping up"); + true + } + } + } + } + + extern "C" fn on_drop(callback_data: *mut c_void) { + unsafe { + let _ = Box::from_raw(callback_data as *mut async_broadcast::Sender); + } + } + + let (tx, rx) = async_broadcast::broadcast(64); + unsafe { + let renderer = LKVideoRendererCreate( + Box::into_raw(Box::new(tx)) as *mut c_void, + on_frame, + on_drop, + ); + LKVideoTrackAddRenderer(*self.native_track.lock(), renderer); + rx + } + } +} + +impl Drop for RemoteVideoTrack { + fn drop(&mut self) { + unsafe { CFRelease(self.native_track.lock().0) } + } +} + +pub enum RemoteVideoTrackUpdate { + Subscribed(Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +pub enum RemoteAudioTrackUpdate { + ActiveSpeakersChanged { speakers: Vec }, + MuteChanged { track_id: Sid, muted: bool }, + Subscribed(Arc, Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +pub struct MacOSDisplay(swift::MacOSDisplay); + +impl MacOSDisplay { + fn new(ptr: swift::MacOSDisplay) -> Self { + unsafe { + CFRetain(ptr.0); + } + Self(ptr) + } +} + +impl Drop for MacOSDisplay { + fn drop(&mut self) { + unsafe { CFRelease(self.0 .0) } + } +} + +#[derive(Clone)] +pub struct Frame(CVImageBuffer); + +impl Frame { + pub fn width(&self) -> usize { + self.0.width() + } + + pub fn height(&self) -> usize { + self.0.height() + } + + pub fn image(&self) -> CVImageBuffer { + self.0.clone() + } +} diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs new file mode 100644 index 0000000000..7185c11fa8 --- /dev/null +++ b/crates/live_kit_client2/src/test.rs @@ -0,0 +1,647 @@ +use anyhow::{anyhow, Result}; +use async_trait::async_trait; +use collections::{BTreeMap, HashMap}; +use futures::Stream; +use gpui2::Executor; +use live_kit_server::token; +use media::core_video::CVImageBuffer; +use parking_lot::Mutex; +use postage::watch; +use std::{future::Future, mem, sync::Arc}; + +static SERVERS: Mutex>> = Mutex::new(BTreeMap::new()); + +pub struct TestServer { + pub url: String, + pub api_key: String, + pub secret_key: String, + rooms: Mutex>, + executor: Arc, +} + +impl TestServer { + pub fn create( + url: String, + api_key: String, + secret_key: String, + executor: Arc, + ) -> Result> { + let mut servers = SERVERS.lock(); + if servers.contains_key(&url) { + Err(anyhow!("a server with url {:?} already exists", url)) + } else { + let server = Arc::new(TestServer { + url: url.clone(), + api_key, + secret_key, + rooms: Default::default(), + executor, + }); + servers.insert(url, server.clone()); + Ok(server) + } + } + + fn get(url: &str) -> Result> { + Ok(SERVERS + .lock() + .get(url) + .ok_or_else(|| anyhow!("no server found for url"))? + .clone()) + } + + pub fn teardown(&self) -> Result<()> { + SERVERS + .lock() + .remove(&self.url) + .ok_or_else(|| anyhow!("server with url {:?} does not exist", self.url))?; + Ok(()) + } + + pub fn create_api_client(&self) -> TestApiClient { + TestApiClient { + url: self.url.clone(), + } + } + + pub async fn create_room(&self, room: String) -> Result<()> { + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + if server_rooms.contains_key(&room) { + Err(anyhow!("room {:?} already exists", room)) + } else { + server_rooms.insert(room, Default::default()); + Ok(()) + } + } + + async fn delete_room(&self, room: String) -> Result<()> { + // TODO: clear state associated with all `Room`s. + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + server_rooms + .remove(&room) + .ok_or_else(|| anyhow!("room {:?} does not exist", room))?; + Ok(()) + } + + async fn join_room(&self, token: String, client_room: Arc) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + let mut server_rooms = self.rooms.lock(); + let room = (*server_rooms).entry(room_name.to_string()).or_default(); + + if room.client_rooms.contains_key(&identity) { + Err(anyhow!( + "{:?} attempted to join room {:?} twice", + identity, + room_name + )) + } else { + for track in &room.video_tracks { + client_room + .0 + .lock() + .video_track_updates + .0 + .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .unwrap(); + } + room.client_rooms.insert(identity, client_room); + Ok(()) + } + } + + async fn leave_room(&self, token: String) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + room.client_rooms.remove(&identity).ok_or_else(|| { + anyhow!( + "{:?} attempted to leave room {:?} before joining it", + identity, + room_name + ) + })?; + Ok(()) + } + + async fn remove_participant(&self, room_name: String, identity: String) -> Result<()> { + // TODO: clear state associated with the `Room`. + + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + room.client_rooms.remove(&identity).ok_or_else(|| { + anyhow!( + "participant {:?} did not join room {:?}", + identity, + room_name + ) + })?; + Ok(()) + } + + pub async fn disconnect_client(&self, client_identity: String) { + self.executor.simulate_random_delay().await; + let mut server_rooms = self.rooms.lock(); + for room in server_rooms.values_mut() { + if let Some(room) = room.client_rooms.remove(&client_identity) { + *room.0.lock().connection.0.borrow_mut() = ConnectionState::Disconnected; + } + } + } + + async fn publish_video_track(&self, token: String, local_track: LocalVideoTrack) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + + let track = Arc::new(RemoteVideoTrack { + sid: nanoid::nanoid!(17), + publisher_id: identity.clone(), + frames_rx: local_track.frames_rx.clone(), + }); + + room.video_tracks.push(track.clone()); + + for (id, client_room) in &room.client_rooms { + if *id != identity { + let _ = client_room + .0 + .lock() + .video_track_updates + .0 + .try_broadcast(RemoteVideoTrackUpdate::Subscribed(track.clone())) + .unwrap(); + } + } + + Ok(()) + } + + async fn publish_audio_track( + &self, + token: String, + _local_track: &LocalAudioTrack, + ) -> Result<()> { + self.executor.simulate_random_delay().await; + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let identity = claims.sub.unwrap().to_string(); + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + + let track = Arc::new(RemoteAudioTrack { + sid: nanoid::nanoid!(17), + publisher_id: identity.clone(), + }); + + let publication = Arc::new(RemoteTrackPublication); + + room.audio_tracks.push(track.clone()); + + for (id, client_room) in &room.client_rooms { + if *id != identity { + let _ = client_room + .0 + .lock() + .audio_track_updates + .0 + .try_broadcast(RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + )) + .unwrap(); + } + } + + Ok(()) + } + + fn video_tracks(&self, token: String) -> Result>> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + Ok(room.video_tracks.clone()) + } + + fn audio_tracks(&self, token: String) -> Result>> { + let claims = live_kit_server::token::validate(&token, &self.secret_key)?; + let room_name = claims.video.room.unwrap(); + + let mut server_rooms = self.rooms.lock(); + let room = server_rooms + .get_mut(&*room_name) + .ok_or_else(|| anyhow!("room {} does not exist", room_name))?; + Ok(room.audio_tracks.clone()) + } +} + +#[derive(Default)] +struct TestServerRoom { + client_rooms: HashMap>, + video_tracks: Vec>, + audio_tracks: Vec>, +} + +impl TestServerRoom {} + +pub struct TestApiClient { + url: String, +} + +#[async_trait] +impl live_kit_server::api::Client for TestApiClient { + fn url(&self) -> &str { + &self.url + } + + async fn create_room(&self, name: String) -> Result<()> { + let server = TestServer::get(&self.url)?; + server.create_room(name).await?; + Ok(()) + } + + async fn delete_room(&self, name: String) -> Result<()> { + let server = TestServer::get(&self.url)?; + server.delete_room(name).await?; + Ok(()) + } + + async fn remove_participant(&self, room: String, identity: String) -> Result<()> { + let server = TestServer::get(&self.url)?; + server.remove_participant(room, identity).await?; + Ok(()) + } + + fn room_token(&self, room: &str, identity: &str) -> Result { + let server = TestServer::get(&self.url)?; + token::create( + &server.api_key, + &server.secret_key, + Some(identity), + token::VideoGrant::to_join(room), + ) + } + + fn guest_token(&self, room: &str, identity: &str) -> Result { + let server = TestServer::get(&self.url)?; + token::create( + &server.api_key, + &server.secret_key, + Some(identity), + token::VideoGrant::for_guest(room), + ) + } +} + +pub type Sid = String; + +struct RoomState { + connection: ( + watch::Sender, + watch::Receiver, + ), + display_sources: Vec, + audio_track_updates: ( + async_broadcast::Sender, + async_broadcast::Receiver, + ), + video_track_updates: ( + async_broadcast::Sender, + async_broadcast::Receiver, + ), +} + +#[derive(Clone, Eq, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connected { url: String, token: String }, +} + +pub struct Room(Mutex); + +impl Room { + pub fn new() -> Arc { + Arc::new(Self(Mutex::new(RoomState { + connection: watch::channel_with(ConnectionState::Disconnected), + display_sources: Default::default(), + video_track_updates: async_broadcast::broadcast(128), + audio_track_updates: async_broadcast::broadcast(128), + }))) + } + + pub fn status(&self) -> watch::Receiver { + self.0.lock().connection.1.clone() + } + + pub fn connect(self: &Arc, url: &str, token: &str) -> impl Future> { + let this = self.clone(); + let url = url.to_string(); + let token = token.to_string(); + async move { + let server = TestServer::get(&url)?; + server.join_room(token.clone(), this.clone()).await?; + *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; + Ok(()) + } + } + + pub fn display_sources(self: &Arc) -> impl Future>> { + let this = self.clone(); + async move { + let server = this.test_server(); + server.executor.simulate_random_delay().await; + Ok(this.0.lock().display_sources.clone()) + } + } + + pub fn publish_video_track( + self: &Arc, + track: LocalVideoTrack, + ) -> impl Future> { + let this = self.clone(); + let track = track.clone(); + async move { + this.test_server() + .publish_video_track(this.token(), track) + .await?; + Ok(LocalTrackPublication) + } + } + pub fn publish_audio_track( + self: &Arc, + track: LocalAudioTrack, + ) -> impl Future> { + let this = self.clone(); + let track = track.clone(); + async move { + this.test_server() + .publish_audio_track(this.token(), &track) + .await?; + Ok(LocalTrackPublication) + } + } + + pub fn unpublish_track(&self, _publication: LocalTrackPublication) {} + + pub fn remote_audio_tracks(&self, publisher_id: &str) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .audio_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .collect() + } + + pub fn remote_audio_track_publications( + &self, + publisher_id: &str, + ) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .audio_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .map(|_track| Arc::new(RemoteTrackPublication {})) + .collect() + } + + pub fn remote_video_tracks(&self, publisher_id: &str) -> Vec> { + if !self.is_connected() { + return Vec::new(); + } + + self.test_server() + .video_tracks(self.token()) + .unwrap() + .into_iter() + .filter(|track| track.publisher_id() == publisher_id) + .collect() + } + + pub fn remote_audio_track_updates(&self) -> impl Stream { + self.0.lock().audio_track_updates.1.clone() + } + + pub fn remote_video_track_updates(&self) -> impl Stream { + self.0.lock().video_track_updates.1.clone() + } + + pub fn set_display_sources(&self, sources: Vec) { + self.0.lock().display_sources = sources; + } + + fn test_server(&self) -> Arc { + match self.0.lock().connection.1.borrow().clone() { + ConnectionState::Disconnected => panic!("must be connected to call this method"), + ConnectionState::Connected { url, .. } => TestServer::get(&url).unwrap(), + } + } + + fn token(&self) -> String { + match self.0.lock().connection.1.borrow().clone() { + ConnectionState::Disconnected => panic!("must be connected to call this method"), + ConnectionState::Connected { token, .. } => token, + } + } + + fn is_connected(&self) -> bool { + match *self.0.lock().connection.1.borrow() { + ConnectionState::Disconnected => false, + ConnectionState::Connected { .. } => true, + } + } +} + +impl Drop for Room { + fn drop(&mut self) { + if let ConnectionState::Connected { token, .. } = mem::replace( + &mut *self.0.lock().connection.0.borrow_mut(), + ConnectionState::Disconnected, + ) { + if let Ok(server) = TestServer::get(&token) { + let executor = server.executor.clone(); + executor + .spawn(async move { server.leave_room(token).await.unwrap() }) + .detach(); + } + } + } +} + +pub struct LocalTrackPublication; + +impl LocalTrackPublication { + pub fn set_mute(&self, _mute: bool) -> impl Future> { + async { Ok(()) } + } +} + +pub struct RemoteTrackPublication; + +impl RemoteTrackPublication { + pub fn set_enabled(&self, _enabled: bool) -> impl Future> { + async { Ok(()) } + } + + pub fn is_muted(&self) -> bool { + false + } + + pub fn sid(&self) -> String { + "".to_string() + } +} + +#[derive(Clone)] +pub struct LocalVideoTrack { + frames_rx: async_broadcast::Receiver, +} + +impl LocalVideoTrack { + pub fn screen_share_for_display(display: &MacOSDisplay) -> Self { + Self { + frames_rx: display.frames.1.clone(), + } + } +} + +#[derive(Clone)] +pub struct LocalAudioTrack; + +impl LocalAudioTrack { + pub fn create() -> Self { + Self + } +} + +pub struct RemoteVideoTrack { + sid: Sid, + publisher_id: Sid, + frames_rx: async_broadcast::Receiver, +} + +impl RemoteVideoTrack { + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn frames(&self) -> async_broadcast::Receiver { + self.frames_rx.clone() + } +} + +#[derive(Debug)] +pub struct RemoteAudioTrack { + sid: Sid, + publisher_id: Sid, +} + +impl RemoteAudioTrack { + pub fn sid(&self) -> &str { + &self.sid + } + + pub fn publisher_id(&self) -> &str { + &self.publisher_id + } + + pub fn enable(&self) -> impl Future> { + async { Ok(()) } + } + + pub fn disable(&self) -> impl Future> { + async { Ok(()) } + } +} + +#[derive(Clone)] +pub enum RemoteVideoTrackUpdate { + Subscribed(Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +#[derive(Clone)] +pub enum RemoteAudioTrackUpdate { + ActiveSpeakersChanged { speakers: Vec }, + MuteChanged { track_id: Sid, muted: bool }, + Subscribed(Arc, Arc), + Unsubscribed { publisher_id: Sid, track_id: Sid }, +} + +#[derive(Clone)] +pub struct MacOSDisplay { + frames: ( + async_broadcast::Sender, + async_broadcast::Receiver, + ), +} + +impl MacOSDisplay { + pub fn new() -> Self { + Self { + frames: async_broadcast::broadcast(128), + } + } + + pub fn send_frame(&self, frame: Frame) { + self.frames.0.try_broadcast(frame).unwrap(); + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Frame { + pub label: String, + pub width: usize, + pub height: usize, +} + +impl Frame { + pub fn width(&self) -> usize { + self.width + } + + pub fn height(&self) -> usize { + self.height + } + + pub fn image(&self) -> CVImageBuffer { + unimplemented!("you can't call this in test mode") + } +} From 795369a1e3d08795ad3cdc6a8a41413c90e2e484 Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 31 Oct 2023 18:10:23 -0400 Subject: [PATCH 03/15] Port `multi_buffer` to gpui2 --- Cargo.lock | 47 + Cargo.toml | 1 + crates/gpui2/src/subscription.rs | 2 +- crates/multi_buffer2/Cargo.toml | 78 + crates/multi_buffer2/src/anchor.rs | 138 + crates/multi_buffer2/src/multi_buffer2.rs | 5393 +++++++++++++++++++++ 6 files changed, 5658 insertions(+), 1 deletion(-) create mode 100644 crates/multi_buffer2/Cargo.toml create mode 100644 crates/multi_buffer2/src/anchor.rs create mode 100644 crates/multi_buffer2/src/multi_buffer2.rs diff --git a/Cargo.lock b/Cargo.lock index 3aca27106c..7b1a259e25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5035,6 +5035,53 @@ dependencies = [ "workspace", ] +[[package]] +name = "multi_buffer2" +version = "0.1.0" +dependencies = [ + "aho-corasick", + "anyhow", + "client2", + "clock", + "collections", + "convert_case 0.6.0", + "copilot2", + "ctor", + "env_logger 0.9.3", + "futures 0.3.28", + "git", + "gpui2", + "indoc", + "itertools 0.10.5", + "language2", + "lazy_static", + "log", + "lsp2", + "ordered-float 2.10.0", + "parking_lot 0.11.2", + "postage", + "project2", + "pulldown-cmark", + "rand 0.8.5", + "rich_text", + "schemars", + "serde", + "serde_derive", + "settings2", + "smallvec", + "smol", + "snippet", + "sum_tree", + "text", + "theme2", + "tree-sitter", + "tree-sitter-html", + "tree-sitter-rust", + "tree-sitter-typescript", + "unindent", + "util", +] + [[package]] name = "multimap" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index ac490ce935..60742b7416 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ members = [ "crates/menu", "crates/menu2", "crates/multi_buffer", + "crates/multi_buffer2", "crates/node_runtime", "crates/notifications", "crates/outline", diff --git a/crates/gpui2/src/subscription.rs b/crates/gpui2/src/subscription.rs index 3bf28792bb..c2799d2fe6 100644 --- a/crates/gpui2/src/subscription.rs +++ b/crates/gpui2/src/subscription.rs @@ -47,8 +47,8 @@ where subscribers.remove(&subscriber_id); if subscribers.is_empty() { lock.subscribers.remove(&emitter_key); - return; } + return; } // We didn't manage to remove the subscription, which means it was dropped diff --git a/crates/multi_buffer2/Cargo.toml b/crates/multi_buffer2/Cargo.toml new file mode 100644 index 0000000000..4c56bab9dc --- /dev/null +++ b/crates/multi_buffer2/Cargo.toml @@ -0,0 +1,78 @@ +[package] +name = "multi_buffer2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/multi_buffer2.rs" +doctest = false + +[features] +test-support = [ + "copilot2/test-support", + "text/test-support", + "language2/test-support", + "gpui2/test-support", + "util/test-support", + "tree-sitter-rust", + "tree-sitter-typescript" +] + +[dependencies] +client2 = { path = "../client2" } +clock = { path = "../clock" } +collections = { path = "../collections" } +git = { path = "../git" } +gpui2 = { path = "../gpui2" } +language2 = { path = "../language2" } +lsp2 = { path = "../lsp2" } +rich_text = { path = "../rich_text" } +settings2 = { path = "../settings2" } +snippet = { path = "../snippet" } +sum_tree = { path = "../sum_tree" } +text = { path = "../text" } +theme2 = { path = "../theme2" } +util = { path = "../util" } + +aho-corasick = "1.1" +anyhow.workspace = true +convert_case = "0.6.0" +futures.workspace = true +indoc = "1.0.4" +itertools = "0.10" +lazy_static.workspace = true +log.workspace = true +ordered-float.workspace = true +parking_lot.workspace = true +postage.workspace = true +pulldown-cmark = { version = "0.9.2", default-features = false } +rand.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true +smallvec.workspace = true +smol.workspace = true + +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-html = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } + +[dev-dependencies] +copilot2 = { path = "../copilot2", features = ["test-support"] } +text = { path = "../text", features = ["test-support"] } +language2 = { path = "../language2", features = ["test-support"] } +lsp2 = { path = "../lsp2", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +project2 = { path = "../project2", features = ["test-support"] } +settings2 = { path = "../settings2", features = ["test-support"] } + +ctor.workspace = true +env_logger.workspace = true +rand.workspace = true +unindent.workspace = true +tree-sitter.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-html.workspace = true +tree-sitter-typescript.workspace = true diff --git a/crates/multi_buffer2/src/anchor.rs b/crates/multi_buffer2/src/anchor.rs new file mode 100644 index 0000000000..fa65bfc800 --- /dev/null +++ b/crates/multi_buffer2/src/anchor.rs @@ -0,0 +1,138 @@ +use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint}; +use language2::{OffsetUtf16, Point, TextDimension}; +use std::{ + cmp::Ordering, + ops::{Range, Sub}, +}; +use sum_tree::Bias; + +#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] +pub struct Anchor { + pub buffer_id: Option, + pub excerpt_id: ExcerptId, + pub text_anchor: text::Anchor, +} + +impl Anchor { + pub fn min() -> Self { + Self { + buffer_id: None, + excerpt_id: ExcerptId::min(), + text_anchor: text::Anchor::MIN, + } + } + + pub fn max() -> Self { + Self { + buffer_id: None, + excerpt_id: ExcerptId::max(), + text_anchor: text::Anchor::MAX, + } + } + + pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering { + let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot); + if excerpt_id_cmp.is_eq() { + if self.excerpt_id == ExcerptId::min() || self.excerpt_id == ExcerptId::max() { + Ordering::Equal + } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + self.text_anchor.cmp(&other.text_anchor, &excerpt.buffer) + } else { + Ordering::Equal + } + } else { + excerpt_id_cmp + } + } + + pub fn bias(&self) -> Bias { + self.text_anchor.bias + } + + pub fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Anchor { + if self.text_anchor.bias != Bias::Left { + if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_left(&excerpt.buffer), + }; + } + } + self.clone() + } + + pub fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Anchor { + if self.text_anchor.bias != Bias::Right { + if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + return Self { + buffer_id: self.buffer_id, + excerpt_id: self.excerpt_id.clone(), + text_anchor: self.text_anchor.bias_right(&excerpt.buffer), + }; + } + } + self.clone() + } + + pub fn summary(&self, snapshot: &MultiBufferSnapshot) -> D + where + D: TextDimension + Ord + Sub, + { + snapshot.summary_for_anchor(self) + } + + pub fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool { + if *self == Anchor::min() || *self == Anchor::max() { + true + } else if let Some(excerpt) = snapshot.excerpt(self.excerpt_id) { + excerpt.contains(self) + && (self.text_anchor == excerpt.range.context.start + || self.text_anchor == excerpt.range.context.end + || self.text_anchor.is_valid(&excerpt.buffer)) + } else { + false + } + } +} + +impl ToOffset for Anchor { + fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize { + self.summary(snapshot) + } +} + +impl ToOffsetUtf16 for Anchor { + fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 { + self.summary(snapshot) + } +} + +impl ToPoint for Anchor { + fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point { + self.summary(snapshot) + } +} + +pub trait AnchorRangeExt { + fn cmp(&self, b: &Range, buffer: &MultiBufferSnapshot) -> Ordering; + fn to_offset(&self, content: &MultiBufferSnapshot) -> Range; + fn to_point(&self, content: &MultiBufferSnapshot) -> Range; +} + +impl AnchorRangeExt for Range { + fn cmp(&self, other: &Range, buffer: &MultiBufferSnapshot) -> Ordering { + match self.start.cmp(&other.start, buffer) { + Ordering::Equal => other.end.cmp(&self.end, buffer), + ord => ord, + } + } + + fn to_offset(&self, content: &MultiBufferSnapshot) -> Range { + self.start.to_offset(content)..self.end.to_offset(content) + } + + fn to_point(&self, content: &MultiBufferSnapshot) -> Range { + self.start.to_point(content)..self.end.to_point(content) + } +} diff --git a/crates/multi_buffer2/src/multi_buffer2.rs b/crates/multi_buffer2/src/multi_buffer2.rs new file mode 100644 index 0000000000..c5827b8b13 --- /dev/null +++ b/crates/multi_buffer2/src/multi_buffer2.rs @@ -0,0 +1,5393 @@ +mod anchor; + +pub use anchor::{Anchor, AnchorRangeExt}; +use anyhow::{anyhow, Result}; +use clock::ReplicaId; +use collections::{BTreeMap, Bound, HashMap, HashSet}; +use futures::{channel::mpsc, SinkExt}; +use git::diff::DiffHunk; +use gpui2::{AppContext, EventEmitter, Model, ModelContext}; +pub use language2::Completion; +use language2::{ + char_kind, + language_settings::{language_settings, LanguageSettings}, + AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape, + DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, + Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, + ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped, +}; +use std::{ + borrow::Cow, + cell::{Ref, RefCell}, + cmp, fmt, + future::Future, + io, + iter::{self, FromIterator}, + mem, + ops::{Range, RangeBounds, Sub}, + str, + sync::Arc, + time::{Duration, Instant}, +}; +use sum_tree::{Bias, Cursor, SumTree}; +use text::{ + locator::Locator, + subscription::{Subscription, Topic}, + Edit, TextSummary, +}; +use theme2::SyntaxTheme; +use util::post_inc; + +#[cfg(any(test, feature = "test-support"))] +use gpui2::Context; + +const NEWLINES: &[u8] = &[b'\n'; u8::MAX as usize]; + +#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct ExcerptId(usize); + +pub struct MultiBuffer { + snapshot: RefCell, + buffers: RefCell>, + next_excerpt_id: usize, + subscriptions: Topic, + singleton: bool, + replica_id: ReplicaId, + history: History, + title: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Event { + ExcerptsAdded { + buffer: Model, + predecessor: ExcerptId, + excerpts: Vec<(ExcerptId, ExcerptRange)>, + }, + ExcerptsRemoved { + ids: Vec, + }, + ExcerptsEdited { + ids: Vec, + }, + Edited { + sigleton_buffer_edited: bool, + }, + TransactionUndone { + transaction_id: TransactionId, + }, + Reloaded, + DiffBaseChanged, + LanguageChanged, + Reparsed, + Saved, + FileHandleChanged, + Closed, + DirtyChanged, + DiagnosticsUpdated, +} + +#[derive(Clone)] +struct History { + next_transaction_id: TransactionId, + undo_stack: Vec, + redo_stack: Vec, + transaction_depth: usize, + group_interval: Duration, +} + +#[derive(Clone)] +struct Transaction { + id: TransactionId, + buffer_transactions: HashMap, + first_edit_at: Instant, + last_edit_at: Instant, + suppress_grouping: bool, +} + +pub trait ToOffset: 'static + fmt::Debug { + fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize; +} + +pub trait ToOffsetUtf16: 'static + fmt::Debug { + fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16; +} + +pub trait ToPoint: 'static + fmt::Debug { + fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point; +} + +pub trait ToPointUtf16: 'static + fmt::Debug { + fn to_point_utf16(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16; +} + +struct BufferState { + buffer: Model, + last_version: clock::Global, + last_parse_count: usize, + last_selections_update_count: usize, + last_diagnostics_update_count: usize, + last_file_update_count: usize, + last_git_diff_update_count: usize, + excerpts: Vec, + _subscriptions: [gpui2::Subscription; 2], +} + +#[derive(Clone, Default)] +pub struct MultiBufferSnapshot { + singleton: bool, + excerpts: SumTree, + excerpt_ids: SumTree, + parse_count: usize, + diagnostics_update_count: usize, + trailing_excerpt_update_count: usize, + git_diff_update_count: usize, + edit_count: usize, + is_dirty: bool, + has_conflict: bool, +} + +pub struct ExcerptBoundary { + pub id: ExcerptId, + pub row: u32, + pub buffer: BufferSnapshot, + pub range: ExcerptRange, + pub starts_new_buffer: bool, +} + +#[derive(Clone)] +struct Excerpt { + id: ExcerptId, + locator: Locator, + buffer_id: u64, + buffer: BufferSnapshot, + range: ExcerptRange, + max_buffer_row: u32, + text_summary: TextSummary, + has_trailing_newline: bool, +} + +#[derive(Clone, Debug)] +struct ExcerptIdMapping { + id: ExcerptId, + locator: Locator, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ExcerptRange { + pub context: Range, + pub primary: Option>, +} + +#[derive(Clone, Debug, Default)] +struct ExcerptSummary { + excerpt_id: ExcerptId, + excerpt_locator: Locator, + max_buffer_row: u32, + text: TextSummary, +} + +#[derive(Clone)] +pub struct MultiBufferRows<'a> { + buffer_row_range: Range, + excerpts: Cursor<'a, Excerpt, Point>, +} + +pub struct MultiBufferChunks<'a> { + range: Range, + excerpts: Cursor<'a, Excerpt, usize>, + excerpt_chunks: Option>, + language_aware: bool, +} + +pub struct MultiBufferBytes<'a> { + range: Range, + excerpts: Cursor<'a, Excerpt, usize>, + excerpt_bytes: Option>, + chunk: &'a [u8], +} + +pub struct ReversedMultiBufferBytes<'a> { + range: Range, + excerpts: Cursor<'a, Excerpt, usize>, + excerpt_bytes: Option>, + chunk: &'a [u8], +} + +struct ExcerptChunks<'a> { + content_chunks: BufferChunks<'a>, + footer_height: usize, +} + +struct ExcerptBytes<'a> { + content_bytes: text::Bytes<'a>, + footer_height: usize, +} + +impl MultiBuffer { + pub fn new(replica_id: ReplicaId) -> Self { + Self { + snapshot: Default::default(), + buffers: Default::default(), + next_excerpt_id: 1, + subscriptions: Default::default(), + singleton: false, + replica_id, + history: History { + next_transaction_id: Default::default(), + undo_stack: Default::default(), + redo_stack: Default::default(), + transaction_depth: 0, + group_interval: Duration::from_millis(300), + }, + title: Default::default(), + } + } + + pub fn clone(&self, new_cx: &mut ModelContext) -> Self { + let mut buffers = HashMap::default(); + for (buffer_id, buffer_state) in self.buffers.borrow().iter() { + buffers.insert( + *buffer_id, + BufferState { + buffer: buffer_state.buffer.clone(), + last_version: buffer_state.last_version.clone(), + last_parse_count: buffer_state.last_parse_count, + last_selections_update_count: buffer_state.last_selections_update_count, + last_diagnostics_update_count: buffer_state.last_diagnostics_update_count, + last_file_update_count: buffer_state.last_file_update_count, + last_git_diff_update_count: buffer_state.last_git_diff_update_count, + excerpts: buffer_state.excerpts.clone(), + _subscriptions: [ + new_cx.observe(&buffer_state.buffer, |_, _, cx| cx.notify()), + new_cx.subscribe(&buffer_state.buffer, Self::on_buffer_event), + ], + }, + ); + } + Self { + snapshot: RefCell::new(self.snapshot.borrow().clone()), + buffers: RefCell::new(buffers), + next_excerpt_id: 1, + subscriptions: Default::default(), + singleton: self.singleton, + replica_id: self.replica_id, + history: self.history.clone(), + title: self.title.clone(), + } + } + + pub fn with_title(mut self, title: String) -> Self { + self.title = Some(title); + self + } + + pub fn singleton(buffer: Model, cx: &mut ModelContext) -> Self { + let mut this = Self::new(buffer.read(cx).replica_id()); + this.singleton = true; + this.push_excerpts( + buffer, + [ExcerptRange { + context: text::Anchor::MIN..text::Anchor::MAX, + primary: None, + }], + cx, + ); + this.snapshot.borrow_mut().singleton = true; + this + } + + pub fn replica_id(&self) -> ReplicaId { + self.replica_id + } + + pub fn snapshot(&self, cx: &AppContext) -> MultiBufferSnapshot { + self.sync(cx); + self.snapshot.borrow().clone() + } + + pub fn read(&self, cx: &AppContext) -> Ref { + self.sync(cx); + self.snapshot.borrow() + } + + pub fn as_singleton(&self) -> Option> { + if self.singleton { + return Some( + self.buffers + .borrow() + .values() + .next() + .unwrap() + .buffer + .clone(), + ); + } else { + None + } + } + + pub fn is_singleton(&self) -> bool { + self.singleton + } + + pub fn subscribe(&mut self) -> Subscription { + self.subscriptions.subscribe() + } + + pub fn is_dirty(&self, cx: &AppContext) -> bool { + self.read(cx).is_dirty() + } + + pub fn has_conflict(&self, cx: &AppContext) -> bool { + self.read(cx).has_conflict() + } + + // The `is_empty` signature doesn't match what clippy expects + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, cx: &AppContext) -> usize { + self.read(cx).len() + } + + pub fn is_empty(&self, cx: &AppContext) -> bool { + self.len(cx) != 0 + } + + pub fn symbols_containing( + &self, + offset: T, + theme: Option<&SyntaxTheme>, + cx: &AppContext, + ) -> Option<(u64, Vec>)> { + self.read(cx).symbols_containing(offset, theme) + } + + pub fn edit( + &mut self, + edits: I, + mut autoindent_mode: Option, + cx: &mut ModelContext, + ) where + I: IntoIterator, T)>, + S: ToOffset, + T: Into>, + { + if self.buffers.borrow().is_empty() { + return; + } + + let snapshot = self.read(cx); + let edits = edits.into_iter().map(|(range, new_text)| { + let mut range = range.start.to_offset(&snapshot)..range.end.to_offset(&snapshot); + if range.start > range.end { + mem::swap(&mut range.start, &mut range.end); + } + (range, new_text) + }); + + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, cx| { + buffer.edit(edits, autoindent_mode, cx); + }); + } + + let original_indent_columns = match &mut autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns, + }) => mem::take(original_indent_columns), + _ => Default::default(), + }; + + struct BufferEdit { + range: Range, + new_text: Arc, + is_insertion: bool, + original_indent_column: u32, + } + let mut buffer_edits: HashMap> = Default::default(); + let mut edited_excerpt_ids = Vec::new(); + let mut cursor = snapshot.excerpts.cursor::(); + for (ix, (range, new_text)) in edits.enumerate() { + let new_text: Arc = new_text.into(); + let original_indent_column = original_indent_columns.get(ix).copied().unwrap_or(0); + cursor.seek(&range.start, Bias::Right, &()); + if cursor.item().is_none() && range.start == *cursor.start() { + cursor.prev(&()); + } + let start_excerpt = cursor.item().expect("start offset out of bounds"); + let start_overshoot = range.start - cursor.start(); + let buffer_start = start_excerpt + .range + .context + .start + .to_offset(&start_excerpt.buffer) + + start_overshoot; + edited_excerpt_ids.push(start_excerpt.id); + + cursor.seek(&range.end, Bias::Right, &()); + if cursor.item().is_none() && range.end == *cursor.start() { + cursor.prev(&()); + } + let end_excerpt = cursor.item().expect("end offset out of bounds"); + let end_overshoot = range.end - cursor.start(); + let buffer_end = end_excerpt + .range + .context + .start + .to_offset(&end_excerpt.buffer) + + end_overshoot; + + if start_excerpt.id == end_excerpt.id { + buffer_edits + .entry(start_excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: buffer_start..buffer_end, + new_text, + is_insertion: true, + original_indent_column, + }); + } else { + edited_excerpt_ids.push(end_excerpt.id); + let start_excerpt_range = buffer_start + ..start_excerpt + .range + .context + .end + .to_offset(&start_excerpt.buffer); + let end_excerpt_range = end_excerpt + .range + .context + .start + .to_offset(&end_excerpt.buffer) + ..buffer_end; + buffer_edits + .entry(start_excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: start_excerpt_range, + new_text: new_text.clone(), + is_insertion: true, + original_indent_column, + }); + buffer_edits + .entry(end_excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: end_excerpt_range, + new_text: new_text.clone(), + is_insertion: false, + original_indent_column, + }); + + cursor.seek(&range.start, Bias::Right, &()); + cursor.next(&()); + while let Some(excerpt) = cursor.item() { + if excerpt.id == end_excerpt.id { + break; + } + buffer_edits + .entry(excerpt.buffer_id) + .or_insert(Vec::new()) + .push(BufferEdit { + range: excerpt.range.context.to_offset(&excerpt.buffer), + new_text: new_text.clone(), + is_insertion: false, + original_indent_column, + }); + edited_excerpt_ids.push(excerpt.id); + cursor.next(&()); + } + } + } + + drop(cursor); + drop(snapshot); + // Non-generic part of edit, hoisted out to avoid blowing up LLVM IR. + fn tail( + this: &mut MultiBuffer, + buffer_edits: HashMap>, + autoindent_mode: Option, + edited_excerpt_ids: Vec, + cx: &mut ModelContext, + ) { + for (buffer_id, mut edits) in buffer_edits { + edits.sort_unstable_by_key(|edit| edit.range.start); + this.buffers.borrow()[&buffer_id] + .buffer + .update(cx, |buffer, cx| { + let mut edits = edits.into_iter().peekable(); + let mut insertions = Vec::new(); + let mut original_indent_columns = Vec::new(); + let mut deletions = Vec::new(); + let empty_str: Arc = "".into(); + while let Some(BufferEdit { + mut range, + new_text, + mut is_insertion, + original_indent_column, + }) = edits.next() + { + while let Some(BufferEdit { + range: next_range, + is_insertion: next_is_insertion, + .. + }) = edits.peek() + { + if range.end >= next_range.start { + range.end = cmp::max(next_range.end, range.end); + is_insertion |= *next_is_insertion; + edits.next(); + } else { + break; + } + } + + if is_insertion { + original_indent_columns.push(original_indent_column); + insertions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + new_text.clone(), + )); + } else if !range.is_empty() { + deletions.push(( + buffer.anchor_before(range.start) + ..buffer.anchor_before(range.end), + empty_str.clone(), + )); + } + } + + let deletion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns: Default::default(), + }) + } else { + None + }; + let insertion_autoindent_mode = + if let Some(AutoindentMode::Block { .. }) = autoindent_mode { + Some(AutoindentMode::Block { + original_indent_columns, + }) + } else { + None + }; + + buffer.edit(deletions, deletion_autoindent_mode, cx); + buffer.edit(insertions, insertion_autoindent_mode, cx); + }) + } + + cx.emit(Event::ExcerptsEdited { + ids: edited_excerpt_ids, + }); + } + tail(self, buffer_edits, autoindent_mode, edited_excerpt_ids, cx); + } + + pub fn start_transaction(&mut self, cx: &mut ModelContext) -> Option { + self.start_transaction_at(Instant::now(), cx) + } + + pub fn start_transaction_at( + &mut self, + now: Instant, + cx: &mut ModelContext, + ) -> Option { + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, _| buffer.start_transaction_at(now)); + } + + for BufferState { buffer, .. } in self.buffers.borrow().values() { + buffer.update(cx, |buffer, _| buffer.start_transaction_at(now)); + } + self.history.start_transaction(now) + } + + pub fn end_transaction(&mut self, cx: &mut ModelContext) -> Option { + self.end_transaction_at(Instant::now(), cx) + } + + pub fn end_transaction_at( + &mut self, + now: Instant, + cx: &mut ModelContext, + ) -> Option { + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)); + } + + let mut buffer_transactions = HashMap::default(); + for BufferState { buffer, .. } in self.buffers.borrow().values() { + if let Some(transaction_id) = + buffer.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx)) + { + buffer_transactions.insert(buffer.read(cx).remote_id(), transaction_id); + } + } + + if self.history.end_transaction(now, buffer_transactions) { + let transaction_id = self.history.group().unwrap(); + Some(transaction_id) + } else { + None + } + } + + pub fn merge_transactions( + &mut self, + transaction: TransactionId, + destination: TransactionId, + cx: &mut ModelContext, + ) { + if let Some(buffer) = self.as_singleton() { + buffer.update(cx, |buffer, _| { + buffer.merge_transactions(transaction, destination) + }); + } else { + if let Some(transaction) = self.history.forget(transaction) { + if let Some(destination) = self.history.transaction_mut(destination) { + for (buffer_id, buffer_transaction_id) in transaction.buffer_transactions { + if let Some(destination_buffer_transaction_id) = + destination.buffer_transactions.get(&buffer_id) + { + if let Some(state) = self.buffers.borrow().get(&buffer_id) { + state.buffer.update(cx, |buffer, _| { + buffer.merge_transactions( + buffer_transaction_id, + *destination_buffer_transaction_id, + ) + }); + } + } else { + destination + .buffer_transactions + .insert(buffer_id, buffer_transaction_id); + } + } + } + } + } + } + + pub fn finalize_last_transaction(&mut self, cx: &mut ModelContext) { + self.history.finalize_last_transaction(); + for BufferState { buffer, .. } in self.buffers.borrow().values() { + buffer.update(cx, |buffer, _| { + buffer.finalize_last_transaction(); + }); + } + } + + pub fn push_transaction<'a, T>(&mut self, buffer_transactions: T, cx: &mut ModelContext) + where + T: IntoIterator, &'a language2::Transaction)>, + { + self.history + .push_transaction(buffer_transactions, Instant::now(), cx); + self.history.finalize_last_transaction(); + } + + pub fn group_until_transaction( + &mut self, + transaction_id: TransactionId, + cx: &mut ModelContext, + ) { + if let Some(buffer) = self.as_singleton() { + buffer.update(cx, |buffer, _| { + buffer.group_until_transaction(transaction_id) + }); + } else { + self.history.group_until(transaction_id); + } + } + + pub fn set_active_selections( + &mut self, + selections: &[Selection], + line_mode: bool, + cursor_shape: CursorShape, + cx: &mut ModelContext, + ) { + let mut selections_by_buffer: HashMap>> = + Default::default(); + let snapshot = self.read(cx); + let mut cursor = snapshot.excerpts.cursor::>(); + for selection in selections { + let start_locator = snapshot.excerpt_locator_for_id(selection.start.excerpt_id); + let end_locator = snapshot.excerpt_locator_for_id(selection.end.excerpt_id); + + cursor.seek(&Some(start_locator), Bias::Left, &()); + while let Some(excerpt) = cursor.item() { + if excerpt.locator > *end_locator { + break; + } + + let mut start = excerpt.range.context.start; + let mut end = excerpt.range.context.end; + if excerpt.id == selection.start.excerpt_id { + start = selection.start.text_anchor; + } + if excerpt.id == selection.end.excerpt_id { + end = selection.end.text_anchor; + } + selections_by_buffer + .entry(excerpt.buffer_id) + .or_default() + .push(Selection { + id: selection.id, + start, + end, + reversed: selection.reversed, + goal: selection.goal, + }); + + cursor.next(&()); + } + } + + for (buffer_id, buffer_state) in self.buffers.borrow().iter() { + if !selections_by_buffer.contains_key(buffer_id) { + buffer_state + .buffer + .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); + } + } + + for (buffer_id, mut selections) in selections_by_buffer { + self.buffers.borrow()[&buffer_id] + .buffer + .update(cx, |buffer, cx| { + selections.sort_unstable_by(|a, b| a.start.cmp(&b.start, buffer)); + let mut selections = selections.into_iter().peekable(); + let merged_selections = Arc::from_iter(iter::from_fn(|| { + let mut selection = selections.next()?; + while let Some(next_selection) = selections.peek() { + if selection.end.cmp(&next_selection.start, buffer).is_ge() { + let next_selection = selections.next().unwrap(); + if next_selection.end.cmp(&selection.end, buffer).is_ge() { + selection.end = next_selection.end; + } + } else { + break; + } + } + Some(selection) + })); + buffer.set_active_selections(merged_selections, line_mode, cursor_shape, cx); + }); + } + } + + pub fn remove_active_selections(&mut self, cx: &mut ModelContext) { + for buffer in self.buffers.borrow().values() { + buffer + .buffer + .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); + } + } + + pub fn undo(&mut self, cx: &mut ModelContext) -> Option { + let mut transaction_id = None; + if let Some(buffer) = self.as_singleton() { + transaction_id = buffer.update(cx, |buffer, cx| buffer.undo(cx)); + } else { + while let Some(transaction) = self.history.pop_undo() { + let mut undone = false; + for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions { + if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) { + undone |= buffer.update(cx, |buffer, cx| { + let undo_to = *buffer_transaction_id; + if let Some(entry) = buffer.peek_undo_stack() { + *buffer_transaction_id = entry.transaction_id(); + } + buffer.undo_to_transaction(undo_to, cx) + }); + } + } + + if undone { + transaction_id = Some(transaction.id); + break; + } + } + } + + if let Some(transaction_id) = transaction_id { + cx.emit(Event::TransactionUndone { transaction_id }); + } + + transaction_id + } + + pub fn redo(&mut self, cx: &mut ModelContext) -> Option { + if let Some(buffer) = self.as_singleton() { + return buffer.update(cx, |buffer, cx| buffer.redo(cx)); + } + + while let Some(transaction) = self.history.pop_redo() { + let mut redone = false; + for (buffer_id, buffer_transaction_id) in &mut transaction.buffer_transactions { + if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) { + redone |= buffer.update(cx, |buffer, cx| { + let redo_to = *buffer_transaction_id; + if let Some(entry) = buffer.peek_redo_stack() { + *buffer_transaction_id = entry.transaction_id(); + } + buffer.redo_to_transaction(redo_to, cx) + }); + } + } + + if redone { + return Some(transaction.id); + } + } + + None + } + + pub fn undo_transaction(&mut self, transaction_id: TransactionId, cx: &mut ModelContext) { + if let Some(buffer) = self.as_singleton() { + buffer.update(cx, |buffer, cx| buffer.undo_transaction(transaction_id, cx)); + } else if let Some(transaction) = self.history.remove_from_undo(transaction_id) { + for (buffer_id, transaction_id) in &transaction.buffer_transactions { + if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(buffer_id) { + buffer.update(cx, |buffer, cx| { + buffer.undo_transaction(*transaction_id, cx) + }); + } + } + } + } + + pub fn stream_excerpts_with_context_lines( + &mut self, + buffer: Model, + ranges: Vec>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> mpsc::Receiver> { + let (buffer_id, buffer_snapshot) = + buffer.update(cx, |buffer, _| (buffer.remote_id(), buffer.snapshot())); + + let (mut tx, rx) = mpsc::channel(256); + cx.spawn(move |this, mut cx| async move { + let mut excerpt_ranges = Vec::new(); + let mut range_counts = Vec::new(); + cx.executor() + .scoped(|scope| { + scope.spawn(async { + let (ranges, counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + excerpt_ranges = ranges; + range_counts = counts; + }); + }) + .await; + + let mut ranges = ranges.into_iter(); + let mut range_counts = range_counts.into_iter(); + for excerpt_ranges in excerpt_ranges.chunks(100) { + let excerpt_ids = match this.update(&mut cx, |this, cx| { + this.push_excerpts(buffer.clone(), excerpt_ranges.iter().cloned(), cx) + }) { + Ok(excerpt_ids) => excerpt_ids, + Err(_) => return, + }; + + for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.by_ref()) + { + for range in ranges.by_ref().take(range_count) { + let start = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.start, + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: range.end, + }; + if tx.send(start..end).await.is_err() { + break; + } + } + } + } + }) + .detach(); + + rx + } + + pub fn push_excerpts( + &mut self, + buffer: Model, + ranges: impl IntoIterator>, + cx: &mut ModelContext, + ) -> Vec + where + O: text::ToOffset, + { + self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx) + } + + pub fn push_excerpts_with_context_lines( + &mut self, + buffer: Model, + ranges: Vec>, + context_line_count: u32, + cx: &mut ModelContext, + ) -> Vec> + where + O: text::ToPoint + text::ToOffset, + { + let buffer_id = buffer.read(cx).remote_id(); + let buffer_snapshot = buffer.read(cx).snapshot(); + let (excerpt_ranges, range_counts) = + build_excerpt_ranges(&buffer_snapshot, &ranges, context_line_count); + + let excerpt_ids = self.push_excerpts(buffer, excerpt_ranges, cx); + + let mut anchor_ranges = Vec::new(); + let mut ranges = ranges.into_iter(); + for (excerpt_id, range_count) in excerpt_ids.into_iter().zip(range_counts.into_iter()) { + anchor_ranges.extend(ranges.by_ref().take(range_count).map(|range| { + let start = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: buffer_snapshot.anchor_after(range.start), + }; + let end = Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: buffer_snapshot.anchor_after(range.end), + }; + start..end + })) + } + anchor_ranges + } + + pub fn insert_excerpts_after( + &mut self, + prev_excerpt_id: ExcerptId, + buffer: Model, + ranges: impl IntoIterator>, + cx: &mut ModelContext, + ) -> Vec + where + O: text::ToOffset, + { + let mut ids = Vec::new(); + let mut next_excerpt_id = self.next_excerpt_id; + self.insert_excerpts_with_ids_after( + prev_excerpt_id, + buffer, + ranges.into_iter().map(|range| { + let id = ExcerptId(post_inc(&mut next_excerpt_id)); + ids.push(id); + (id, range) + }), + cx, + ); + ids + } + + pub fn insert_excerpts_with_ids_after( + &mut self, + prev_excerpt_id: ExcerptId, + buffer: Model, + ranges: impl IntoIterator)>, + cx: &mut ModelContext, + ) where + O: text::ToOffset, + { + assert_eq!(self.history.transaction_depth, 0); + let mut ranges = ranges.into_iter().peekable(); + if ranges.peek().is_none() { + return Default::default(); + } + + self.sync(cx); + + let buffer_id = buffer.read(cx).remote_id(); + let buffer_snapshot = buffer.read(cx).snapshot(); + + let mut buffers = self.buffers.borrow_mut(); + let buffer_state = buffers.entry(buffer_id).or_insert_with(|| BufferState { + last_version: buffer_snapshot.version().clone(), + last_parse_count: buffer_snapshot.parse_count(), + last_selections_update_count: buffer_snapshot.selections_update_count(), + last_diagnostics_update_count: buffer_snapshot.diagnostics_update_count(), + last_file_update_count: buffer_snapshot.file_update_count(), + last_git_diff_update_count: buffer_snapshot.git_diff_update_count(), + excerpts: Default::default(), + _subscriptions: [ + cx.observe(&buffer, |_, _, cx| cx.notify()), + cx.subscribe(&buffer, Self::on_buffer_event), + ], + buffer: buffer.clone(), + }); + + let mut snapshot = self.snapshot.borrow_mut(); + + let mut prev_locator = snapshot.excerpt_locator_for_id(prev_excerpt_id).clone(); + let mut new_excerpt_ids = mem::take(&mut snapshot.excerpt_ids); + let mut cursor = snapshot.excerpts.cursor::>(); + let mut new_excerpts = cursor.slice(&prev_locator, Bias::Right, &()); + prev_locator = cursor.start().unwrap_or(Locator::min_ref()).clone(); + + let edit_start = new_excerpts.summary().text.len; + new_excerpts.update_last( + |excerpt| { + excerpt.has_trailing_newline = true; + }, + &(), + ); + + let next_locator = if let Some(excerpt) = cursor.item() { + excerpt.locator.clone() + } else { + Locator::max() + }; + + let mut excerpts = Vec::new(); + while let Some((id, range)) = ranges.next() { + let locator = Locator::between(&prev_locator, &next_locator); + if let Err(ix) = buffer_state.excerpts.binary_search(&locator) { + buffer_state.excerpts.insert(ix, locator.clone()); + } + let range = ExcerptRange { + context: buffer_snapshot.anchor_before(&range.context.start) + ..buffer_snapshot.anchor_after(&range.context.end), + primary: range.primary.map(|primary| { + buffer_snapshot.anchor_before(&primary.start) + ..buffer_snapshot.anchor_after(&primary.end) + }), + }; + if id.0 >= self.next_excerpt_id { + self.next_excerpt_id = id.0 + 1; + } + excerpts.push((id, range.clone())); + let excerpt = Excerpt::new( + id, + locator.clone(), + buffer_id, + buffer_snapshot.clone(), + range, + ranges.peek().is_some() || cursor.item().is_some(), + ); + new_excerpts.push(excerpt, &()); + prev_locator = locator.clone(); + new_excerpt_ids.push(ExcerptIdMapping { id, locator }, &()); + } + + let edit_end = new_excerpts.summary().text.len; + + let suffix = cursor.suffix(&()); + let changed_trailing_excerpt = suffix.is_empty(); + new_excerpts.append(suffix, &()); + drop(cursor); + snapshot.excerpts = new_excerpts; + snapshot.excerpt_ids = new_excerpt_ids; + if changed_trailing_excerpt { + snapshot.trailing_excerpt_update_count += 1; + } + + self.subscriptions.publish_mut([Edit { + old: edit_start..edit_start, + new: edit_start..edit_end, + }]); + cx.emit(Event::Edited { + sigleton_buffer_edited: false, + }); + cx.emit(Event::ExcerptsAdded { + buffer, + predecessor: prev_excerpt_id, + excerpts, + }); + cx.notify(); + } + + pub fn clear(&mut self, cx: &mut ModelContext) { + self.sync(cx); + let ids = self.excerpt_ids(); + self.buffers.borrow_mut().clear(); + let mut snapshot = self.snapshot.borrow_mut(); + let prev_len = snapshot.len(); + snapshot.excerpts = Default::default(); + snapshot.trailing_excerpt_update_count += 1; + snapshot.is_dirty = false; + snapshot.has_conflict = false; + + self.subscriptions.publish_mut([Edit { + old: 0..prev_len, + new: 0..0, + }]); + cx.emit(Event::Edited { + sigleton_buffer_edited: false, + }); + cx.emit(Event::ExcerptsRemoved { ids }); + cx.notify(); + } + + pub fn excerpts_for_buffer( + &self, + buffer: &Model, + cx: &AppContext, + ) -> Vec<(ExcerptId, ExcerptRange)> { + let mut excerpts = Vec::new(); + let snapshot = self.read(cx); + let buffers = self.buffers.borrow(); + let mut cursor = snapshot.excerpts.cursor::>(); + for locator in buffers + .get(&buffer.read(cx).remote_id()) + .map(|state| &state.excerpts) + .into_iter() + .flatten() + { + cursor.seek_forward(&Some(locator), Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.locator == *locator { + excerpts.push((excerpt.id.clone(), excerpt.range.clone())); + } + } + } + + excerpts + } + + pub fn excerpt_ids(&self) -> Vec { + self.snapshot + .borrow() + .excerpts + .iter() + .map(|entry| entry.id) + .collect() + } + + pub fn excerpt_containing( + &self, + position: impl ToOffset, + cx: &AppContext, + ) -> Option<(ExcerptId, Model, Range)> { + let snapshot = self.read(cx); + let position = position.to_offset(&snapshot); + + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&position, Bias::Right, &()); + cursor + .item() + .or_else(|| snapshot.excerpts.last()) + .map(|excerpt| { + ( + excerpt.id.clone(), + self.buffers + .borrow() + .get(&excerpt.buffer_id) + .unwrap() + .buffer + .clone(), + excerpt.range.context.clone(), + ) + }) + } + + // If point is at the end of the buffer, the last excerpt is returned + pub fn point_to_buffer_offset( + &self, + point: T, + cx: &AppContext, + ) -> Option<(Model, usize, ExcerptId)> { + let snapshot = self.read(cx); + let offset = point.to_offset(&snapshot); + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + cursor.item().map(|excerpt| { + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let buffer_point = excerpt_start + offset - *cursor.start(); + let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); + + (buffer, buffer_point, excerpt.id) + }) + } + + pub fn range_to_buffer_ranges( + &self, + range: Range, + cx: &AppContext, + ) -> Vec<(Model, Range, ExcerptId)> { + let snapshot = self.read(cx); + let start = range.start.to_offset(&snapshot); + let end = range.end.to_offset(&snapshot); + + let mut result = Vec::new(); + let mut cursor = snapshot.excerpts.cursor::(); + cursor.seek(&start, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + while let Some(excerpt) = cursor.item() { + if *cursor.start() > end { + break; + } + + let mut end_before_newline = cursor.end(&()); + if excerpt.has_trailing_newline { + end_before_newline -= 1; + } + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start()); + let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start()); + let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); + result.push((buffer, start..end, excerpt.id)); + cursor.next(&()); + } + + result + } + + pub fn remove_excerpts( + &mut self, + excerpt_ids: impl IntoIterator, + cx: &mut ModelContext, + ) { + self.sync(cx); + let ids = excerpt_ids.into_iter().collect::>(); + if ids.is_empty() { + return; + } + + let mut buffers = self.buffers.borrow_mut(); + let mut snapshot = self.snapshot.borrow_mut(); + let mut new_excerpts = SumTree::new(); + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(); + let mut edits = Vec::new(); + let mut excerpt_ids = ids.iter().copied().peekable(); + + while let Some(excerpt_id) = excerpt_ids.next() { + // Seek to the next excerpt to remove, preserving any preceding excerpts. + let locator = snapshot.excerpt_locator_for_id(excerpt_id); + new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &()); + + if let Some(mut excerpt) = cursor.item() { + if excerpt.id != excerpt_id { + continue; + } + let mut old_start = cursor.start().1; + + // Skip over the removed excerpt. + 'remove_excerpts: loop { + if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) { + buffer_state.excerpts.retain(|l| l != &excerpt.locator); + if buffer_state.excerpts.is_empty() { + buffers.remove(&excerpt.buffer_id); + } + } + cursor.next(&()); + + // Skip over any subsequent excerpts that are also removed. + while let Some(&next_excerpt_id) = excerpt_ids.peek() { + let next_locator = snapshot.excerpt_locator_for_id(next_excerpt_id); + if let Some(next_excerpt) = cursor.item() { + if next_excerpt.locator == *next_locator { + excerpt_ids.next(); + excerpt = next_excerpt; + continue 'remove_excerpts; + } + } + break; + } + + break; + } + + // When removing the last excerpt, remove the trailing newline from + // the previous excerpt. + if cursor.item().is_none() && old_start > 0 { + old_start -= 1; + new_excerpts.update_last(|e| e.has_trailing_newline = false, &()); + } + + // Push an edit for the removal of this run of excerpts. + let old_end = cursor.start().1; + let new_start = new_excerpts.summary().text.len; + edits.push(Edit { + old: old_start..old_end, + new: new_start..new_start, + }); + } + } + let suffix = cursor.suffix(&()); + let changed_trailing_excerpt = suffix.is_empty(); + new_excerpts.append(suffix, &()); + drop(cursor); + snapshot.excerpts = new_excerpts; + + if changed_trailing_excerpt { + snapshot.trailing_excerpt_update_count += 1; + } + + self.subscriptions.publish_mut(edits); + cx.emit(Event::Edited { + sigleton_buffer_edited: false, + }); + cx.emit(Event::ExcerptsRemoved { ids }); + cx.notify(); + } + + pub fn wait_for_anchors<'a>( + &self, + anchors: impl 'a + Iterator, + cx: &mut ModelContext, + ) -> impl 'static + Future> { + let borrow = self.buffers.borrow(); + let mut error = None; + let mut futures = Vec::new(); + for anchor in anchors { + if let Some(buffer_id) = anchor.buffer_id { + if let Some(buffer) = borrow.get(&buffer_id) { + buffer.buffer.update(cx, |buffer, _| { + futures.push(buffer.wait_for_anchors([anchor.text_anchor])) + }); + } else { + error = Some(anyhow!( + "buffer {buffer_id} is not part of this multi-buffer" + )); + break; + } + } + } + async move { + if let Some(error) = error { + Err(error)?; + } + for future in futures { + future.await?; + } + Ok(()) + } + } + + pub fn text_anchor_for_position( + &self, + position: T, + cx: &AppContext, + ) -> Option<(Model, language2::Anchor)> { + let snapshot = self.read(cx); + let anchor = snapshot.anchor_before(position); + let buffer = self + .buffers + .borrow() + .get(&anchor.buffer_id?)? + .buffer + .clone(); + Some((buffer, anchor.text_anchor)) + } + + fn on_buffer_event( + &mut self, + _: Model, + event: &language2::Event, + cx: &mut ModelContext, + ) { + cx.emit(match event { + language2::Event::Edited => Event::Edited { + sigleton_buffer_edited: true, + }, + language2::Event::DirtyChanged => Event::DirtyChanged, + language2::Event::Saved => Event::Saved, + language2::Event::FileHandleChanged => Event::FileHandleChanged, + language2::Event::Reloaded => Event::Reloaded, + language2::Event::DiffBaseChanged => Event::DiffBaseChanged, + language2::Event::LanguageChanged => Event::LanguageChanged, + language2::Event::Reparsed => Event::Reparsed, + language2::Event::DiagnosticsUpdated => Event::DiagnosticsUpdated, + language2::Event::Closed => Event::Closed, + + // + language2::Event::Operation(_) => return, + }); + } + + pub fn all_buffers(&self) -> HashSet> { + self.buffers + .borrow() + .values() + .map(|state| state.buffer.clone()) + .collect() + } + + pub fn buffer(&self, buffer_id: u64) -> Option> { + self.buffers + .borrow() + .get(&buffer_id) + .map(|state| state.buffer.clone()) + } + + pub fn is_completion_trigger(&self, position: Anchor, text: &str, cx: &AppContext) -> bool { + let mut chars = text.chars(); + let char = if let Some(char) = chars.next() { + char + } else { + return false; + }; + if chars.next().is_some() { + return false; + } + + let snapshot = self.snapshot(cx); + let position = position.to_offset(&snapshot); + let scope = snapshot.language_scope_at(position); + if char_kind(&scope, char) == CharKind::Word { + return true; + } + + let anchor = snapshot.anchor_before(position); + anchor + .buffer_id + .and_then(|buffer_id| { + let buffer = self.buffers.borrow().get(&buffer_id)?.buffer.clone(); + Some( + buffer + .read(cx) + .completion_triggers() + .iter() + .any(|string| string == text), + ) + }) + .unwrap_or(false) + } + + pub fn language_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> Option> { + self.point_to_buffer_offset(point, cx) + .and_then(|(buffer, offset, _)| buffer.read(cx).language_at(offset)) + } + + pub fn settings_at<'a, T: ToOffset>( + &self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let mut language = None; + let mut file = None; + if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) { + let buffer = buffer.read(cx); + language = buffer.language_at(offset); + file = buffer.file(); + } + language_settings(language.as_ref(), file, cx) + } + + pub fn for_each_buffer(&self, mut f: impl FnMut(&Model)) { + self.buffers + .borrow() + .values() + .for_each(|state| f(&state.buffer)) + } + + pub fn title<'a>(&'a self, cx: &'a AppContext) -> Cow<'a, str> { + if let Some(title) = self.title.as_ref() { + return title.into(); + } + + if let Some(buffer) = self.as_singleton() { + if let Some(file) = buffer.read(cx).file() { + return file.file_name(cx).to_string_lossy(); + } + } + + "untitled".into() + } + + #[cfg(any(test, feature = "test-support"))] + pub fn is_parsing(&self, cx: &AppContext) -> bool { + self.as_singleton().unwrap().read(cx).is_parsing() + } + + fn sync(&self, cx: &AppContext) { + let mut snapshot = self.snapshot.borrow_mut(); + let mut excerpts_to_edit = Vec::new(); + let mut reparsed = false; + let mut diagnostics_updated = false; + let mut git_diff_updated = false; + let mut is_dirty = false; + let mut has_conflict = false; + let mut edited = false; + let mut buffers = self.buffers.borrow_mut(); + for buffer_state in buffers.values_mut() { + let buffer = buffer_state.buffer.read(cx); + let version = buffer.version(); + let parse_count = buffer.parse_count(); + let selections_update_count = buffer.selections_update_count(); + let diagnostics_update_count = buffer.diagnostics_update_count(); + let file_update_count = buffer.file_update_count(); + let git_diff_update_count = buffer.git_diff_update_count(); + + let buffer_edited = version.changed_since(&buffer_state.last_version); + let buffer_reparsed = parse_count > buffer_state.last_parse_count; + let buffer_selections_updated = + selections_update_count > buffer_state.last_selections_update_count; + let buffer_diagnostics_updated = + diagnostics_update_count > buffer_state.last_diagnostics_update_count; + let buffer_file_updated = file_update_count > buffer_state.last_file_update_count; + let buffer_git_diff_updated = + git_diff_update_count > buffer_state.last_git_diff_update_count; + if buffer_edited + || buffer_reparsed + || buffer_selections_updated + || buffer_diagnostics_updated + || buffer_file_updated + || buffer_git_diff_updated + { + buffer_state.last_version = version; + buffer_state.last_parse_count = parse_count; + buffer_state.last_selections_update_count = selections_update_count; + buffer_state.last_diagnostics_update_count = diagnostics_update_count; + buffer_state.last_file_update_count = file_update_count; + buffer_state.last_git_diff_update_count = git_diff_update_count; + excerpts_to_edit.extend( + buffer_state + .excerpts + .iter() + .map(|locator| (locator, buffer_state.buffer.clone(), buffer_edited)), + ); + } + + edited |= buffer_edited; + reparsed |= buffer_reparsed; + diagnostics_updated |= buffer_diagnostics_updated; + git_diff_updated |= buffer_git_diff_updated; + is_dirty |= buffer.is_dirty(); + has_conflict |= buffer.has_conflict(); + } + if edited { + snapshot.edit_count += 1; + } + if reparsed { + snapshot.parse_count += 1; + } + if diagnostics_updated { + snapshot.diagnostics_update_count += 1; + } + if git_diff_updated { + snapshot.git_diff_update_count += 1; + } + snapshot.is_dirty = is_dirty; + snapshot.has_conflict = has_conflict; + + excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator); + + let mut edits = Vec::new(); + let mut new_excerpts = SumTree::new(); + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, usize)>(); + + for (locator, buffer, buffer_edited) in excerpts_to_edit { + new_excerpts.append(cursor.slice(&Some(locator), Bias::Left, &()), &()); + let old_excerpt = cursor.item().unwrap(); + let buffer = buffer.read(cx); + let buffer_id = buffer.remote_id(); + + let mut new_excerpt; + if buffer_edited { + edits.extend( + buffer + .edits_since_in_range::( + old_excerpt.buffer.version(), + old_excerpt.range.context.clone(), + ) + .map(|mut edit| { + let excerpt_old_start = cursor.start().1; + let excerpt_new_start = new_excerpts.summary().text.len; + edit.old.start += excerpt_old_start; + edit.old.end += excerpt_old_start; + edit.new.start += excerpt_new_start; + edit.new.end += excerpt_new_start; + edit + }), + ); + + new_excerpt = Excerpt::new( + old_excerpt.id, + locator.clone(), + buffer_id, + buffer.snapshot(), + old_excerpt.range.clone(), + old_excerpt.has_trailing_newline, + ); + } else { + new_excerpt = old_excerpt.clone(); + new_excerpt.buffer = buffer.snapshot(); + } + + new_excerpts.push(new_excerpt, &()); + cursor.next(&()); + } + new_excerpts.append(cursor.suffix(&()), &()); + + drop(cursor); + snapshot.excerpts = new_excerpts; + + self.subscriptions.publish(edits); + } +} + +#[cfg(any(test, feature = "test-support"))] +impl MultiBuffer { + pub fn build_simple(text: &str, cx: &mut gpui2::AppContext) -> Model { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)); + cx.build_model(|cx| Self::singleton(buffer, cx)) + } + + pub fn build_multi( + excerpts: [(&str, Vec>); COUNT], + cx: &mut gpui2::AppContext, + ) -> Model { + let multi = cx.build_model(|_| Self::new(0)); + for (text, ranges) in excerpts { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text)); + let excerpt_ranges = ranges.into_iter().map(|range| ExcerptRange { + context: range, + primary: None, + }); + multi.update(cx, |multi, cx| { + multi.push_excerpts(buffer, excerpt_ranges, cx) + }); + } + + multi + } + + pub fn build_from_buffer(buffer: Model, cx: &mut gpui2::AppContext) -> Model { + cx.build_model(|cx| Self::singleton(buffer, cx)) + } + + pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui2::AppContext) -> Model { + cx.build_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + let mutation_count = rng.gen_range(1..=5); + multibuffer.randomly_edit_excerpts(rng, mutation_count, cx); + multibuffer + }) + } + + pub fn randomly_edit( + &mut self, + rng: &mut impl rand::Rng, + edit_count: usize, + cx: &mut ModelContext, + ) { + use util::RandomCharIter; + + let snapshot = self.read(cx); + let mut edits: Vec<(Range, Arc)> = Vec::new(); + let mut last_end = None; + for _ in 0..edit_count { + if last_end.map_or(false, |last_end| last_end >= snapshot.len()) { + break; + } + + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let end = snapshot.clip_offset(rng.gen_range(new_start..=snapshot.len()), Bias::Right); + let start = snapshot.clip_offset(rng.gen_range(new_start..=end), Bias::Right); + last_end = Some(end); + + let mut range = start..end; + if rng.gen_bool(0.2) { + mem::swap(&mut range.start, &mut range.end); + } + + let new_text_len = rng.gen_range(0..10); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + edits.push((range, new_text.into())); + } + log::info!("mutating multi-buffer with {:?}", edits); + drop(snapshot); + + self.edit(edits, None, cx); + } + + pub fn randomly_edit_excerpts( + &mut self, + rng: &mut impl rand::Rng, + mutation_count: usize, + cx: &mut ModelContext, + ) { + use rand::prelude::*; + use std::env; + use util::RandomCharIter; + + let max_excerpts = env::var("MAX_EXCERPTS") + .map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable")) + .unwrap_or(5); + + let mut buffers = Vec::new(); + for _ in 0..mutation_count { + if rng.gen_bool(0.05) { + log::info!("Clearing multi-buffer"); + self.clear(cx); + continue; + } + + let excerpt_ids = self.excerpt_ids(); + if excerpt_ids.is_empty() || (rng.gen() && excerpt_ids.len() < max_excerpts) { + let buffer_handle = if rng.gen() || self.buffers.borrow().is_empty() { + let text = RandomCharIter::new(&mut *rng).take(10).collect::(); + buffers + .push(cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text))); + let buffer = buffers.last().unwrap().read(cx); + log::info!( + "Creating new buffer {} with text: {:?}", + buffer.remote_id(), + buffer.text() + ); + buffers.last().unwrap().clone() + } else { + self.buffers + .borrow() + .values() + .choose(rng) + .unwrap() + .buffer + .clone() + }; + + let buffer = buffer_handle.read(cx); + let buffer_text = buffer.text(); + let ranges = (0..rng.gen_range(0..5)) + .map(|_| { + let end_ix = + buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right); + let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + ExcerptRange { + context: start_ix..end_ix, + primary: None, + } + }) + .collect::>(); + log::info!( + "Inserting excerpts from buffer {} and ranges {:?}: {:?}", + buffer_handle.read(cx).remote_id(), + ranges.iter().map(|r| &r.context).collect::>(), + ranges + .iter() + .map(|r| &buffer_text[r.context.clone()]) + .collect::>() + ); + + let excerpt_id = self.push_excerpts(buffer_handle.clone(), ranges, cx); + log::info!("Inserted with ids: {:?}", excerpt_id); + } else { + let remove_count = rng.gen_range(1..=excerpt_ids.len()); + let mut excerpts_to_remove = excerpt_ids + .choose_multiple(rng, remove_count) + .cloned() + .collect::>(); + let snapshot = self.snapshot.borrow(); + excerpts_to_remove.sort_unstable_by(|a, b| a.cmp(b, &*snapshot)); + drop(snapshot); + log::info!("Removing excerpts {:?}", excerpts_to_remove); + self.remove_excerpts(excerpts_to_remove, cx); + } + } + } + + pub fn randomly_mutate( + &mut self, + rng: &mut impl rand::Rng, + mutation_count: usize, + cx: &mut ModelContext, + ) { + use rand::prelude::*; + + if rng.gen_bool(0.7) || self.singleton { + let buffer = self + .buffers + .borrow() + .values() + .choose(rng) + .map(|state| state.buffer.clone()); + + if let Some(buffer) = buffer { + buffer.update(cx, |buffer, cx| { + if rng.gen() { + buffer.randomly_edit(rng, mutation_count, cx); + } else { + buffer.randomly_undo_redo(rng, cx); + } + }); + } else { + self.randomly_edit(rng, mutation_count, cx); + } + } else { + self.randomly_edit_excerpts(rng, mutation_count, cx); + } + + self.check_invariants(cx); + } + + fn check_invariants(&self, cx: &mut ModelContext) { + let snapshot = self.read(cx); + let excerpts = snapshot.excerpts.items(&()); + let excerpt_ids = snapshot.excerpt_ids.items(&()); + + for (ix, excerpt) in excerpts.iter().enumerate() { + if ix == 0 { + if excerpt.locator <= Locator::min() { + panic!("invalid first excerpt locator {:?}", excerpt.locator); + } + } else { + if excerpt.locator <= excerpts[ix - 1].locator { + panic!("excerpts are out-of-order: {:?}", excerpts); + } + } + } + + for (ix, entry) in excerpt_ids.iter().enumerate() { + if ix == 0 { + if entry.id.cmp(&ExcerptId::min(), &*snapshot).is_le() { + panic!("invalid first excerpt id {:?}", entry.id); + } + } else { + if entry.id <= excerpt_ids[ix - 1].id { + panic!("excerpt ids are out-of-order: {:?}", excerpt_ids); + } + } + } + } +} + +impl EventEmitter for MultiBuffer { + type Event = Event; +} + +impl MultiBufferSnapshot { + pub fn text(&self) -> String { + self.chunks(0..self.len(), false) + .map(|chunk| chunk.text) + .collect() + } + + pub fn reversed_chars_at(&self, position: T) -> impl Iterator + '_ { + let mut offset = position.to_offset(self); + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Left, &()); + let mut excerpt_chunks = cursor.item().map(|excerpt| { + let end_before_footer = cursor.start() + excerpt.text_summary.len; + let start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let end = start + (cmp::min(offset, end_before_footer) - cursor.start()); + excerpt.buffer.reversed_chunks_in_range(start..end) + }); + iter::from_fn(move || { + if offset == *cursor.start() { + cursor.prev(&()); + let excerpt = cursor.item()?; + excerpt_chunks = Some( + excerpt + .buffer + .reversed_chunks_in_range(excerpt.range.context.clone()), + ); + } + + let excerpt = cursor.item().unwrap(); + if offset == cursor.end(&()) && excerpt.has_trailing_newline { + offset -= 1; + Some("\n") + } else { + let chunk = excerpt_chunks.as_mut().unwrap().next().unwrap(); + offset -= chunk.len(); + Some(chunk) + } + }) + .flat_map(|c| c.chars().rev()) + } + + pub fn chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.text_for_range(offset..self.len()) + .flat_map(|chunk| chunk.chars()) + } + + pub fn text_for_range(&self, range: Range) -> impl Iterator + '_ { + self.chunks(range, false).map(|chunk| chunk.text) + } + + pub fn is_line_blank(&self, row: u32) -> bool { + self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row))) + .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) + } + + pub fn contains_str_at(&self, position: T, needle: &str) -> bool + where + T: ToOffset, + { + let position = position.to_offset(self); + position == self.clip_offset(position, Bias::Left) + && self + .bytes_in_range(position..self.len()) + .flatten() + .copied() + .take(needle.len()) + .eq(needle.bytes()) + } + + pub fn surrounding_word(&self, start: T) -> (Range, Option) { + let mut start = start.to_offset(self); + let mut end = start; + let mut next_chars = self.chars_at(start).peekable(); + let mut prev_chars = self.reversed_chars_at(start).peekable(); + + let scope = self.language_scope_at(start); + let kind = |c| char_kind(&scope, c); + let word_kind = cmp::max( + prev_chars.peek().copied().map(kind), + next_chars.peek().copied().map(kind), + ); + + for ch in prev_chars { + if Some(kind(ch)) == word_kind && ch != '\n' { + start -= ch.len_utf8(); + } else { + break; + } + } + + for ch in next_chars { + if Some(kind(ch)) == word_kind && ch != '\n' { + end += ch.len_utf8(); + } else { + break; + } + } + + (start..end, word_kind) + } + + pub fn as_singleton(&self) -> Option<(&ExcerptId, u64, &BufferSnapshot)> { + if self.singleton { + self.excerpts + .iter() + .next() + .map(|e| (&e.id, e.buffer_id, &e.buffer)) + } else { + None + } + } + + pub fn len(&self) -> usize { + self.excerpts.summary().text.len + } + + pub fn is_empty(&self) -> bool { + self.excerpts.summary().text.len == 0 + } + + pub fn max_buffer_row(&self) -> u32 { + self.excerpts.summary().max_buffer_row + } + + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_offset(offset, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let buffer_offset = excerpt + .buffer + .clip_offset(excerpt_start + (offset - cursor.start()), bias); + buffer_offset.saturating_sub(excerpt_start) + } else { + 0 + }; + cursor.start() + overshoot + } + + pub fn clip_point(&self, point: Point, bias: Bias) -> Point { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_point(point, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&point, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .clip_point(excerpt_start + (point - cursor.start()), bias); + buffer_point.saturating_sub(excerpt_start) + } else { + Point::zero() + }; + *cursor.start() + overshoot + } + + pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_offset_utf16(offset, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt.range.context.start.to_offset_utf16(&excerpt.buffer); + let buffer_offset = excerpt + .buffer + .clip_offset_utf16(excerpt_start + (offset - cursor.start()), bias); + OffsetUtf16(buffer_offset.0.saturating_sub(excerpt_start.0)) + } else { + OffsetUtf16(0) + }; + *cursor.start() + overshoot + } + + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.clip_point_utf16(point, bias); + } + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&point.0, Bias::Right, &()); + let overshoot = if let Some(excerpt) = cursor.item() { + let excerpt_start = excerpt + .buffer + .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); + let buffer_point = excerpt + .buffer + .clip_point_utf16(Unclipped(excerpt_start + (point.0 - cursor.start())), bias); + buffer_point.saturating_sub(excerpt_start) + } else { + PointUtf16::zero() + }; + *cursor.start() + overshoot + } + + pub fn bytes_in_range(&self, range: Range) -> MultiBufferBytes { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut excerpts = self.excerpts.cursor::(); + excerpts.seek(&range.start, Bias::Right, &()); + + let mut chunk = &[][..]; + let excerpt_bytes = if let Some(excerpt) = excerpts.item() { + let mut excerpt_bytes = excerpt + .bytes_in_range(range.start - excerpts.start()..range.end - excerpts.start()); + chunk = excerpt_bytes.next().unwrap_or(&[][..]); + Some(excerpt_bytes) + } else { + None + }; + MultiBufferBytes { + range, + excerpts, + excerpt_bytes, + chunk, + } + } + + pub fn reversed_bytes_in_range( + &self, + range: Range, + ) -> ReversedMultiBufferBytes { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut excerpts = self.excerpts.cursor::(); + excerpts.seek(&range.end, Bias::Left, &()); + + let mut chunk = &[][..]; + let excerpt_bytes = if let Some(excerpt) = excerpts.item() { + let mut excerpt_bytes = excerpt.reversed_bytes_in_range( + range.start - excerpts.start()..range.end - excerpts.start(), + ); + chunk = excerpt_bytes.next().unwrap_or(&[][..]); + Some(excerpt_bytes) + } else { + None + }; + + ReversedMultiBufferBytes { + range, + excerpts, + excerpt_bytes, + chunk, + } + } + + pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows { + let mut result = MultiBufferRows { + buffer_row_range: 0..0, + excerpts: self.excerpts.cursor(), + }; + result.seek(start_row); + result + } + + pub fn chunks(&self, range: Range, language_aware: bool) -> MultiBufferChunks { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let mut chunks = MultiBufferChunks { + range: range.clone(), + excerpts: self.excerpts.cursor(), + excerpt_chunks: None, + language_aware, + }; + chunks.seek(range.start); + chunks + } + + pub fn offset_to_point(&self, offset: usize) -> Point { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_to_point(offset); + } + + let mut cursor = self.excerpts.cursor::<(usize, Point)>(); + cursor.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_point) = cursor.start(); + let overshoot = offset - start_offset; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .offset_to_point(excerpt_start_offset + overshoot); + *start_point + (buffer_point - excerpt_start_point) + } else { + self.excerpts.summary().text.lines + } + } + + pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_to_point_utf16(offset); + } + + let mut cursor = self.excerpts.cursor::<(usize, PointUtf16)>(); + cursor.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_point) = cursor.start(); + let overshoot = offset - start_offset; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt.range.context.start.to_point_utf16(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .offset_to_point_utf16(excerpt_start_offset + overshoot); + *start_point + (buffer_point - excerpt_start_point) + } else { + self.excerpts.summary().text.lines_utf16() + } + } + + pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.point_to_point_utf16(point); + } + + let mut cursor = self.excerpts.cursor::<(Point, PointUtf16)>(); + cursor.seek(&point, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_point) = cursor.start(); + let overshoot = point - start_offset; + let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer); + let excerpt_start_point_utf16 = + excerpt.range.context.start.to_point_utf16(&excerpt.buffer); + let buffer_point = excerpt + .buffer + .point_to_point_utf16(excerpt_start_point + overshoot); + *start_point + (buffer_point - excerpt_start_point_utf16) + } else { + self.excerpts.summary().text.lines_utf16() + } + } + + pub fn point_to_offset(&self, point: Point) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.point_to_offset(point); + } + + let mut cursor = self.excerpts.cursor::<(Point, usize)>(); + cursor.seek(&point, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_point, start_offset) = cursor.start(); + let overshoot = point - start_point; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt.range.context.start.to_point(&excerpt.buffer); + let buffer_offset = excerpt + .buffer + .point_to_offset(excerpt_start_point + overshoot); + *start_offset + buffer_offset - excerpt_start_offset + } else { + self.excerpts.summary().text.len + } + } + + pub fn offset_utf16_to_offset(&self, offset_utf16: OffsetUtf16) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_utf16_to_offset(offset_utf16); + } + + let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>(); + cursor.seek(&offset_utf16, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset_utf16, start_offset) = cursor.start(); + let overshoot = offset_utf16 - start_offset_utf16; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_offset_utf16 = + excerpt.buffer.offset_to_offset_utf16(excerpt_start_offset); + let buffer_offset = excerpt + .buffer + .offset_utf16_to_offset(excerpt_start_offset_utf16 + overshoot); + *start_offset + (buffer_offset - excerpt_start_offset) + } else { + self.excerpts.summary().text.len + } + } + + pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.offset_to_offset_utf16(offset); + } + + let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>(); + cursor.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_offset, start_offset_utf16) = cursor.start(); + let overshoot = offset - start_offset; + let excerpt_start_offset_utf16 = + excerpt.range.context.start.to_offset_utf16(&excerpt.buffer); + let excerpt_start_offset = excerpt + .buffer + .offset_utf16_to_offset(excerpt_start_offset_utf16); + let buffer_offset_utf16 = excerpt + .buffer + .offset_to_offset_utf16(excerpt_start_offset + overshoot); + *start_offset_utf16 + (buffer_offset_utf16 - excerpt_start_offset_utf16) + } else { + self.excerpts.summary().text.len_utf16 + } + } + + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer.point_utf16_to_offset(point); + } + + let mut cursor = self.excerpts.cursor::<(PointUtf16, usize)>(); + cursor.seek(&point, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let (start_point, start_offset) = cursor.start(); + let overshoot = point - start_point; + let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_start_point = excerpt + .buffer + .offset_to_point_utf16(excerpt.range.context.start.to_offset(&excerpt.buffer)); + let buffer_offset = excerpt + .buffer + .point_utf16_to_offset(excerpt_start_point + overshoot); + *start_offset + (buffer_offset - excerpt_start_offset) + } else { + self.excerpts.summary().text.len + } + } + + pub fn point_to_buffer_offset( + &self, + point: T, + ) -> Option<(&BufferSnapshot, usize)> { + let offset = point.to_offset(&self); + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&offset, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + cursor.item().map(|excerpt| { + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let buffer_point = excerpt_start + offset - *cursor.start(); + (&excerpt.buffer, buffer_point) + }) + } + + pub fn suggested_indents( + &self, + rows: impl IntoIterator, + cx: &AppContext, + ) -> BTreeMap { + let mut result = BTreeMap::new(); + + let mut rows_for_excerpt = Vec::new(); + let mut cursor = self.excerpts.cursor::(); + let mut rows = rows.into_iter().peekable(); + let mut prev_row = u32::MAX; + let mut prev_language_indent_size = IndentSize::default(); + + while let Some(row) = rows.next() { + cursor.seek(&Point::new(row, 0), Bias::Right, &()); + let excerpt = match cursor.item() { + Some(excerpt) => excerpt, + _ => continue, + }; + + // Retrieve the language and indent size once for each disjoint region being indented. + let single_indent_size = if row.saturating_sub(1) == prev_row { + prev_language_indent_size + } else { + excerpt + .buffer + .language_indent_size_at(Point::new(row, 0), cx) + }; + prev_language_indent_size = single_indent_size; + prev_row = row; + + let start_buffer_row = excerpt.range.context.start.to_point(&excerpt.buffer).row; + let start_multibuffer_row = cursor.start().row; + + rows_for_excerpt.push(row); + while let Some(next_row) = rows.peek().copied() { + if cursor.end(&()).row > next_row { + rows_for_excerpt.push(next_row); + rows.next(); + } else { + break; + } + } + + let buffer_rows = rows_for_excerpt + .drain(..) + .map(|row| start_buffer_row + row - start_multibuffer_row); + let buffer_indents = excerpt + .buffer + .suggested_indents(buffer_rows, single_indent_size); + let multibuffer_indents = buffer_indents + .into_iter() + .map(|(row, indent)| (start_multibuffer_row + row - start_buffer_row, indent)); + result.extend(multibuffer_indents); + } + + result + } + + pub fn indent_size_for_line(&self, row: u32) -> IndentSize { + if let Some((buffer, range)) = self.buffer_line_for_row(row) { + let mut size = buffer.indent_size_for_line(range.start.row); + size.len = size + .len + .min(range.end.column) + .saturating_sub(range.start.column); + size + } else { + IndentSize::spaces(0) + } + } + + pub fn prev_non_blank_row(&self, mut row: u32) -> Option { + while row > 0 { + row -= 1; + if !self.is_line_blank(row) { + return Some(row); + } + } + None + } + + pub fn line_len(&self, row: u32) -> u32 { + if let Some((_, range)) = self.buffer_line_for_row(row) { + range.end.column - range.start.column + } else { + 0 + } + } + + pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range)> { + let mut cursor = self.excerpts.cursor::(); + let point = Point::new(row, 0); + cursor.seek(&point, Bias::Right, &()); + if cursor.item().is_none() && *cursor.start() == point { + cursor.prev(&()); + } + if let Some(excerpt) = cursor.item() { + let overshoot = row - cursor.start().row; + let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer); + let excerpt_end = excerpt.range.context.end.to_point(&excerpt.buffer); + let buffer_row = excerpt_start.row + overshoot; + let line_start = Point::new(buffer_row, 0); + let line_end = Point::new(buffer_row, excerpt.buffer.line_len(buffer_row)); + return Some(( + &excerpt.buffer, + line_start.max(excerpt_start)..line_end.min(excerpt_end), + )); + } + None + } + + pub fn max_point(&self) -> Point { + self.text_summary().lines + } + + pub fn text_summary(&self) -> TextSummary { + self.excerpts.summary().text.clone() + } + + pub fn text_summary_for_range(&self, range: Range) -> D + where + D: TextDimension, + O: ToOffset, + { + let mut summary = D::default(); + let mut range = range.start.to_offset(self)..range.end.to_offset(self); + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&range.start, Bias::Right, &()); + if let Some(excerpt) = cursor.item() { + let mut end_before_newline = cursor.end(&()); + if excerpt.has_trailing_newline { + end_before_newline -= 1; + } + + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let start_in_excerpt = excerpt_start + (range.start - cursor.start()); + let end_in_excerpt = + excerpt_start + (cmp::min(end_before_newline, range.end) - cursor.start()); + summary.add_assign( + &excerpt + .buffer + .text_summary_for_range(start_in_excerpt..end_in_excerpt), + ); + + if range.end > end_before_newline { + summary.add_assign(&D::from_text_summary(&TextSummary::from("\n"))); + } + + cursor.next(&()); + } + + if range.end > *cursor.start() { + summary.add_assign(&D::from_text_summary(&cursor.summary::<_, TextSummary>( + &range.end, + Bias::Right, + &(), + ))); + if let Some(excerpt) = cursor.item() { + range.end = cmp::max(*cursor.start(), range.end); + + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let end_in_excerpt = excerpt_start + (range.end - cursor.start()); + summary.add_assign( + &excerpt + .buffer + .text_summary_for_range(excerpt_start..end_in_excerpt), + ); + } + } + + summary + } + + pub fn summary_for_anchor(&self, anchor: &Anchor) -> D + where + D: TextDimension + Ord + Sub, + { + let mut cursor = self.excerpts.cursor::(); + let locator = self.excerpt_locator_for_id(anchor.excerpt_id); + + cursor.seek(locator, Bias::Left, &()); + if cursor.item().is_none() { + cursor.next(&()); + } + + let mut position = D::from_text_summary(&cursor.start().text); + if let Some(excerpt) = cursor.item() { + if excerpt.id == anchor.excerpt_id { + let excerpt_buffer_start = + excerpt.range.context.start.summary::(&excerpt.buffer); + let excerpt_buffer_end = excerpt.range.context.end.summary::(&excerpt.buffer); + let buffer_position = cmp::min( + excerpt_buffer_end, + anchor.text_anchor.summary::(&excerpt.buffer), + ); + if buffer_position > excerpt_buffer_start { + position.add_assign(&(buffer_position - excerpt_buffer_start)); + } + } + } + position + } + + pub fn summaries_for_anchors<'a, D, I>(&'a self, anchors: I) -> Vec + where + D: TextDimension + Ord + Sub, + I: 'a + IntoIterator, + { + if let Some((_, _, buffer)) = self.as_singleton() { + return buffer + .summaries_for_anchors(anchors.into_iter().map(|a| &a.text_anchor)) + .collect(); + } + + let mut anchors = anchors.into_iter().peekable(); + let mut cursor = self.excerpts.cursor::(); + let mut summaries = Vec::new(); + while let Some(anchor) = anchors.peek() { + let excerpt_id = anchor.excerpt_id; + let excerpt_anchors = iter::from_fn(|| { + let anchor = anchors.peek()?; + if anchor.excerpt_id == excerpt_id { + Some(&anchors.next().unwrap().text_anchor) + } else { + None + } + }); + + let locator = self.excerpt_locator_for_id(excerpt_id); + cursor.seek_forward(locator, Bias::Left, &()); + if cursor.item().is_none() { + cursor.next(&()); + } + + let position = D::from_text_summary(&cursor.start().text); + if let Some(excerpt) = cursor.item() { + if excerpt.id == excerpt_id { + let excerpt_buffer_start = + excerpt.range.context.start.summary::(&excerpt.buffer); + let excerpt_buffer_end = + excerpt.range.context.end.summary::(&excerpt.buffer); + summaries.extend( + excerpt + .buffer + .summaries_for_anchors::(excerpt_anchors) + .map(move |summary| { + let summary = cmp::min(excerpt_buffer_end.clone(), summary); + let mut position = position.clone(); + let excerpt_buffer_start = excerpt_buffer_start.clone(); + if summary > excerpt_buffer_start { + position.add_assign(&(summary - excerpt_buffer_start)); + } + position + }), + ); + continue; + } + } + + summaries.extend(excerpt_anchors.map(|_| position.clone())); + } + + summaries + } + + pub fn refresh_anchors<'a, I>(&'a self, anchors: I) -> Vec<(usize, Anchor, bool)> + where + I: 'a + IntoIterator, + { + let mut anchors = anchors.into_iter().enumerate().peekable(); + let mut cursor = self.excerpts.cursor::>(); + cursor.next(&()); + + let mut result = Vec::new(); + + while let Some((_, anchor)) = anchors.peek() { + let old_excerpt_id = anchor.excerpt_id; + + // Find the location where this anchor's excerpt should be. + let old_locator = self.excerpt_locator_for_id(old_excerpt_id); + cursor.seek_forward(&Some(old_locator), Bias::Left, &()); + + if cursor.item().is_none() { + cursor.next(&()); + } + + let next_excerpt = cursor.item(); + let prev_excerpt = cursor.prev_item(); + + // Process all of the anchors for this excerpt. + while let Some((_, anchor)) = anchors.peek() { + if anchor.excerpt_id != old_excerpt_id { + break; + } + let (anchor_ix, anchor) = anchors.next().unwrap(); + let mut anchor = *anchor; + + // Leave min and max anchors unchanged if invalid or + // if the old excerpt still exists at this location + let mut kept_position = next_excerpt + .map_or(false, |e| e.id == old_excerpt_id && e.contains(&anchor)) + || old_excerpt_id == ExcerptId::max() + || old_excerpt_id == ExcerptId::min(); + + // If the old excerpt no longer exists at this location, then attempt to + // find an equivalent position for this anchor in an adjacent excerpt. + if !kept_position { + for excerpt in [next_excerpt, prev_excerpt].iter().filter_map(|e| *e) { + if excerpt.contains(&anchor) { + anchor.excerpt_id = excerpt.id.clone(); + kept_position = true; + break; + } + } + } + + // If there's no adjacent excerpt that contains the anchor's position, + // then report that the anchor has lost its position. + if !kept_position { + anchor = if let Some(excerpt) = next_excerpt { + let mut text_anchor = excerpt + .range + .context + .start + .bias(anchor.text_anchor.bias, &excerpt.buffer); + if text_anchor + .cmp(&excerpt.range.context.end, &excerpt.buffer) + .is_gt() + { + text_anchor = excerpt.range.context.end; + } + Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor, + } + } else if let Some(excerpt) = prev_excerpt { + let mut text_anchor = excerpt + .range + .context + .end + .bias(anchor.text_anchor.bias, &excerpt.buffer); + if text_anchor + .cmp(&excerpt.range.context.start, &excerpt.buffer) + .is_lt() + { + text_anchor = excerpt.range.context.start; + } + Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor, + } + } else if anchor.text_anchor.bias == Bias::Left { + Anchor::min() + } else { + Anchor::max() + }; + } + + result.push((anchor_ix, anchor, kept_position)); + } + } + result.sort_unstable_by(|a, b| a.1.cmp(&b.1, self)); + result + } + + pub fn anchor_before(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Left) + } + + pub fn anchor_after(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Right) + } + + pub fn anchor_at(&self, position: T, mut bias: Bias) -> Anchor { + let offset = position.to_offset(self); + if let Some((excerpt_id, buffer_id, buffer)) = self.as_singleton() { + return Anchor { + buffer_id: Some(buffer_id), + excerpt_id: excerpt_id.clone(), + text_anchor: buffer.anchor_at(offset, bias), + }; + } + + let mut cursor = self.excerpts.cursor::<(usize, Option)>(); + cursor.seek(&offset, Bias::Right, &()); + if cursor.item().is_none() && offset == cursor.start().0 && bias == Bias::Left { + cursor.prev(&()); + } + if let Some(excerpt) = cursor.item() { + let mut overshoot = offset.saturating_sub(cursor.start().0); + if excerpt.has_trailing_newline && offset == cursor.end(&()).0 { + overshoot -= 1; + bias = Bias::Right; + } + + let buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let text_anchor = + excerpt.clip_anchor(excerpt.buffer.anchor_at(buffer_start + overshoot, bias)); + Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor, + } + } else if offset == 0 && bias == Bias::Left { + Anchor::min() + } else { + Anchor::max() + } + } + + pub fn anchor_in_excerpt(&self, excerpt_id: ExcerptId, text_anchor: text::Anchor) -> Anchor { + let locator = self.excerpt_locator_for_id(excerpt_id); + let mut cursor = self.excerpts.cursor::>(); + cursor.seek(locator, Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.id == excerpt_id { + let text_anchor = excerpt.clip_anchor(text_anchor); + drop(cursor); + return Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id, + text_anchor, + }; + } + } + panic!("excerpt not found"); + } + + pub fn can_resolve(&self, anchor: &Anchor) -> bool { + if anchor.excerpt_id == ExcerptId::min() || anchor.excerpt_id == ExcerptId::max() { + true + } else if let Some(excerpt) = self.excerpt(anchor.excerpt_id) { + excerpt.buffer.can_resolve(&anchor.text_anchor) + } else { + false + } + } + + pub fn excerpts( + &self, + ) -> impl Iterator)> { + self.excerpts + .iter() + .map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone())) + } + + pub fn excerpt_boundaries_in_range( + &self, + range: R, + ) -> impl Iterator + '_ + where + R: RangeBounds, + T: ToOffset, + { + let start_offset; + let start = match range.start_bound() { + Bound::Included(start) => { + start_offset = start.to_offset(self); + Bound::Included(start_offset) + } + Bound::Excluded(start) => { + start_offset = start.to_offset(self); + Bound::Excluded(start_offset) + } + Bound::Unbounded => { + start_offset = 0; + Bound::Unbounded + } + }; + let end = match range.end_bound() { + Bound::Included(end) => Bound::Included(end.to_offset(self)), + Bound::Excluded(end) => Bound::Excluded(end.to_offset(self)), + Bound::Unbounded => Bound::Unbounded, + }; + let bounds = (start, end); + + let mut cursor = self.excerpts.cursor::<(usize, Point)>(); + cursor.seek(&start_offset, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + if !bounds.contains(&cursor.start().0) { + cursor.next(&()); + } + + let mut prev_buffer_id = cursor.prev_item().map(|excerpt| excerpt.buffer_id); + std::iter::from_fn(move || { + if self.singleton { + None + } else if bounds.contains(&cursor.start().0) { + let excerpt = cursor.item()?; + let starts_new_buffer = Some(excerpt.buffer_id) != prev_buffer_id; + let boundary = ExcerptBoundary { + id: excerpt.id.clone(), + row: cursor.start().1.row, + buffer: excerpt.buffer.clone(), + range: excerpt.range.clone(), + starts_new_buffer, + }; + + prev_buffer_id = Some(excerpt.buffer_id); + cursor.next(&()); + Some(boundary) + } else { + None + } + }) + } + + pub fn edit_count(&self) -> usize { + self.edit_count + } + + pub fn parse_count(&self) -> usize { + self.parse_count + } + + /// Returns the smallest enclosing bracket ranges containing the given range or + /// None if no brackets contain range or the range is not contained in a single + /// excerpt + pub fn innermost_enclosing_bracket_ranges( + &self, + range: Range, + ) -> Option<(Range, Range)> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + // Get the ranges of the innermost pair of brackets. + let mut result: Option<(Range, Range)> = None; + + let Some(enclosing_bracket_ranges) = self.enclosing_bracket_ranges(range.clone()) else { + return None; + }; + + for (open, close) in enclosing_bracket_ranges { + let len = close.end - open.start; + + if let Some((existing_open, existing_close)) = &result { + let existing_len = existing_close.end - existing_open.start; + if len > existing_len { + continue; + } + } + + result = Some((open, close)); + } + + result + } + + /// Returns enclosing bracket ranges containing the given range or returns None if the range is + /// not contained in a single excerpt + pub fn enclosing_bracket_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option, Range)> + 'a> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + self.bracket_ranges(range.clone()).map(|range_pairs| { + range_pairs + .filter(move |(open, close)| open.start <= range.start && close.end >= range.end) + }) + } + + /// Returns bracket range pairs overlapping the given `range` or returns None if the `range` is + /// not contained in a single excerpt + pub fn bracket_ranges<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option, Range)> + 'a> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + let excerpt = self.excerpt_containing(range.clone()); + excerpt.map(|(excerpt, excerpt_offset)| { + let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; + + let start_in_buffer = excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); + let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); + + excerpt + .buffer + .bracket_ranges(start_in_buffer..end_in_buffer) + .filter_map(move |(start_bracket_range, end_bracket_range)| { + if start_bracket_range.start < excerpt_buffer_start + || end_bracket_range.end > excerpt_buffer_end + { + return None; + } + + let mut start_bracket_range = start_bracket_range.clone(); + start_bracket_range.start = + excerpt_offset + (start_bracket_range.start - excerpt_buffer_start); + start_bracket_range.end = + excerpt_offset + (start_bracket_range.end - excerpt_buffer_start); + + let mut end_bracket_range = end_bracket_range.clone(); + end_bracket_range.start = + excerpt_offset + (end_bracket_range.start - excerpt_buffer_start); + end_bracket_range.end = + excerpt_offset + (end_bracket_range.end - excerpt_buffer_start); + Some((start_bracket_range, end_bracket_range)) + }) + }) + } + + pub fn diagnostics_update_count(&self) -> usize { + self.diagnostics_update_count + } + + pub fn git_diff_update_count(&self) -> usize { + self.git_diff_update_count + } + + pub fn trailing_excerpt_update_count(&self) -> usize { + self.trailing_excerpt_update_count + } + + pub fn file_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc> { + self.point_to_buffer_offset(point) + .and_then(|(buffer, _)| buffer.file()) + } + + pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc> { + self.point_to_buffer_offset(point) + .and_then(|(buffer, offset)| buffer.language_at(offset)) + } + + pub fn settings_at<'a, T: ToOffset>( + &'a self, + point: T, + cx: &'a AppContext, + ) -> &'a LanguageSettings { + let mut language = None; + let mut file = None; + if let Some((buffer, offset)) = self.point_to_buffer_offset(point) { + language = buffer.language_at(offset); + file = buffer.file(); + } + language_settings(language, file, cx) + } + + pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option { + self.point_to_buffer_offset(point) + .and_then(|(buffer, offset)| buffer.language_scope_at(offset)) + } + + pub fn language_indent_size_at( + &self, + position: T, + cx: &AppContext, + ) -> Option { + let (buffer_snapshot, offset) = self.point_to_buffer_offset(position)?; + Some(buffer_snapshot.language_indent_size_at(offset, cx)) + } + + pub fn is_dirty(&self) -> bool { + self.is_dirty + } + + pub fn has_conflict(&self) -> bool { + self.has_conflict + } + + pub fn diagnostic_group<'a, O>( + &'a self, + group_id: usize, + ) -> impl Iterator> + 'a + where + O: text::FromAnchor + 'a, + { + self.as_singleton() + .into_iter() + .flat_map(move |(_, _, buffer)| buffer.diagnostic_group(group_id)) + } + + pub fn diagnostics_in_range<'a, T, O>( + &'a self, + range: Range, + reversed: bool, + ) -> impl Iterator> + 'a + where + T: 'a + ToOffset, + O: 'a + text::FromAnchor + Ord, + { + self.as_singleton() + .into_iter() + .flat_map(move |(_, _, buffer)| { + buffer.diagnostics_in_range( + range.start.to_offset(self)..range.end.to_offset(self), + reversed, + ) + }) + } + + pub fn has_git_diffs(&self) -> bool { + for excerpt in self.excerpts.iter() { + if !excerpt.buffer.git_diff.is_empty() { + return true; + } + } + false + } + + pub fn git_diff_hunks_in_range_rev<'a>( + &'a self, + row_range: Range, + ) -> impl 'a + Iterator> { + let mut cursor = self.excerpts.cursor::(); + + cursor.seek(&Point::new(row_range.end, 0), Bias::Left, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + std::iter::from_fn(move || { + let excerpt = cursor.item()?; + let multibuffer_start = *cursor.start(); + let multibuffer_end = multibuffer_start + excerpt.text_summary.lines; + if multibuffer_start.row >= row_range.end { + return None; + } + + let mut buffer_start = excerpt.range.context.start; + let mut buffer_end = excerpt.range.context.end; + let excerpt_start_point = buffer_start.to_point(&excerpt.buffer); + let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines; + + if row_range.start > multibuffer_start.row { + let buffer_start_point = + excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0); + buffer_start = excerpt.buffer.anchor_before(buffer_start_point); + } + + if row_range.end < multibuffer_end.row { + let buffer_end_point = + excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0); + buffer_end = excerpt.buffer.anchor_before(buffer_end_point); + } + + let buffer_hunks = excerpt + .buffer + .git_diff_hunks_intersecting_range_rev(buffer_start..buffer_end) + .filter_map(move |hunk| { + let start = multibuffer_start.row + + hunk + .buffer_range + .start + .saturating_sub(excerpt_start_point.row); + let end = multibuffer_start.row + + hunk + .buffer_range + .end + .min(excerpt_end_point.row + 1) + .saturating_sub(excerpt_start_point.row); + + Some(DiffHunk { + buffer_range: start..end, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }); + + cursor.prev(&()); + + Some(buffer_hunks) + }) + .flatten() + } + + pub fn git_diff_hunks_in_range<'a>( + &'a self, + row_range: Range, + ) -> impl 'a + Iterator> { + let mut cursor = self.excerpts.cursor::(); + + cursor.seek(&Point::new(row_range.start, 0), Bias::Right, &()); + + std::iter::from_fn(move || { + let excerpt = cursor.item()?; + let multibuffer_start = *cursor.start(); + let multibuffer_end = multibuffer_start + excerpt.text_summary.lines; + if multibuffer_start.row >= row_range.end { + return None; + } + + let mut buffer_start = excerpt.range.context.start; + let mut buffer_end = excerpt.range.context.end; + let excerpt_start_point = buffer_start.to_point(&excerpt.buffer); + let excerpt_end_point = excerpt_start_point + excerpt.text_summary.lines; + + if row_range.start > multibuffer_start.row { + let buffer_start_point = + excerpt_start_point + Point::new(row_range.start - multibuffer_start.row, 0); + buffer_start = excerpt.buffer.anchor_before(buffer_start_point); + } + + if row_range.end < multibuffer_end.row { + let buffer_end_point = + excerpt_start_point + Point::new(row_range.end - multibuffer_start.row, 0); + buffer_end = excerpt.buffer.anchor_before(buffer_end_point); + } + + let buffer_hunks = excerpt + .buffer + .git_diff_hunks_intersecting_range(buffer_start..buffer_end) + .filter_map(move |hunk| { + let start = multibuffer_start.row + + hunk + .buffer_range + .start + .saturating_sub(excerpt_start_point.row); + let end = multibuffer_start.row + + hunk + .buffer_range + .end + .min(excerpt_end_point.row + 1) + .saturating_sub(excerpt_start_point.row); + + Some(DiffHunk { + buffer_range: start..end, + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + }) + }); + + cursor.next(&()); + + Some(buffer_hunks) + }) + .flatten() + } + + pub fn range_for_syntax_ancestor(&self, range: Range) -> Option> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + self.excerpt_containing(range.clone()) + .and_then(|(excerpt, excerpt_offset)| { + let excerpt_buffer_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let excerpt_buffer_end = excerpt_buffer_start + excerpt.text_summary.len; + + let start_in_buffer = + excerpt_buffer_start + range.start.saturating_sub(excerpt_offset); + let end_in_buffer = excerpt_buffer_start + range.end.saturating_sub(excerpt_offset); + let mut ancestor_buffer_range = excerpt + .buffer + .range_for_syntax_ancestor(start_in_buffer..end_in_buffer)?; + ancestor_buffer_range.start = + cmp::max(ancestor_buffer_range.start, excerpt_buffer_start); + ancestor_buffer_range.end = cmp::min(ancestor_buffer_range.end, excerpt_buffer_end); + + let start = excerpt_offset + (ancestor_buffer_range.start - excerpt_buffer_start); + let end = excerpt_offset + (ancestor_buffer_range.end - excerpt_buffer_start); + Some(start..end) + }) + } + + pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option> { + let (excerpt_id, _, buffer) = self.as_singleton()?; + let outline = buffer.outline(theme)?; + Some(Outline::new( + outline + .items + .into_iter() + .map(|item| OutlineItem { + depth: item.depth, + range: self.anchor_in_excerpt(excerpt_id.clone(), item.range.start) + ..self.anchor_in_excerpt(excerpt_id.clone(), item.range.end), + text: item.text, + highlight_ranges: item.highlight_ranges, + name_ranges: item.name_ranges, + }) + .collect(), + )) + } + + pub fn symbols_containing( + &self, + offset: T, + theme: Option<&SyntaxTheme>, + ) -> Option<(u64, Vec>)> { + let anchor = self.anchor_before(offset); + let excerpt_id = anchor.excerpt_id; + let excerpt = self.excerpt(excerpt_id)?; + Some(( + excerpt.buffer_id, + excerpt + .buffer + .symbols_containing(anchor.text_anchor, theme) + .into_iter() + .flatten() + .map(|item| OutlineItem { + depth: item.depth, + range: self.anchor_in_excerpt(excerpt_id, item.range.start) + ..self.anchor_in_excerpt(excerpt_id, item.range.end), + text: item.text, + highlight_ranges: item.highlight_ranges, + name_ranges: item.name_ranges, + }) + .collect(), + )) + } + + fn excerpt_locator_for_id<'a>(&'a self, id: ExcerptId) -> &'a Locator { + if id == ExcerptId::min() { + Locator::min_ref() + } else if id == ExcerptId::max() { + Locator::max_ref() + } else { + let mut cursor = self.excerpt_ids.cursor::(); + cursor.seek(&id, Bias::Left, &()); + if let Some(entry) = cursor.item() { + if entry.id == id { + return &entry.locator; + } + } + panic!("invalid excerpt id {:?}", id) + } + } + + pub fn buffer_id_for_excerpt(&self, excerpt_id: ExcerptId) -> Option { + Some(self.excerpt(excerpt_id)?.buffer_id) + } + + pub fn buffer_for_excerpt(&self, excerpt_id: ExcerptId) -> Option<&BufferSnapshot> { + Some(&self.excerpt(excerpt_id)?.buffer) + } + + fn excerpt<'a>(&'a self, excerpt_id: ExcerptId) -> Option<&'a Excerpt> { + let mut cursor = self.excerpts.cursor::>(); + let locator = self.excerpt_locator_for_id(excerpt_id); + cursor.seek(&Some(locator), Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.id == excerpt_id { + return Some(excerpt); + } + } + None + } + + /// Returns the excerpt containing range and its offset start within the multibuffer or none if `range` spans multiple excerpts + fn excerpt_containing<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> Option<(&'a Excerpt, usize)> { + let range = range.start.to_offset(self)..range.end.to_offset(self); + + let mut cursor = self.excerpts.cursor::(); + cursor.seek(&range.start, Bias::Right, &()); + let start_excerpt = cursor.item(); + + if range.start == range.end { + return start_excerpt.map(|excerpt| (excerpt, *cursor.start())); + } + + cursor.seek(&range.end, Bias::Right, &()); + let end_excerpt = cursor.item(); + + start_excerpt + .zip(end_excerpt) + .and_then(|(start_excerpt, end_excerpt)| { + if start_excerpt.id != end_excerpt.id { + return None; + } + + Some((start_excerpt, *cursor.start())) + }) + } + + pub fn remote_selections_in_range<'a>( + &'a self, + range: &'a Range, + ) -> impl 'a + Iterator)> { + let mut cursor = self.excerpts.cursor::(); + let start_locator = self.excerpt_locator_for_id(range.start.excerpt_id); + let end_locator = self.excerpt_locator_for_id(range.end.excerpt_id); + cursor.seek(start_locator, Bias::Left, &()); + cursor + .take_while(move |excerpt| excerpt.locator <= *end_locator) + .flat_map(move |excerpt| { + let mut query_range = excerpt.range.context.start..excerpt.range.context.end; + if excerpt.id == range.start.excerpt_id { + query_range.start = range.start.text_anchor; + } + if excerpt.id == range.end.excerpt_id { + query_range.end = range.end.text_anchor; + } + + excerpt + .buffer + .remote_selections_in_range(query_range) + .flat_map(move |(replica_id, line_mode, cursor_shape, selections)| { + selections.map(move |selection| { + let mut start = Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor: selection.start, + }; + let mut end = Anchor { + buffer_id: Some(excerpt.buffer_id), + excerpt_id: excerpt.id.clone(), + text_anchor: selection.end, + }; + if range.start.cmp(&start, self).is_gt() { + start = range.start.clone(); + } + if range.end.cmp(&end, self).is_lt() { + end = range.end.clone(); + } + + ( + replica_id, + line_mode, + cursor_shape, + Selection { + id: selection.id, + start, + end, + reversed: selection.reversed, + goal: selection.goal, + }, + ) + }) + }) + }) + } +} + +#[cfg(any(test, feature = "test-support"))] +impl MultiBufferSnapshot { + pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range { + let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); + let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right); + start..end + } +} + +impl History { + fn start_transaction(&mut self, now: Instant) -> Option { + self.transaction_depth += 1; + if self.transaction_depth == 1 { + let id = self.next_transaction_id.tick(); + self.undo_stack.push(Transaction { + id, + buffer_transactions: Default::default(), + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + Some(id) + } else { + None + } + } + + fn end_transaction( + &mut self, + now: Instant, + buffer_transactions: HashMap, + ) -> bool { + assert_ne!(self.transaction_depth, 0); + self.transaction_depth -= 1; + if self.transaction_depth == 0 { + if buffer_transactions.is_empty() { + self.undo_stack.pop(); + false + } else { + self.redo_stack.clear(); + let transaction = self.undo_stack.last_mut().unwrap(); + transaction.last_edit_at = now; + for (buffer_id, transaction_id) in buffer_transactions { + transaction + .buffer_transactions + .entry(buffer_id) + .or_insert(transaction_id); + } + true + } + } else { + false + } + } + + fn push_transaction<'a, T>( + &mut self, + buffer_transactions: T, + now: Instant, + cx: &mut ModelContext, + ) where + T: IntoIterator, &'a language2::Transaction)>, + { + assert_eq!(self.transaction_depth, 0); + let transaction = Transaction { + id: self.next_transaction_id.tick(), + buffer_transactions: buffer_transactions + .into_iter() + .map(|(buffer, transaction)| (buffer.read(cx).remote_id(), transaction.id)) + .collect(), + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }; + if !transaction.buffer_transactions.is_empty() { + self.undo_stack.push(transaction); + self.redo_stack.clear(); + } + } + + fn finalize_last_transaction(&mut self) { + if let Some(transaction) = self.undo_stack.last_mut() { + transaction.suppress_grouping = true; + } + } + + fn forget(&mut self, transaction_id: TransactionId) -> Option { + if let Some(ix) = self + .undo_stack + .iter() + .rposition(|transaction| transaction.id == transaction_id) + { + Some(self.undo_stack.remove(ix)) + } else if let Some(ix) = self + .redo_stack + .iter() + .rposition(|transaction| transaction.id == transaction_id) + { + Some(self.redo_stack.remove(ix)) + } else { + None + } + } + + fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> { + self.undo_stack + .iter_mut() + .find(|transaction| transaction.id == transaction_id) + .or_else(|| { + self.redo_stack + .iter_mut() + .find(|transaction| transaction.id == transaction_id) + }) + } + + fn pop_undo(&mut self) -> Option<&mut Transaction> { + assert_eq!(self.transaction_depth, 0); + if let Some(transaction) = self.undo_stack.pop() { + self.redo_stack.push(transaction); + self.redo_stack.last_mut() + } else { + None + } + } + + fn pop_redo(&mut self) -> Option<&mut Transaction> { + assert_eq!(self.transaction_depth, 0); + if let Some(transaction) = self.redo_stack.pop() { + self.undo_stack.push(transaction); + self.undo_stack.last_mut() + } else { + None + } + } + + fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&Transaction> { + let ix = self + .undo_stack + .iter() + .rposition(|transaction| transaction.id == transaction_id)?; + let transaction = self.undo_stack.remove(ix); + self.redo_stack.push(transaction); + self.redo_stack.last() + } + + fn group(&mut self) -> Option { + let mut count = 0; + let mut transactions = self.undo_stack.iter(); + if let Some(mut transaction) = transactions.next_back() { + while let Some(prev_transaction) = transactions.next_back() { + if !prev_transaction.suppress_grouping + && transaction.first_edit_at - prev_transaction.last_edit_at + <= self.group_interval + { + transaction = prev_transaction; + count += 1; + } else { + break; + } + } + } + self.group_trailing(count) + } + + fn group_until(&mut self, transaction_id: TransactionId) { + let mut count = 0; + for transaction in self.undo_stack.iter().rev() { + if transaction.id == transaction_id { + self.group_trailing(count); + break; + } else if transaction.suppress_grouping { + break; + } else { + count += 1; + } + } + } + + fn group_trailing(&mut self, n: usize) -> Option { + let new_len = self.undo_stack.len() - n; + let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len); + if let Some(last_transaction) = transactions_to_keep.last_mut() { + if let Some(transaction) = transactions_to_merge.last() { + last_transaction.last_edit_at = transaction.last_edit_at; + } + for to_merge in transactions_to_merge { + for (buffer_id, transaction_id) in &to_merge.buffer_transactions { + last_transaction + .buffer_transactions + .entry(*buffer_id) + .or_insert(*transaction_id); + } + } + } + + self.undo_stack.truncate(new_len); + self.undo_stack.last().map(|t| t.id) + } +} + +impl Excerpt { + fn new( + id: ExcerptId, + locator: Locator, + buffer_id: u64, + buffer: BufferSnapshot, + range: ExcerptRange, + has_trailing_newline: bool, + ) -> Self { + Excerpt { + id, + locator, + max_buffer_row: range.context.end.to_point(&buffer).row, + text_summary: buffer + .text_summary_for_range::(range.context.to_offset(&buffer)), + buffer_id, + buffer, + range, + has_trailing_newline, + } + } + + fn chunks_in_range(&self, range: Range, language_aware: bool) -> ExcerptChunks { + let content_start = self.range.context.start.to_offset(&self.buffer); + let chunks_start = content_start + range.start; + let chunks_end = content_start + cmp::min(range.end, self.text_summary.len); + + let footer_height = if self.has_trailing_newline + && range.start <= self.text_summary.len + && range.end > self.text_summary.len + { + 1 + } else { + 0 + }; + + let content_chunks = self.buffer.chunks(chunks_start..chunks_end, language_aware); + + ExcerptChunks { + content_chunks, + footer_height, + } + } + + fn bytes_in_range(&self, range: Range) -> ExcerptBytes { + let content_start = self.range.context.start.to_offset(&self.buffer); + let bytes_start = content_start + range.start; + let bytes_end = content_start + cmp::min(range.end, self.text_summary.len); + let footer_height = if self.has_trailing_newline + && range.start <= self.text_summary.len + && range.end > self.text_summary.len + { + 1 + } else { + 0 + }; + let content_bytes = self.buffer.bytes_in_range(bytes_start..bytes_end); + + ExcerptBytes { + content_bytes, + footer_height, + } + } + + fn reversed_bytes_in_range(&self, range: Range) -> ExcerptBytes { + let content_start = self.range.context.start.to_offset(&self.buffer); + let bytes_start = content_start + range.start; + let bytes_end = content_start + cmp::min(range.end, self.text_summary.len); + let footer_height = if self.has_trailing_newline + && range.start <= self.text_summary.len + && range.end > self.text_summary.len + { + 1 + } else { + 0 + }; + let content_bytes = self.buffer.reversed_bytes_in_range(bytes_start..bytes_end); + + ExcerptBytes { + content_bytes, + footer_height, + } + } + + fn clip_anchor(&self, text_anchor: text::Anchor) -> text::Anchor { + if text_anchor + .cmp(&self.range.context.start, &self.buffer) + .is_lt() + { + self.range.context.start + } else if text_anchor + .cmp(&self.range.context.end, &self.buffer) + .is_gt() + { + self.range.context.end + } else { + text_anchor + } + } + + fn contains(&self, anchor: &Anchor) -> bool { + Some(self.buffer_id) == anchor.buffer_id + && self + .range + .context + .start + .cmp(&anchor.text_anchor, &self.buffer) + .is_le() + && self + .range + .context + .end + .cmp(&anchor.text_anchor, &self.buffer) + .is_ge() + } +} + +impl ExcerptId { + pub fn min() -> Self { + Self(0) + } + + pub fn max() -> Self { + Self(usize::MAX) + } + + pub fn to_proto(&self) -> u64 { + self.0 as _ + } + + pub fn from_proto(proto: u64) -> Self { + Self(proto as _) + } + + pub fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> cmp::Ordering { + let a = snapshot.excerpt_locator_for_id(*self); + let b = snapshot.excerpt_locator_for_id(*other); + a.cmp(&b).then_with(|| self.0.cmp(&other.0)) + } +} + +impl Into for ExcerptId { + fn into(self) -> usize { + self.0 + } +} + +impl fmt::Debug for Excerpt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Excerpt") + .field("id", &self.id) + .field("locator", &self.locator) + .field("buffer_id", &self.buffer_id) + .field("range", &self.range) + .field("text_summary", &self.text_summary) + .field("has_trailing_newline", &self.has_trailing_newline) + .finish() + } +} + +impl sum_tree::Item for Excerpt { + type Summary = ExcerptSummary; + + fn summary(&self) -> Self::Summary { + let mut text = self.text_summary.clone(); + if self.has_trailing_newline { + text += TextSummary::from("\n"); + } + ExcerptSummary { + excerpt_id: self.id, + excerpt_locator: self.locator.clone(), + max_buffer_row: self.max_buffer_row, + text, + } + } +} + +impl sum_tree::Item for ExcerptIdMapping { + type Summary = ExcerptId; + + fn summary(&self) -> Self::Summary { + self.id + } +} + +impl sum_tree::KeyedItem for ExcerptIdMapping { + type Key = ExcerptId; + + fn key(&self) -> Self::Key { + self.id + } +} + +impl sum_tree::Summary for ExcerptId { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + *self = *other; + } +} + +impl sum_tree::Summary for ExcerptSummary { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + debug_assert!(summary.excerpt_locator > self.excerpt_locator); + self.excerpt_locator = summary.excerpt_locator.clone(); + self.text.add_summary(&summary.text, &()); + self.max_buffer_row = cmp::max(self.max_buffer_row, summary.max_buffer_row); + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += &summary.text; + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.len; + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize { + fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.text.len) + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, Option<&'a Locator>> for Locator { + fn cmp(&self, cursor_location: &Option<&'a Locator>, _: &()) -> cmp::Ordering { + Ord::cmp(&Some(self), cursor_location) + } +} + +impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Locator { + fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.excerpt_locator) + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for OffsetUtf16 { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.len_utf16; + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.lines; + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self += summary.text.lines_utf16() + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option<&'a Locator> { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self = Some(&summary.excerpt_locator); + } +} + +impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Option { + fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) { + *self = Some(summary.excerpt_id); + } +} + +impl<'a> MultiBufferRows<'a> { + pub fn seek(&mut self, row: u32) { + self.buffer_row_range = 0..0; + + self.excerpts + .seek_forward(&Point::new(row, 0), Bias::Right, &()); + if self.excerpts.item().is_none() { + self.excerpts.prev(&()); + + if self.excerpts.item().is_none() && row == 0 { + self.buffer_row_range = 0..1; + return; + } + } + + if let Some(excerpt) = self.excerpts.item() { + let overshoot = row - self.excerpts.start().row; + let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer).row; + self.buffer_row_range.start = excerpt_start + overshoot; + self.buffer_row_range.end = excerpt_start + excerpt.text_summary.lines.row + 1; + } + } +} + +impl<'a> Iterator for MultiBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + loop { + if !self.buffer_row_range.is_empty() { + let row = Some(self.buffer_row_range.start); + self.buffer_row_range.start += 1; + return Some(row); + } + self.excerpts.item()?; + self.excerpts.next(&()); + let excerpt = self.excerpts.item()?; + self.buffer_row_range.start = excerpt.range.context.start.to_point(&excerpt.buffer).row; + self.buffer_row_range.end = + self.buffer_row_range.start + excerpt.text_summary.lines.row + 1; + } + } +} + +impl<'a> MultiBufferChunks<'a> { + pub fn offset(&self) -> usize { + self.range.start + } + + pub fn seek(&mut self, offset: usize) { + self.range.start = offset; + self.excerpts.seek(&offset, Bias::Right, &()); + if let Some(excerpt) = self.excerpts.item() { + self.excerpt_chunks = Some(excerpt.chunks_in_range( + self.range.start - self.excerpts.start()..self.range.end - self.excerpts.start(), + self.language_aware, + )); + } else { + self.excerpt_chunks = None; + } + } +} + +impl<'a> Iterator for MultiBufferChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.range.is_empty() { + None + } else if let Some(chunk) = self.excerpt_chunks.as_mut()?.next() { + self.range.start += chunk.text.len(); + Some(chunk) + } else { + self.excerpts.next(&()); + let excerpt = self.excerpts.item()?; + self.excerpt_chunks = Some(excerpt.chunks_in_range( + 0..self.range.end - self.excerpts.start(), + self.language_aware, + )); + self.next() + } + } +} + +impl<'a> MultiBufferBytes<'a> { + fn consume(&mut self, len: usize) { + self.range.start += len; + self.chunk = &self.chunk[len..]; + + if !self.range.is_empty() && self.chunk.is_empty() { + if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) { + self.chunk = chunk; + } else { + self.excerpts.next(&()); + if let Some(excerpt) = self.excerpts.item() { + let mut excerpt_bytes = + excerpt.bytes_in_range(0..self.range.end - self.excerpts.start()); + self.chunk = excerpt_bytes.next().unwrap(); + self.excerpt_bytes = Some(excerpt_bytes); + } + } + } + } +} + +impl<'a> Iterator for MultiBufferBytes<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + let chunk = self.chunk; + if chunk.is_empty() { + None + } else { + self.consume(chunk.len()); + Some(chunk) + } + } +} + +impl<'a> io::Read for MultiBufferBytes<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = cmp::min(buf.len(), self.chunk.len()); + buf[..len].copy_from_slice(&self.chunk[..len]); + if len > 0 { + self.consume(len); + } + Ok(len) + } +} + +impl<'a> ReversedMultiBufferBytes<'a> { + fn consume(&mut self, len: usize) { + self.range.end -= len; + self.chunk = &self.chunk[..self.chunk.len() - len]; + + if !self.range.is_empty() && self.chunk.is_empty() { + if let Some(chunk) = self.excerpt_bytes.as_mut().and_then(|bytes| bytes.next()) { + self.chunk = chunk; + } else { + self.excerpts.next(&()); + if let Some(excerpt) = self.excerpts.item() { + let mut excerpt_bytes = + excerpt.bytes_in_range(0..self.range.end - self.excerpts.start()); + self.chunk = excerpt_bytes.next().unwrap(); + self.excerpt_bytes = Some(excerpt_bytes); + } + } + } + } +} + +impl<'a> io::Read for ReversedMultiBufferBytes<'a> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let len = cmp::min(buf.len(), self.chunk.len()); + buf[..len].copy_from_slice(&self.chunk[..len]); + buf[..len].reverse(); + if len > 0 { + self.consume(len); + } + Ok(len) + } +} +impl<'a> Iterator for ExcerptBytes<'a> { + type Item = &'a [u8]; + + fn next(&mut self) -> Option { + if let Some(chunk) = self.content_bytes.next() { + if !chunk.is_empty() { + return Some(chunk); + } + } + + if self.footer_height > 0 { + let result = &NEWLINES[..self.footer_height]; + self.footer_height = 0; + return Some(result); + } + + None + } +} + +impl<'a> Iterator for ExcerptChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if let Some(chunk) = self.content_chunks.next() { + return Some(chunk); + } + + if self.footer_height > 0 { + let text = unsafe { str::from_utf8_unchecked(&NEWLINES[..self.footer_height]) }; + self.footer_height = 0; + return Some(Chunk { + text, + ..Default::default() + }); + } + + None + } +} + +impl ToOffset for Point { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.point_to_offset(*self) + } +} + +impl ToOffset for usize { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + assert!(*self <= snapshot.len(), "offset is out of range"); + *self + } +} + +impl ToOffset for OffsetUtf16 { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.offset_utf16_to_offset(*self) + } +} + +impl ToOffset for PointUtf16 { + fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize { + snapshot.point_utf16_to_offset(*self) + } +} + +impl ToOffsetUtf16 for OffsetUtf16 { + fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 { + *self + } +} + +impl ToOffsetUtf16 for usize { + fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 { + snapshot.offset_to_offset_utf16(*self) + } +} + +impl ToPoint for usize { + fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point { + snapshot.offset_to_point(*self) + } +} + +impl ToPoint for Point { + fn to_point<'a>(&self, _: &MultiBufferSnapshot) -> Point { + *self + } +} + +impl ToPointUtf16 for usize { + fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 { + snapshot.offset_to_point_utf16(*self) + } +} + +impl ToPointUtf16 for Point { + fn to_point_utf16<'a>(&self, snapshot: &MultiBufferSnapshot) -> PointUtf16 { + snapshot.point_to_point_utf16(*self) + } +} + +impl ToPointUtf16 for PointUtf16 { + fn to_point_utf16<'a>(&self, _: &MultiBufferSnapshot) -> PointUtf16 { + *self + } +} + +fn build_excerpt_ranges( + buffer: &BufferSnapshot, + ranges: &[Range], + context_line_count: u32, +) -> (Vec>, Vec) +where + T: text::ToPoint, +{ + let max_point = buffer.max_point(); + let mut range_counts = Vec::new(); + let mut excerpt_ranges = Vec::new(); + let mut range_iter = ranges + .iter() + .map(|range| range.start.to_point(buffer)..range.end.to_point(buffer)) + .peekable(); + while let Some(range) = range_iter.next() { + let excerpt_start = Point::new(range.start.row.saturating_sub(context_line_count), 0); + let mut excerpt_end = Point::new(range.end.row + 1 + context_line_count, 0).min(max_point); + let mut ranges_in_excerpt = 1; + + while let Some(next_range) = range_iter.peek() { + if next_range.start.row <= excerpt_end.row + context_line_count { + excerpt_end = + Point::new(next_range.end.row + 1 + context_line_count, 0).min(max_point); + ranges_in_excerpt += 1; + range_iter.next(); + } else { + break; + } + } + + excerpt_ranges.push(ExcerptRange { + context: excerpt_start..excerpt_end, + primary: Some(range), + }); + range_counts.push(ranges_in_excerpt); + } + + (excerpt_ranges, range_counts) +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::StreamExt; + use gpui2::{AppContext, Context, TestAppContext}; + use language2::{Buffer, Rope}; + use parking_lot::RwLock; + use rand::prelude::*; + use settings2::SettingsStore; + use std::env; + use util::test::sample_text; + + #[gpui2::test] + fn test_singleton(cx: &mut AppContext) { + let buffer = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a'))); + let multibuffer = cx.build_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), buffer.read(cx).text()); + + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + (0..buffer.read(cx).row_count()) + .map(Some) + .collect::>() + ); + + buffer.update(cx, |buffer, cx| buffer.edit([(1..3, "XXX\n")], None, cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(snapshot.text(), buffer.read(cx).text()); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + (0..buffer.read(cx).row_count()) + .map(Some) + .collect::>() + ); + } + + #[gpui2::test] + fn test_remote(cx: &mut AppContext) { + let host_buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "a")); + let guest_buffer = cx.build_model(|cx| { + let state = host_buffer.read(cx).to_proto(); + let ops = cx + .executor() + .block(host_buffer.read(cx).serialize_ops(None, cx)); + let mut buffer = Buffer::from_proto(1, state, None).unwrap(); + buffer + .apply_ops( + ops.into_iter() + .map(|op| language2::proto::deserialize_operation(op).unwrap()), + cx, + ) + .unwrap(); + buffer + }); + let multibuffer = cx.build_model(|cx| MultiBuffer::singleton(guest_buffer.clone(), cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), "a"); + + guest_buffer.update(cx, |buffer, cx| buffer.edit([(1..1, "b")], None, cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), "ab"); + + guest_buffer.update(cx, |buffer, cx| buffer.edit([(2..2, "c")], None, cx)); + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), "abc"); + } + + #[gpui2::test] + fn test_excerpt_boundaries_and_clipping(cx: &mut AppContext) { + let buffer_1 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'a'))); + let buffer_2 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(6, 6, 'g'))); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + + let events = Arc::new(RwLock::new(Vec::::new())); + multibuffer.update(cx, |_, cx| { + let events = events.clone(); + cx.subscribe(&multibuffer, move |_, _, event, _| { + if let Event::Edited { .. } = event { + events.write().push(event.clone()) + } + }) + .detach(); + }); + + let subscription = multibuffer.update(cx, |multibuffer, cx| { + let subscription = multibuffer.subscribe(); + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: Point::new(1, 2)..Point::new(2, 5), + primary: None, + }], + cx, + ); + assert_eq!( + subscription.consume().into_inner(), + [Edit { + old: 0..0, + new: 0..10 + }] + ); + + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: Point::new(3, 3)..Point::new(4, 4), + primary: None, + }], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ExcerptRange { + context: Point::new(3, 1)..Point::new(3, 3), + primary: None, + }], + cx, + ); + assert_eq!( + subscription.consume().into_inner(), + [Edit { + old: 10..10, + new: 10..22 + }] + ); + + subscription + }); + + // Adding excerpts emits an edited event. + assert_eq!( + events.read().as_slice(), + &[ + Event::Edited { + sigleton_buffer_edited: false + }, + Event::Edited { + sigleton_buffer_edited: false + }, + Event::Edited { + sigleton_buffer_edited: false + } + ] + ); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.text(), + concat!( + "bbbb\n", // Preserve newlines + "ccccc\n", // + "ddd\n", // + "eeee\n", // + "jj" // + ) + ); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + [Some(1), Some(2), Some(3), Some(4), Some(3)] + ); + assert_eq!( + snapshot.buffer_rows(2).collect::>(), + [Some(3), Some(4), Some(3)] + ); + assert_eq!(snapshot.buffer_rows(4).collect::>(), [Some(3)]); + assert_eq!(snapshot.buffer_rows(5).collect::>(), []); + + assert_eq!( + boundaries_in_range(Point::new(0, 0)..Point::new(4, 2), &snapshot), + &[ + (0, "bbbb\nccccc".to_string(), true), + (2, "ddd\neeee".to_string(), false), + (4, "jj".to_string(), true), + ] + ); + assert_eq!( + boundaries_in_range(Point::new(0, 0)..Point::new(2, 0), &snapshot), + &[(0, "bbbb\nccccc".to_string(), true)] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(1, 5), &snapshot), + &[] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(2, 0), &snapshot), + &[] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot), + &[(2, "ddd\neeee".to_string(), false)] + ); + assert_eq!( + boundaries_in_range(Point::new(1, 0)..Point::new(4, 0), &snapshot), + &[(2, "ddd\neeee".to_string(), false)] + ); + assert_eq!( + boundaries_in_range(Point::new(2, 0)..Point::new(3, 0), &snapshot), + &[(2, "ddd\neeee".to_string(), false)] + ); + assert_eq!( + boundaries_in_range(Point::new(4, 0)..Point::new(4, 2), &snapshot), + &[(4, "jj".to_string(), true)] + ); + assert_eq!( + boundaries_in_range(Point::new(4, 2)..Point::new(4, 2), &snapshot), + &[] + ); + + buffer_1.update(cx, |buffer, cx| { + let text = "\n"; + buffer.edit( + [ + (Point::new(0, 0)..Point::new(0, 0), text), + (Point::new(2, 1)..Point::new(2, 3), text), + ], + None, + cx, + ); + }); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.text(), + concat!( + "bbbb\n", // Preserve newlines + "c\n", // + "cc\n", // + "ddd\n", // + "eeee\n", // + "jj" // + ) + ); + + assert_eq!( + subscription.consume().into_inner(), + [Edit { + old: 6..8, + new: 6..7 + }] + ); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.clip_point(Point::new(0, 5), Bias::Left), + Point::new(0, 4) + ); + assert_eq!( + snapshot.clip_point(Point::new(0, 5), Bias::Right), + Point::new(0, 4) + ); + assert_eq!( + snapshot.clip_point(Point::new(5, 1), Bias::Right), + Point::new(5, 1) + ); + assert_eq!( + snapshot.clip_point(Point::new(5, 2), Bias::Right), + Point::new(5, 2) + ); + assert_eq!( + snapshot.clip_point(Point::new(5, 3), Bias::Right), + Point::new(5, 2) + ); + + let snapshot = multibuffer.update(cx, |multibuffer, cx| { + let (buffer_2_excerpt_id, _) = + multibuffer.excerpts_for_buffer(&buffer_2, cx)[0].clone(); + multibuffer.remove_excerpts([buffer_2_excerpt_id], cx); + multibuffer.snapshot(cx) + }); + + assert_eq!( + snapshot.text(), + concat!( + "bbbb\n", // Preserve newlines + "c\n", // + "cc\n", // + "ddd\n", // + "eeee", // + ) + ); + + fn boundaries_in_range( + range: Range, + snapshot: &MultiBufferSnapshot, + ) -> Vec<(u32, String, bool)> { + snapshot + .excerpt_boundaries_in_range(range) + .map(|boundary| { + ( + boundary.row, + boundary + .buffer + .text_for_range(boundary.range.context) + .collect::(), + boundary.starts_new_buffer, + ) + }) + .collect::>() + } + } + + #[gpui2::test] + fn test_excerpt_events(cx: &mut AppContext) { + let buffer_1 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'a'))); + let buffer_2 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(10, 3, 'm'))); + + let leader_multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let follower_multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let follower_edit_event_count = Arc::new(RwLock::new(0)); + + follower_multibuffer.update(cx, |_, cx| { + let follower_edit_event_count = follower_edit_event_count.clone(); + cx.subscribe( + &leader_multibuffer, + move |follower, _, event, cx| match event.clone() { + Event::ExcerptsAdded { + buffer, + predecessor, + excerpts, + } => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx), + Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx), + Event::Edited { .. } => { + *follower_edit_event_count.write() += 1; + } + _ => {} + }, + ) + .detach(); + }); + + leader_multibuffer.update(cx, |leader, cx| { + leader.push_excerpts( + buffer_1.clone(), + [ + ExcerptRange { + context: 0..8, + primary: None, + }, + ExcerptRange { + context: 12..16, + primary: None, + }, + ], + cx, + ); + leader.insert_excerpts_after( + leader.excerpt_ids()[0], + buffer_2.clone(), + [ + ExcerptRange { + context: 0..5, + primary: None, + }, + ExcerptRange { + context: 10..15, + primary: None, + }, + ], + cx, + ) + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 2); + + leader_multibuffer.update(cx, |leader, cx| { + let excerpt_ids = leader.excerpt_ids(); + leader.remove_excerpts([excerpt_ids[1], excerpt_ids[3]], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 3); + + // Removing an empty set of excerpts is a noop. + leader_multibuffer.update(cx, |leader, cx| { + leader.remove_excerpts([], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 3); + + // Adding an empty set of excerpts is a noop. + leader_multibuffer.update(cx, |leader, cx| { + leader.push_excerpts::(buffer_2.clone(), [], cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 3); + + leader_multibuffer.update(cx, |leader, cx| { + leader.clear(cx); + }); + assert_eq!( + leader_multibuffer.read(cx).snapshot(cx).text(), + follower_multibuffer.read(cx).snapshot(cx).text(), + ); + assert_eq!(*follower_edit_event_count.read(), 4); + } + + #[gpui2::test] + fn test_push_excerpts_with_context_lines(cx: &mut AppContext) { + let buffer = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a'))); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.push_excerpts_with_context_lines( + buffer.clone(), + vec![ + Point::new(3, 2)..Point::new(4, 2), + Point::new(7, 1)..Point::new(7, 3), + Point::new(15, 0)..Point::new(15, 0), + ], + 2, + cx, + ) + }); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!( + snapshot.text(), + "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n" + ); + + assert_eq!( + anchor_ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(2, 2)..Point::new(3, 2), + Point::new(6, 1)..Point::new(6, 3), + Point::new(12, 0)..Point::new(12, 0) + ] + ); + } + + #[gpui2::test] + async fn test_stream_excerpts_with_context_lines(cx: &mut TestAppContext) { + let buffer = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), sample_text(20, 3, 'a'))); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let anchor_ranges = multibuffer.update(cx, |multibuffer, cx| { + let snapshot = buffer.read(cx); + let ranges = vec![ + snapshot.anchor_before(Point::new(3, 2))..snapshot.anchor_before(Point::new(4, 2)), + snapshot.anchor_before(Point::new(7, 1))..snapshot.anchor_before(Point::new(7, 3)), + snapshot.anchor_before(Point::new(15, 0)) + ..snapshot.anchor_before(Point::new(15, 0)), + ]; + multibuffer.stream_excerpts_with_context_lines(buffer.clone(), ranges, 2, cx) + }); + + let anchor_ranges = anchor_ranges.collect::>().await; + + let snapshot = multibuffer.update(cx, |multibuffer, cx| multibuffer.snapshot(cx)); + assert_eq!( + snapshot.text(), + "bbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\n\nnnn\nooo\nppp\nqqq\nrrr\n" + ); + + assert_eq!( + anchor_ranges + .iter() + .map(|range| range.to_point(&snapshot)) + .collect::>(), + vec![ + Point::new(2, 2)..Point::new(3, 2), + Point::new(6, 1)..Point::new(6, 3), + Point::new(12, 0)..Point::new(12, 0) + ] + ); + } + + #[gpui2::test] + fn test_empty_multibuffer(cx: &mut AppContext) { + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + + let snapshot = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot.text(), ""); + assert_eq!(snapshot.buffer_rows(0).collect::>(), &[Some(0)]); + assert_eq!(snapshot.buffer_rows(1).collect::>(), &[]); + } + + #[gpui2::test] + fn test_singleton_multibuffer_anchors(cx: &mut AppContext) { + let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); + let multibuffer = cx.build_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + let old_snapshot = multibuffer.read(cx).snapshot(cx); + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, "X")], None, cx); + buffer.edit([(5..5, "Y")], None, cx); + }); + let new_snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(old_snapshot.text(), "abcd"); + assert_eq!(new_snapshot.text(), "XabcdY"); + + assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0); + assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1); + assert_eq!(old_snapshot.anchor_before(4).to_offset(&new_snapshot), 5); + assert_eq!(old_snapshot.anchor_after(4).to_offset(&new_snapshot), 6); + } + + #[gpui2::test] + fn test_multibuffer_anchors(cx: &mut AppContext) { + let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); + let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "efghi")); + let multibuffer = cx.build_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: 0..4, + primary: None, + }], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ExcerptRange { + context: 0..5, + primary: None, + }], + cx, + ); + multibuffer + }); + let old_snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(old_snapshot.anchor_before(0).to_offset(&old_snapshot), 0); + assert_eq!(old_snapshot.anchor_after(0).to_offset(&old_snapshot), 0); + assert_eq!(Anchor::min().to_offset(&old_snapshot), 0); + assert_eq!(Anchor::min().to_offset(&old_snapshot), 0); + assert_eq!(Anchor::max().to_offset(&old_snapshot), 10); + assert_eq!(Anchor::max().to_offset(&old_snapshot), 10); + + buffer_1.update(cx, |buffer, cx| { + buffer.edit([(0..0, "W")], None, cx); + buffer.edit([(5..5, "X")], None, cx); + }); + buffer_2.update(cx, |buffer, cx| { + buffer.edit([(0..0, "Y")], None, cx); + buffer.edit([(6..6, "Z")], None, cx); + }); + let new_snapshot = multibuffer.read(cx).snapshot(cx); + + assert_eq!(old_snapshot.text(), "abcd\nefghi"); + assert_eq!(new_snapshot.text(), "WabcdX\nYefghiZ"); + + assert_eq!(old_snapshot.anchor_before(0).to_offset(&new_snapshot), 0); + assert_eq!(old_snapshot.anchor_after(0).to_offset(&new_snapshot), 1); + assert_eq!(old_snapshot.anchor_before(1).to_offset(&new_snapshot), 2); + assert_eq!(old_snapshot.anchor_after(1).to_offset(&new_snapshot), 2); + assert_eq!(old_snapshot.anchor_before(2).to_offset(&new_snapshot), 3); + assert_eq!(old_snapshot.anchor_after(2).to_offset(&new_snapshot), 3); + assert_eq!(old_snapshot.anchor_before(5).to_offset(&new_snapshot), 7); + assert_eq!(old_snapshot.anchor_after(5).to_offset(&new_snapshot), 8); + assert_eq!(old_snapshot.anchor_before(10).to_offset(&new_snapshot), 13); + assert_eq!(old_snapshot.anchor_after(10).to_offset(&new_snapshot), 14); + } + + #[gpui2::test] + fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut AppContext) { + let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcd")); + let buffer_2 = + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "ABCDEFGHIJKLMNOP")); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + + // Create an insertion id in buffer 1 that doesn't exist in buffer 2. + // Add an excerpt from buffer 1 that spans this new insertion. + buffer_1.update(cx, |buffer, cx| buffer.edit([(4..4, "123")], None, cx)); + let excerpt_id_1 = multibuffer.update(cx, |multibuffer, cx| { + multibuffer + .push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: 0..7, + primary: None, + }], + cx, + ) + .pop() + .unwrap() + }); + + let snapshot_1 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_1.text(), "abcd123"); + + // Replace the buffer 1 excerpt with new excerpts from buffer 2. + let (excerpt_id_2, excerpt_id_3) = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([excerpt_id_1], cx); + let mut ids = multibuffer + .push_excerpts( + buffer_2.clone(), + [ + ExcerptRange { + context: 0..4, + primary: None, + }, + ExcerptRange { + context: 6..10, + primary: None, + }, + ExcerptRange { + context: 12..16, + primary: None, + }, + ], + cx, + ) + .into_iter(); + (ids.next().unwrap(), ids.next().unwrap()) + }); + let snapshot_2 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_2.text(), "ABCD\nGHIJ\nMNOP"); + + // The old excerpt id doesn't get reused. + assert_ne!(excerpt_id_2, excerpt_id_1); + + // Resolve some anchors from the previous snapshot in the new snapshot. + // The current excerpts are from a different buffer, so we don't attempt to + // resolve the old text anchor in the new buffer. + assert_eq!( + snapshot_2.summary_for_anchor::(&snapshot_1.anchor_before(2)), + 0 + ); + assert_eq!( + snapshot_2.summaries_for_anchors::(&[ + snapshot_1.anchor_before(2), + snapshot_1.anchor_after(3) + ]), + vec![0, 0] + ); + + // Refresh anchors from the old snapshot. The return value indicates that both + // anchors lost their original excerpt. + let refresh = + snapshot_2.refresh_anchors(&[snapshot_1.anchor_before(2), snapshot_1.anchor_after(3)]); + assert_eq!( + refresh, + &[ + (0, snapshot_2.anchor_before(0), false), + (1, snapshot_2.anchor_after(0), false), + ] + ); + + // Replace the middle excerpt with a smaller excerpt in buffer 2, + // that intersects the old excerpt. + let excerpt_id_5 = multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts([excerpt_id_3], cx); + multibuffer + .insert_excerpts_after( + excerpt_id_2, + buffer_2.clone(), + [ExcerptRange { + context: 5..8, + primary: None, + }], + cx, + ) + .pop() + .unwrap() + }); + + let snapshot_3 = multibuffer.read(cx).snapshot(cx); + assert_eq!(snapshot_3.text(), "ABCD\nFGH\nMNOP"); + assert_ne!(excerpt_id_5, excerpt_id_3); + + // Resolve some anchors from the previous snapshot in the new snapshot. + // The third anchor can't be resolved, since its excerpt has been removed, + // so it resolves to the same position as its predecessor. + let anchors = [ + snapshot_2.anchor_before(0), + snapshot_2.anchor_after(2), + snapshot_2.anchor_after(6), + snapshot_2.anchor_after(14), + ]; + assert_eq!( + snapshot_3.summaries_for_anchors::(&anchors), + &[0, 2, 9, 13] + ); + + let new_anchors = snapshot_3.refresh_anchors(&anchors); + assert_eq!( + new_anchors.iter().map(|a| (a.0, a.2)).collect::>(), + &[(0, true), (1, true), (2, true), (3, true)] + ); + assert_eq!( + snapshot_3.summaries_for_anchors::(new_anchors.iter().map(|a| &a.1)), + &[0, 2, 7, 13] + ); + } + + #[gpui2::test(iterations = 100)] + fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let mut buffers: Vec> = Vec::new(); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let mut excerpt_ids = Vec::::new(); + let mut expected_excerpts = Vec::<(Model, Range)>::new(); + let mut anchors = Vec::new(); + let mut old_versions = Vec::new(); + + for _ in 0..operations { + match rng.gen_range(0..100) { + 0..=19 if !buffers.is_empty() => { + let buffer = buffers.choose(&mut rng).unwrap(); + buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx)); + } + 20..=29 if !expected_excerpts.is_empty() => { + let mut ids_to_remove = vec![]; + for _ in 0..rng.gen_range(1..=3) { + if expected_excerpts.is_empty() { + break; + } + + let ix = rng.gen_range(0..expected_excerpts.len()); + ids_to_remove.push(excerpt_ids.remove(ix)); + let (buffer, range) = expected_excerpts.remove(ix); + let buffer = buffer.read(cx); + log::info!( + "Removing excerpt {}: {:?}", + ix, + buffer + .text_for_range(range.to_offset(buffer)) + .collect::(), + ); + } + let snapshot = multibuffer.read(cx).read(cx); + ids_to_remove.sort_unstable_by(|a, b| a.cmp(&b, &snapshot)); + drop(snapshot); + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.remove_excerpts(ids_to_remove, cx) + }); + } + 30..=39 if !expected_excerpts.is_empty() => { + let multibuffer = multibuffer.read(cx).read(cx); + let offset = + multibuffer.clip_offset(rng.gen_range(0..=multibuffer.len()), Bias::Left); + let bias = if rng.gen() { Bias::Left } else { Bias::Right }; + log::info!("Creating anchor at {} with bias {:?}", offset, bias); + anchors.push(multibuffer.anchor_at(offset, bias)); + anchors.sort_by(|a, b| a.cmp(b, &multibuffer)); + } + 40..=44 if !anchors.is_empty() => { + let multibuffer = multibuffer.read(cx).read(cx); + let prev_len = anchors.len(); + anchors = multibuffer + .refresh_anchors(&anchors) + .into_iter() + .map(|a| a.1) + .collect(); + + // Ensure the newly-refreshed anchors point to a valid excerpt and don't + // overshoot its boundaries. + assert_eq!(anchors.len(), prev_len); + for anchor in &anchors { + if anchor.excerpt_id == ExcerptId::min() + || anchor.excerpt_id == ExcerptId::max() + { + continue; + } + + let excerpt = multibuffer.excerpt(anchor.excerpt_id).unwrap(); + assert_eq!(excerpt.id, anchor.excerpt_id); + assert!(excerpt.contains(anchor)); + } + } + _ => { + let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) { + let base_text = util::RandomCharIter::new(&mut rng) + .take(10) + .collect::(); + buffers.push( + cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text)), + ); + buffers.last().unwrap() + } else { + buffers.choose(&mut rng).unwrap() + }; + + let buffer = buffer_handle.read(cx); + let end_ix = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Right); + let start_ix = buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let anchor_range = buffer.anchor_before(start_ix)..buffer.anchor_after(end_ix); + let prev_excerpt_ix = rng.gen_range(0..=expected_excerpts.len()); + let prev_excerpt_id = excerpt_ids + .get(prev_excerpt_ix) + .cloned() + .unwrap_or_else(ExcerptId::max); + let excerpt_ix = (prev_excerpt_ix + 1).min(expected_excerpts.len()); + + log::info!( + "Inserting excerpt at {} of {} for buffer {}: {:?}[{:?}] = {:?}", + excerpt_ix, + expected_excerpts.len(), + buffer_handle.read(cx).remote_id(), + buffer.text(), + start_ix..end_ix, + &buffer.text()[start_ix..end_ix] + ); + + let excerpt_id = multibuffer.update(cx, |multibuffer, cx| { + multibuffer + .insert_excerpts_after( + prev_excerpt_id, + buffer_handle.clone(), + [ExcerptRange { + context: start_ix..end_ix, + primary: None, + }], + cx, + ) + .pop() + .unwrap() + }); + + excerpt_ids.insert(excerpt_ix, excerpt_id); + expected_excerpts.insert(excerpt_ix, (buffer_handle.clone(), anchor_range)); + } + } + + if rng.gen_bool(0.3) { + multibuffer.update(cx, |multibuffer, cx| { + old_versions.push((multibuffer.snapshot(cx), multibuffer.subscribe())); + }) + } + + let snapshot = multibuffer.read(cx).snapshot(cx); + + let mut excerpt_starts = Vec::new(); + let mut expected_text = String::new(); + let mut expected_buffer_rows = Vec::new(); + for (buffer, range) in &expected_excerpts { + let buffer = buffer.read(cx); + let buffer_range = range.to_offset(buffer); + + excerpt_starts.push(TextSummary::from(expected_text.as_str())); + expected_text.extend(buffer.text_for_range(buffer_range.clone())); + expected_text.push('\n'); + + let buffer_row_range = buffer.offset_to_point(buffer_range.start).row + ..=buffer.offset_to_point(buffer_range.end).row; + for row in buffer_row_range { + expected_buffer_rows.push(Some(row)); + } + } + // Remove final trailing newline. + if !expected_excerpts.is_empty() { + expected_text.pop(); + } + + // Always report one buffer row + if expected_buffer_rows.is_empty() { + expected_buffer_rows.push(Some(0)); + } + + assert_eq!(snapshot.text(), expected_text); + log::info!("MultiBuffer text: {:?}", expected_text); + + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + expected_buffer_rows, + ); + + for _ in 0..5 { + let start_row = rng.gen_range(0..=expected_buffer_rows.len()); + assert_eq!( + snapshot.buffer_rows(start_row as u32).collect::>(), + &expected_buffer_rows[start_row..], + "buffer_rows({})", + start_row + ); + } + + assert_eq!( + snapshot.max_buffer_row(), + expected_buffer_rows.into_iter().flatten().max().unwrap() + ); + + let mut excerpt_starts = excerpt_starts.into_iter(); + for (buffer, range) in &expected_excerpts { + let buffer = buffer.read(cx); + let buffer_id = buffer.remote_id(); + let buffer_range = range.to_offset(buffer); + let buffer_start_point = buffer.offset_to_point(buffer_range.start); + let buffer_start_point_utf16 = + buffer.text_summary_for_range::(0..buffer_range.start); + + let excerpt_start = excerpt_starts.next().unwrap(); + let mut offset = excerpt_start.len; + let mut buffer_offset = buffer_range.start; + let mut point = excerpt_start.lines; + let mut buffer_point = buffer_start_point; + let mut point_utf16 = excerpt_start.lines_utf16(); + let mut buffer_point_utf16 = buffer_start_point_utf16; + for ch in buffer + .snapshot() + .chunks(buffer_range.clone(), false) + .flat_map(|c| c.text.chars()) + { + for _ in 0..ch.len_utf8() { + let left_offset = snapshot.clip_offset(offset, Bias::Left); + let right_offset = snapshot.clip_offset(offset, Bias::Right); + let buffer_left_offset = buffer.clip_offset(buffer_offset, Bias::Left); + let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right); + assert_eq!( + left_offset, + excerpt_start.len + (buffer_left_offset - buffer_range.start), + "clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}", + offset, + buffer_id, + buffer_offset, + ); + assert_eq!( + right_offset, + excerpt_start.len + (buffer_right_offset - buffer_range.start), + "clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}", + offset, + buffer_id, + buffer_offset, + ); + + let left_point = snapshot.clip_point(point, Bias::Left); + let right_point = snapshot.clip_point(point, Bias::Right); + let buffer_left_point = buffer.clip_point(buffer_point, Bias::Left); + let buffer_right_point = buffer.clip_point(buffer_point, Bias::Right); + assert_eq!( + left_point, + excerpt_start.lines + (buffer_left_point - buffer_start_point), + "clip_point({:?}, Left). buffer: {:?}, buffer point: {:?}", + point, + buffer_id, + buffer_point, + ); + assert_eq!( + right_point, + excerpt_start.lines + (buffer_right_point - buffer_start_point), + "clip_point({:?}, Right). buffer: {:?}, buffer point: {:?}", + point, + buffer_id, + buffer_point, + ); + + assert_eq!( + snapshot.point_to_offset(left_point), + left_offset, + "point_to_offset({:?})", + left_point, + ); + assert_eq!( + snapshot.offset_to_point(left_offset), + left_point, + "offset_to_point({:?})", + left_offset, + ); + + offset += 1; + buffer_offset += 1; + if ch == '\n' { + point += Point::new(1, 0); + buffer_point += Point::new(1, 0); + } else { + point += Point::new(0, 1); + buffer_point += Point::new(0, 1); + } + } + + for _ in 0..ch.len_utf16() { + let left_point_utf16 = + snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Left); + let right_point_utf16 = + snapshot.clip_point_utf16(Unclipped(point_utf16), Bias::Right); + let buffer_left_point_utf16 = + buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Left); + let buffer_right_point_utf16 = + buffer.clip_point_utf16(Unclipped(buffer_point_utf16), Bias::Right); + assert_eq!( + left_point_utf16, + excerpt_start.lines_utf16() + + (buffer_left_point_utf16 - buffer_start_point_utf16), + "clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}", + point_utf16, + buffer_id, + buffer_point_utf16, + ); + assert_eq!( + right_point_utf16, + excerpt_start.lines_utf16() + + (buffer_right_point_utf16 - buffer_start_point_utf16), + "clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}", + point_utf16, + buffer_id, + buffer_point_utf16, + ); + + if ch == '\n' { + point_utf16 += PointUtf16::new(1, 0); + buffer_point_utf16 += PointUtf16::new(1, 0); + } else { + point_utf16 += PointUtf16::new(0, 1); + buffer_point_utf16 += PointUtf16::new(0, 1); + } + } + } + } + + for (row, line) in expected_text.split('\n').enumerate() { + assert_eq!( + snapshot.line_len(row as u32), + line.len() as u32, + "line_len({}).", + row + ); + } + + let text_rope = Rope::from(expected_text.as_str()); + for _ in 0..10 { + let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right); + let start_ix = text_rope.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + + let text_for_range = snapshot + .text_for_range(start_ix..end_ix) + .collect::(); + assert_eq!( + text_for_range, + &expected_text[start_ix..end_ix], + "incorrect text for range {:?}", + start_ix..end_ix + ); + + let excerpted_buffer_ranges = multibuffer + .read(cx) + .range_to_buffer_ranges(start_ix..end_ix, cx); + let excerpted_buffers_text = excerpted_buffer_ranges + .iter() + .map(|(buffer, buffer_range, _)| { + buffer + .read(cx) + .text_for_range(buffer_range.clone()) + .collect::() + }) + .collect::>() + .join("\n"); + assert_eq!(excerpted_buffers_text, text_for_range); + if !expected_excerpts.is_empty() { + assert!(!excerpted_buffer_ranges.is_empty()); + } + + let expected_summary = TextSummary::from(&expected_text[start_ix..end_ix]); + assert_eq!( + snapshot.text_summary_for_range::(start_ix..end_ix), + expected_summary, + "incorrect summary for range {:?}", + start_ix..end_ix + ); + } + + // Anchor resolution + let summaries = snapshot.summaries_for_anchors::(&anchors); + assert_eq!(anchors.len(), summaries.len()); + for (anchor, resolved_offset) in anchors.iter().zip(summaries) { + assert!(resolved_offset <= snapshot.len()); + assert_eq!( + snapshot.summary_for_anchor::(anchor), + resolved_offset + ); + } + + for _ in 0..10 { + let end_ix = text_rope.clip_offset(rng.gen_range(0..=text_rope.len()), Bias::Right); + assert_eq!( + snapshot.reversed_chars_at(end_ix).collect::(), + expected_text[..end_ix].chars().rev().collect::(), + ); + } + + for _ in 0..10 { + let end_ix = rng.gen_range(0..=text_rope.len()); + let start_ix = rng.gen_range(0..=end_ix); + assert_eq!( + snapshot + .bytes_in_range(start_ix..end_ix) + .flatten() + .copied() + .collect::>(), + expected_text.as_bytes()[start_ix..end_ix].to_vec(), + "bytes_in_range({:?})", + start_ix..end_ix, + ); + } + } + + let snapshot = multibuffer.read(cx).snapshot(cx); + for (old_snapshot, subscription) in old_versions { + let edits = subscription.consume().into_inner(); + + log::info!( + "applying subscription edits to old text: {:?}: {:?}", + old_snapshot.text(), + edits, + ); + + let mut text = old_snapshot.text(); + for edit in edits { + let new_text: String = snapshot.text_for_range(edit.new.clone()).collect(); + text.replace_range(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), snapshot.text()); + } + } + + #[gpui2::test] + fn test_history(cx: &mut AppContext) { + let test_settings = SettingsStore::test(cx); + cx.set_global(test_settings); + + let buffer_1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "1234")); + let buffer_2 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "5678")); + let multibuffer = cx.build_model(|_| MultiBuffer::new(0)); + let group_interval = multibuffer.read(cx).history.group_interval; + multibuffer.update(cx, |multibuffer, cx| { + multibuffer.push_excerpts( + buffer_1.clone(), + [ExcerptRange { + context: 0..buffer_1.read(cx).len(), + primary: None, + }], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ExcerptRange { + context: 0..buffer_2.read(cx).len(), + primary: None, + }], + cx, + ); + }); + + let mut now = Instant::now(); + + multibuffer.update(cx, |multibuffer, cx| { + let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap(); + multibuffer.edit( + [ + (Point::new(0, 0)..Point::new(0, 0), "A"), + (Point::new(1, 0)..Point::new(1, 0), "A"), + ], + None, + cx, + ); + multibuffer.edit( + [ + (Point::new(0, 1)..Point::new(0, 1), "B"), + (Point::new(1, 1)..Point::new(1, 1), "B"), + ], + None, + cx, + ); + multibuffer.end_transaction_at(now, cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + // Edit buffer 1 through the multibuffer + now += 2 * group_interval; + multibuffer.start_transaction_at(now, cx); + multibuffer.edit([(2..2, "C")], None, cx); + multibuffer.end_transaction_at(now, cx); + assert_eq!(multibuffer.read(cx).text(), "ABC1234\nAB5678"); + + // Edit buffer 1 independently + buffer_1.update(cx, |buffer_1, cx| { + buffer_1.start_transaction_at(now); + buffer_1.edit([(3..3, "D")], None, cx); + buffer_1.end_transaction_at(now, cx); + + now += 2 * group_interval; + buffer_1.start_transaction_at(now); + buffer_1.edit([(4..4, "E")], None, cx); + buffer_1.end_transaction_at(now, cx); + }); + assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678"); + + // An undo in the multibuffer undoes the multibuffer transaction + // and also any individual buffer edits that have occurred since + // that transaction. + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\nAB5678"); + + // Undo buffer 2 independently. + buffer_2.update(cx, |buffer_2, cx| buffer_2.undo(cx)); + assert_eq!(multibuffer.read(cx).text(), "ABCDE1234\n5678"); + + // An undo in the multibuffer undoes the components of the + // the last multibuffer transaction that are not already undone. + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\n5678"); + + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "AB1234\nAB5678"); + + buffer_1.update(cx, |buffer_1, cx| buffer_1.redo(cx)); + assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678"); + + // Redo stack gets cleared after an edit. + now += 2 * group_interval; + multibuffer.start_transaction_at(now, cx); + multibuffer.edit([(0..0, "X")], None, cx); + multibuffer.end_transaction_at(now, cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678"); + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + + // Transactions can be grouped manually. + multibuffer.redo(cx); + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + multibuffer.group_until_transaction(transaction_1, cx); + multibuffer.undo(cx); + assert_eq!(multibuffer.read(cx).text(), "1234\n5678"); + multibuffer.redo(cx); + assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678"); + }); + } +} From 18431051d9d750d9e66284a71f7a55a1e31c1374 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 03:23:00 +0100 Subject: [PATCH 04/15] Rework `theme2` with new theme structure (#3194) This PR reworks the theme definition in the `theme2` crate to be based off of the new theme work that @iamnbutler has been working on. We're still developing the new theme system, but it is complete enough that we can now load the default theme and use it to theme the storybook (albeit with some further refining of the color palette required). --------- Co-authored-by: Nate Butler Co-authored-by: Marshall Bowers --- Cargo.lock | 1 + crates/storybook2/src/stories/colors.rs | 7 +- crates/storybook2/src/stories/focus.rs | 16 +- crates/storybook2/src/stories/scroll.rs | 10 +- crates/storybook2/src/storybook2.rs | 3 +- crates/theme2/Cargo.toml | 12 +- crates/theme2/src/colors.rs | 143 +++ .../src/{default.rs => default_colors.rs} | 838 ++++++++++++++++-- crates/theme2/src/default_theme.rs | 58 ++ crates/theme2/src/scale.rs | 193 ++-- crates/theme2/src/settings.rs | 10 +- crates/theme2/src/syntax.rs | 227 +++++ crates/theme2/src/theme2.rs | 63 +- crates/theme2/src/utils.rs | 43 + crates/ui2/src/components/breadcrumb.rs | 24 +- crates/ui2/src/components/buffer.rs | 19 +- crates/ui2/src/components/buffer_search.rs | 4 +- crates/ui2/src/components/collab_panel.rs | 30 +- crates/ui2/src/components/context_menu.rs | 6 +- crates/ui2/src/components/icon_button.rs | 14 +- crates/ui2/src/components/keybinding.rs | 6 +- crates/ui2/src/components/list.rs | 34 +- crates/ui2/src/components/modal.rs | 10 +- crates/ui2/src/components/multi_buffer.rs | 16 +- .../ui2/src/components/notification_toast.rs | 7 +- .../ui2/src/components/notifications_panel.rs | 4 +- crates/ui2/src/components/palette.rs | 16 +- crates/ui2/src/components/panel.rs | 6 +- crates/ui2/src/components/panes.rs | 4 +- crates/ui2/src/components/player_stack.rs | 4 +- crates/ui2/src/components/project_panel.rs | 4 +- crates/ui2/src/components/status_bar.rs | 4 +- crates/ui2/src/components/tab.rs | 13 +- crates/ui2/src/components/tab_bar.rs | 4 +- crates/ui2/src/components/terminal.rs | 6 +- crates/ui2/src/components/title_bar.rs | 3 +- crates/ui2/src/components/toast.rs | 4 +- crates/ui2/src/components/toolbar.rs | 14 +- crates/ui2/src/components/traffic_lights.rs | 10 +- crates/ui2/src/components/workspace.rs | 8 +- crates/ui2/src/elements/avatar.rs | 5 +- crates/ui2/src/elements/button.rs | 18 +- crates/ui2/src/elements/details.rs | 7 +- crates/ui2/src/elements/icon.rs | 13 +- crates/ui2/src/elements/input.rs | 16 +- crates/ui2/src/elements/label.rs | 16 +- crates/ui2/src/elements/player.rs | 8 +- crates/ui2/src/elements/tool_divider.rs | 4 +- crates/ui2/src/prelude.rs | 16 +- crates/ui2/src/static_data.rs | 96 +- crates/ui2/src/story.rs | 12 +- 51 files changed, 1615 insertions(+), 494 deletions(-) create mode 100644 crates/theme2/src/colors.rs rename crates/theme2/src/{default.rs => default_colors.rs} (65%) create mode 100644 crates/theme2/src/default_theme.rs create mode 100644 crates/theme2/src/syntax.rs create mode 100644 crates/theme2/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 3aca27106c..272320895d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8759,6 +8759,7 @@ dependencies = [ "gpui2", "indexmap 1.9.3", "parking_lot 0.11.2", + "refineable", "schemars", "serde", "serde_derive", diff --git a/crates/storybook2/src/stories/colors.rs b/crates/storybook2/src/stories/colors.rs index afc29660ff..0dd56071c8 100644 --- a/crates/storybook2/src/stories/colors.rs +++ b/crates/storybook2/src/stories/colors.rs @@ -1,5 +1,6 @@ use crate::story::Story; use gpui2::{px, Div, Render}; +use theme2::default_color_scales; use ui::prelude::*; pub struct ColorsStory; @@ -8,7 +9,7 @@ impl Render for ColorsStory { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let color_scales = theme2::default_color_scales(); + let color_scales = default_color_scales(); Story::container(cx) .child(Story::title(cx, "Colors")) @@ -20,14 +21,14 @@ impl Render for ColorsStory { .gap_1() .overflow_y_scroll() .text_color(gpui2::white()) - .children(color_scales.into_iter().map(|(name, scale)| { + .children(color_scales.into_iter().map(|scale| { div() .flex() .child( div() .w(px(75.)) .line_height(px(24.)) - .child(name.to_string()), + .child(scale.name().to_string()), ) .child(div().flex().gap_1().children( (1..=12).map(|step| div().flex().size_6().bg(scale.step(cx, step))), diff --git a/crates/storybook2/src/stories/focus.rs b/crates/storybook2/src/stories/focus.rs index f3f6a8d5fb..aa71040b47 100644 --- a/crates/storybook2/src/stories/focus.rs +++ b/crates/storybook2/src/stories/focus.rs @@ -3,7 +3,7 @@ use gpui2::{ StatelessInteractive, Styled, View, VisualContext, WindowContext, }; use serde::Deserialize; -use theme2::theme; +use theme2::ActiveTheme; #[derive(Clone, Default, PartialEq, Deserialize)] struct ActionA; @@ -34,13 +34,13 @@ impl Render for FocusStory { type Element = Div, FocusEnabled>; fn render(&mut self, cx: &mut gpui2::ViewContext) -> Self::Element { - let theme = theme(cx); - let color_1 = theme.git_created; - let color_2 = theme.git_modified; - let color_3 = theme.git_deleted; - let color_4 = theme.git_conflict; - let color_5 = theme.git_ignored; - let color_6 = theme.git_renamed; + let theme = cx.theme(); + let color_1 = theme.styles.git.created; + let color_2 = theme.styles.git.modified; + let color_3 = theme.styles.git.deleted; + let color_4 = theme.styles.git.conflict; + let color_5 = theme.styles.git.ignored; + let color_6 = theme.styles.git.renamed; let child_1 = cx.focus_handle(); let child_2 = cx.focus_handle(); diff --git a/crates/storybook2/src/stories/scroll.rs b/crates/storybook2/src/stories/scroll.rs index b504a512a6..9236629c34 100644 --- a/crates/storybook2/src/stories/scroll.rs +++ b/crates/storybook2/src/stories/scroll.rs @@ -2,7 +2,7 @@ use gpui2::{ div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled, View, VisualContext, WindowContext, }; -use theme2::theme; +use theme2::ActiveTheme; pub struct ScrollStory; @@ -16,13 +16,13 @@ impl Render for ScrollStory { type Element = Div>; fn render(&mut self, cx: &mut gpui2::ViewContext) -> Self::Element { - let theme = theme(cx); - let color_1 = theme.git_created; - let color_2 = theme.git_modified; + let theme = cx.theme(); + let color_1 = theme.styles.git.created; + let color_2 = theme.styles.git.modified; div() .id("parent") - .bg(theme.background) + .bg(theme.colors().background) .size_full() .overflow_scroll() .children((0..10).map(|row| { diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index c2903c88e1..6028695d7f 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -68,10 +68,9 @@ fn main() { let theme_registry = cx.global::(); let mut theme_settings = ThemeSettings::get_global(cx).clone(); - theme_settings.active_theme = theme_registry.get(&theme_name).unwrap(); + theme_settings.old_active_theme = theme_registry.get(&theme_name).unwrap(); ThemeSettings::override_global(theme_settings, cx); - cx.set_global(theme.clone()); ui::settings::init(cx); let window = cx.open_window( diff --git a/crates/theme2/Cargo.toml b/crates/theme2/Cargo.toml index 2f89425d21..6b273e5042 100644 --- a/crates/theme2/Cargo.toml +++ b/crates/theme2/Cargo.toml @@ -16,19 +16,19 @@ path = "src/theme2.rs" doctest = false [dependencies] -gpui2 = { path = "../gpui2" } -fs = { path = "../fs" } -schemars.workspace = true -settings2 = { path = "../settings2" } -util = { path = "../util" } - anyhow.workspace = true +fs = { path = "../fs" } +gpui2 = { path = "../gpui2" } indexmap = "1.6.2" parking_lot.workspace = true +refineable.workspace = true +schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true +settings2 = { path = "../settings2" } toml.workspace = true +util = { path = "../util" } [dev-dependencies] gpui2 = { path = "../gpui2", features = ["test-support"] } diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs new file mode 100644 index 0000000000..d23fde1ee0 --- /dev/null +++ b/crates/theme2/src/colors.rs @@ -0,0 +1,143 @@ +use gpui2::Hsla; +use refineable::Refineable; + +use crate::{generate_struct_with_overrides, SyntaxStyles}; + +pub struct SystemColors { + pub transparent: Hsla, + pub mac_os_traffic_light_red: Hsla, + pub mac_os_traffic_light_yellow: Hsla, + pub mac_os_traffic_light_green: Hsla, +} + +#[derive(Debug, Clone, Copy)] +pub struct PlayerColor { + pub cursor: Hsla, + pub background: Hsla, + pub selection: Hsla, +} + +pub struct PlayerColors(pub Vec); + +#[derive(Refineable, Clone, Debug)] +#[refineable(debug)] +pub struct StatusColors { + pub conflict: Hsla, + pub created: Hsla, + pub deleted: Hsla, + pub error: Hsla, + pub hidden: Hsla, + pub ignored: Hsla, + pub info: Hsla, + pub modified: Hsla, + pub renamed: Hsla, + pub success: Hsla, + pub warning: Hsla, +} + +#[derive(Refineable, Clone, Debug)] +#[refineable(debug)] +pub struct GitStatusColors { + pub conflict: Hsla, + pub created: Hsla, + pub deleted: Hsla, + pub ignored: Hsla, + pub modified: Hsla, + pub renamed: Hsla, +} + +#[derive(Refineable, Clone, Debug)] +#[refineable(debug)] +pub struct ThemeColors { + pub border: Hsla, + pub border_variant: Hsla, + pub border_focused: Hsla, + pub border_transparent: Hsla, + pub elevated_surface: Hsla, + pub surface: Hsla, + pub background: Hsla, + pub element: Hsla, + pub element_hover: Hsla, + pub element_active: Hsla, + pub element_selected: Hsla, + pub element_disabled: Hsla, + pub element_placeholder: Hsla, + pub ghost_element: Hsla, + pub ghost_element_hover: Hsla, + pub ghost_element_active: Hsla, + pub ghost_element_selected: Hsla, + pub ghost_element_disabled: Hsla, + pub text: Hsla, + pub text_muted: Hsla, + pub text_placeholder: Hsla, + pub text_disabled: Hsla, + pub text_accent: Hsla, + pub icon: Hsla, + pub icon_muted: Hsla, + pub icon_disabled: Hsla, + pub icon_placeholder: Hsla, + pub icon_accent: Hsla, + pub status_bar: Hsla, + pub title_bar: Hsla, + pub toolbar: Hsla, + pub tab_bar: Hsla, + pub editor: Hsla, + pub editor_subheader: Hsla, + pub editor_active_line: Hsla, +} + +generate_struct_with_overrides! { + ThemeStyle, + ThemeStyleOverrides, + system: SystemColors, + colors: ThemeColors, + status: StatusColors, + git: GitStatusColors, + player: PlayerColors, + syntax: SyntaxStyles +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn override_a_single_theme_color() { + let mut colors = ThemeColors::default_light(); + + let magenta: Hsla = gpui2::rgb(0xff00ff); + + assert_ne!(colors.text, magenta); + + let overrides = ThemeColorsRefinement { + text: Some(magenta), + ..Default::default() + }; + + colors.refine(&overrides); + + assert_eq!(colors.text, magenta); + } + + #[test] + fn override_multiple_theme_colors() { + let mut colors = ThemeColors::default_light(); + + let magenta: Hsla = gpui2::rgb(0xff00ff); + let green: Hsla = gpui2::rgb(0x00ff00); + + assert_ne!(colors.text, magenta); + assert_ne!(colors.background, green); + + let overrides = ThemeColorsRefinement { + text: Some(magenta), + background: Some(green), + ..Default::default() + }; + + colors.refine(&overrides); + + assert_eq!(colors.text, magenta); + assert_eq!(colors.background, green); + } +} diff --git a/crates/theme2/src/default.rs b/crates/theme2/src/default_colors.rs similarity index 65% rename from crates/theme2/src/default.rs rename to crates/theme2/src/default_colors.rs index 41d408f980..e8146cdeaa 100644 --- a/crates/theme2/src/default.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,10 +1,704 @@ -use gpui2::Rgba; +use gpui2::{hsla, FontWeight, Rgba}; use indexmap::IndexMap; -use crate::scale::{ColorScaleName, ColorScaleSet, ColorScales}; +use crate::{ + colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, + scale::{ColorScaleSet, ColorScales}, + syntax::{SyntaxStyleName, SyntaxStyles}, + SyntaxStyle, +}; + +impl Default for SystemColors { + fn default() -> Self { + Self { + transparent: hsla(0.0, 0.0, 0.0, 0.0), + mac_os_traffic_light_red: hsla(0.0139, 0.79, 0.65, 1.0), + mac_os_traffic_light_yellow: hsla(0.114, 0.88, 0.63, 1.0), + mac_os_traffic_light_green: hsla(0.313, 0.49, 0.55, 1.0), + } + } +} + +impl Default for StatusColors { + fn default() -> Self { + Self { + conflict: gpui2::black(), + created: gpui2::black(), + deleted: gpui2::black(), + error: gpui2::black(), + hidden: gpui2::black(), + ignored: gpui2::black(), + info: gpui2::black(), + modified: gpui2::black(), + renamed: gpui2::black(), + success: gpui2::black(), + warning: gpui2::black(), + } + } +} + +impl Default for GitStatusColors { + fn default() -> Self { + Self { + conflict: gpui2::rgba(0xdec184ff).into(), + created: gpui2::rgba(0xa1c181ff).into(), + deleted: gpui2::rgba(0xd07277ff).into(), + ignored: gpui2::rgba(0x555a63ff).into(), + modified: gpui2::rgba(0x74ade8ff).into(), + renamed: gpui2::rgba(0xdec184ff).into(), + } + } +} + +impl Default for PlayerColors { + fn default() -> Self { + Self(vec![ + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + PlayerColor { + cursor: hsla(0.0, 0.0, 0.0, 0.0), + background: hsla(0.0, 0.0, 0.0, 0.0), + selection: hsla(0.0, 0.0, 0.0, 0.0), + }, + ]) + } +} + +impl SyntaxStyles { + pub fn default_light() -> Self { + use SyntaxStyleName::*; + + let neutral: ColorScaleSet = slate().into(); + + Self(IndexMap::from_iter([ + ( + Comment, + SyntaxStyle::builder().color(neutral.light(11)).build(), + ), + ( + CommentDoc, + SyntaxStyle::builder().color(neutral.light(11)).build(), + ), + ( + Primary, + SyntaxStyle::builder().color(neutral.light(12)).build(), + ), + ( + Predictive, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + Hint, + SyntaxStyle::builder() + .color(ColorScaleSet::from(cyan()).light(10)) + .build(), + ), + ( + Emphasis, + SyntaxStyle::builder().weight(FontWeight(600.0)).build(), + ), + ( + EmphasisStrong, + SyntaxStyle::builder().weight(FontWeight(800.0)).build(), + ), + ( + Title, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + LinkUri, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).light(12)) + .build(), + ), + ( + LinkText, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).light(12)) + .build(), + ), + ( + TextLiteral, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).light(12)) + .build(), + ), + ( + Punctuation, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + PunctuationBracket, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + PunctuationDelimiter, + SyntaxStyle::builder().color(neutral.light(10)).build(), + ), + ( + PunctuationSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + PunctuationListMarker, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).light(12)) + .build(), + ), + ( + String, + SyntaxStyle::builder() + .color(ColorScaleSet::from(green()).light(12)) + .build(), + ), + ( + StringSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + StringSpecialSymbol, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + StringEscape, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).light(12)) + .build(), + ), + ( + StringRegex, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).light(12)) + .build(), + ), + ( + Constructor, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).light(12)) + .build(), + ), + // TODO: Continue assigning syntax colors from here + ( + Variant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Type, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + TypeBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Variable, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + VariableSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Label, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Tag, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Attribute, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Property, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Constant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Keyword, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Enum, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Operator, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Number, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Boolean, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + ConstantBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Function, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionSpecialDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionMethod, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + FunctionMethodBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Preproc, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ( + Embedded, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).light(12)) + .build(), + ), + ])) + } + + pub fn default_dark() -> Self { + use SyntaxStyleName::*; + + let neutral: ColorScaleSet = slate().into(); + + Self(IndexMap::from_iter([ + ( + Comment, + SyntaxStyle::builder().color(neutral.dark(11)).build(), + ), + ( + CommentDoc, + SyntaxStyle::builder().color(neutral.dark(11)).build(), + ), + ( + Primary, + SyntaxStyle::builder().color(neutral.dark(12)).build(), + ), + ( + Predictive, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + Hint, + SyntaxStyle::builder() + .color(ColorScaleSet::from(cyan()).dark(10)) + .build(), + ), + ( + Emphasis, + SyntaxStyle::builder().weight(FontWeight(600.0)).build(), + ), + ( + EmphasisStrong, + SyntaxStyle::builder().weight(FontWeight(800.0)).build(), + ), + ( + Title, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + LinkUri, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).dark(12)) + .build(), + ), + ( + LinkText, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).dark(12)) + .build(), + ), + ( + TextLiteral, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).dark(12)) + .build(), + ), + ( + Punctuation, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + PunctuationBracket, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + PunctuationDelimiter, + SyntaxStyle::builder().color(neutral.dark(10)).build(), + ), + ( + PunctuationSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + PunctuationListMarker, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).dark(12)) + .build(), + ), + ( + String, + SyntaxStyle::builder() + .color(ColorScaleSet::from(green()).dark(12)) + .build(), + ), + ( + StringSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + StringSpecialSymbol, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + StringEscape, + SyntaxStyle::builder() + .color(ColorScaleSet::from(blue()).dark(12)) + .build(), + ), + ( + StringRegex, + SyntaxStyle::builder() + .color(ColorScaleSet::from(orange()).dark(12)) + .build(), + ), + ( + Constructor, + SyntaxStyle::builder() + .color(ColorScaleSet::from(purple()).dark(12)) + .build(), + ), + // TODO: Continue assigning syntax colors from here + ( + Variant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Type, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + TypeBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Variable, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + VariableSpecial, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Label, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Tag, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Attribute, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Property, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Constant, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Keyword, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Enum, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Operator, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Number, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Boolean, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + ConstantBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Function, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionSpecialDefinition, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionMethod, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + FunctionMethodBuiltin, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Preproc, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ( + Embedded, + SyntaxStyle::builder() + .color(ColorScaleSet::from(red()).dark(12)) + .build(), + ), + ])) + } +} + +impl ThemeColors { + pub fn default_light() -> Self { + Self { + border: gpui2::white(), + border_variant: gpui2::white(), + border_focused: gpui2::white(), + border_transparent: gpui2::white(), + elevated_surface: gpui2::white(), + surface: gpui2::white(), + background: gpui2::white(), + element: gpui2::white(), + element_hover: gpui2::white(), + element_active: gpui2::white(), + element_selected: gpui2::white(), + element_disabled: gpui2::white(), + element_placeholder: gpui2::white(), + ghost_element: gpui2::white(), + ghost_element_hover: gpui2::white(), + ghost_element_active: gpui2::white(), + ghost_element_selected: gpui2::white(), + ghost_element_disabled: gpui2::white(), + text: gpui2::white(), + text_muted: gpui2::white(), + text_placeholder: gpui2::white(), + text_disabled: gpui2::white(), + text_accent: gpui2::white(), + icon: gpui2::white(), + icon_muted: gpui2::white(), + icon_disabled: gpui2::white(), + icon_placeholder: gpui2::white(), + icon_accent: gpui2::white(), + status_bar: gpui2::white(), + title_bar: gpui2::white(), + toolbar: gpui2::white(), + tab_bar: gpui2::white(), + editor: gpui2::white(), + editor_subheader: gpui2::white(), + editor_active_line: gpui2::white(), + } + } + + pub fn default_dark() -> Self { + Self { + border: gpui2::rgba(0x464b57ff).into(), + border_variant: gpui2::rgba(0x464b57ff).into(), + border_focused: gpui2::rgba(0x293b5bff).into(), + border_transparent: gpui2::rgba(0x00000000).into(), + elevated_surface: gpui2::rgba(0x3b414dff).into(), + surface: gpui2::rgba(0x2f343eff).into(), + background: gpui2::rgba(0x3b414dff).into(), + element: gpui2::rgba(0x3b414dff).into(), + element_hover: gpui2::rgba(0xffffff1e).into(), + element_active: gpui2::rgba(0xffffff28).into(), + element_selected: gpui2::rgba(0x18243dff).into(), + element_disabled: gpui2::rgba(0x00000000).into(), + element_placeholder: gpui2::black(), + ghost_element: gpui2::rgba(0x00000000).into(), + ghost_element_hover: gpui2::rgba(0xffffff14).into(), + ghost_element_active: gpui2::rgba(0xffffff1e).into(), + ghost_element_selected: gpui2::rgba(0x18243dff).into(), + ghost_element_disabled: gpui2::rgba(0x00000000).into(), + text: gpui2::rgba(0xc8ccd4ff).into(), + text_muted: gpui2::rgba(0x838994ff).into(), + text_placeholder: gpui2::rgba(0xd07277ff).into(), + text_disabled: gpui2::rgba(0x555a63ff).into(), + text_accent: gpui2::rgba(0x74ade8ff).into(), + icon: gpui2::black(), + icon_muted: gpui2::rgba(0x838994ff).into(), + icon_disabled: gpui2::black(), + icon_placeholder: gpui2::black(), + icon_accent: gpui2::black(), + status_bar: gpui2::rgba(0x3b414dff).into(), + title_bar: gpui2::rgba(0x3b414dff).into(), + toolbar: gpui2::rgba(0x282c33ff).into(), + tab_bar: gpui2::rgba(0x2f343eff).into(), + editor: gpui2::rgba(0x282c33ff).into(), + editor_subheader: gpui2::rgba(0x2f343eff).into(), + editor_active_line: gpui2::rgba(0x2f343eff).into(), + } + } +} struct DefaultColorScaleSet { - scale: ColorScaleName, + scale: &'static str, light: [&'static str; 12], light_alpha: [&'static str; 12], dark: [&'static str; 12], @@ -32,48 +726,46 @@ impl From for ColorScaleSet { } pub fn default_color_scales() -> ColorScales { - use ColorScaleName::*; - - IndexMap::from_iter([ - (Gray, gray().into()), - (Mauve, mauve().into()), - (Slate, slate().into()), - (Sage, sage().into()), - (Olive, olive().into()), - (Sand, sand().into()), - (Gold, gold().into()), - (Bronze, bronze().into()), - (Brown, brown().into()), - (Yellow, yellow().into()), - (Amber, amber().into()), - (Orange, orange().into()), - (Tomato, tomato().into()), - (Red, red().into()), - (Ruby, ruby().into()), - (Crimson, crimson().into()), - (Pink, pink().into()), - (Plum, plum().into()), - (Purple, purple().into()), - (Violet, violet().into()), - (Iris, iris().into()), - (Indigo, indigo().into()), - (Blue, blue().into()), - (Cyan, cyan().into()), - (Teal, teal().into()), - (Jade, jade().into()), - (Green, green().into()), - (Grass, grass().into()), - (Lime, lime().into()), - (Mint, mint().into()), - (Sky, sky().into()), - (Black, black().into()), - (White, white().into()), - ]) + ColorScales { + gray: gray().into(), + mauve: mauve().into(), + slate: slate().into(), + sage: sage().into(), + olive: olive().into(), + sand: sand().into(), + gold: gold().into(), + bronze: bronze().into(), + brown: brown().into(), + yellow: yellow().into(), + amber: amber().into(), + orange: orange().into(), + tomato: tomato().into(), + red: red().into(), + ruby: ruby().into(), + crimson: crimson().into(), + pink: pink().into(), + plum: plum().into(), + purple: purple().into(), + violet: violet().into(), + iris: iris().into(), + indigo: indigo().into(), + blue: blue().into(), + cyan: cyan().into(), + teal: teal().into(), + jade: jade().into(), + green: green().into(), + grass: grass().into(), + lime: lime().into(), + mint: mint().into(), + sky: sky().into(), + black: black().into(), + white: white().into(), + } } fn gray() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Gray, + scale: "Gray", light: [ "#fcfcfcff", "#f9f9f9ff", @@ -135,7 +827,7 @@ fn gray() -> DefaultColorScaleSet { fn mauve() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Mauve, + scale: "Mauve", light: [ "#fdfcfdff", "#faf9fbff", @@ -197,7 +889,7 @@ fn mauve() -> DefaultColorScaleSet { fn slate() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Slate, + scale: "Slate", light: [ "#fcfcfdff", "#f9f9fbff", @@ -259,7 +951,7 @@ fn slate() -> DefaultColorScaleSet { fn sage() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Sage, + scale: "Sage", light: [ "#fbfdfcff", "#f7f9f8ff", @@ -321,7 +1013,7 @@ fn sage() -> DefaultColorScaleSet { fn olive() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Olive, + scale: "Olive", light: [ "#fcfdfcff", "#f8faf8ff", @@ -383,7 +1075,7 @@ fn olive() -> DefaultColorScaleSet { fn sand() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Sand, + scale: "Sand", light: [ "#fdfdfcff", "#f9f9f8ff", @@ -445,7 +1137,7 @@ fn sand() -> DefaultColorScaleSet { fn gold() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Gold, + scale: "Gold", light: [ "#fdfdfcff", "#faf9f2ff", @@ -507,7 +1199,7 @@ fn gold() -> DefaultColorScaleSet { fn bronze() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Bronze, + scale: "Bronze", light: [ "#fdfcfcff", "#fdf7f5ff", @@ -569,7 +1261,7 @@ fn bronze() -> DefaultColorScaleSet { fn brown() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Brown, + scale: "Brown", light: [ "#fefdfcff", "#fcf9f6ff", @@ -631,7 +1323,7 @@ fn brown() -> DefaultColorScaleSet { fn yellow() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Yellow, + scale: "Yellow", light: [ "#fdfdf9ff", "#fefce9ff", @@ -693,7 +1385,7 @@ fn yellow() -> DefaultColorScaleSet { fn amber() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Amber, + scale: "Amber", light: [ "#fefdfbff", "#fefbe9ff", @@ -755,7 +1447,7 @@ fn amber() -> DefaultColorScaleSet { fn orange() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Orange, + scale: "Orange", light: [ "#fefcfbff", "#fff7edff", @@ -817,7 +1509,7 @@ fn orange() -> DefaultColorScaleSet { fn tomato() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Tomato, + scale: "Tomato", light: [ "#fffcfcff", "#fff8f7ff", @@ -879,7 +1571,7 @@ fn tomato() -> DefaultColorScaleSet { fn red() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Red, + scale: "Red", light: [ "#fffcfcff", "#fff7f7ff", @@ -941,7 +1633,7 @@ fn red() -> DefaultColorScaleSet { fn ruby() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Ruby, + scale: "Ruby", light: [ "#fffcfdff", "#fff7f8ff", @@ -1003,7 +1695,7 @@ fn ruby() -> DefaultColorScaleSet { fn crimson() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Crimson, + scale: "Crimson", light: [ "#fffcfdff", "#fef7f9ff", @@ -1065,7 +1757,7 @@ fn crimson() -> DefaultColorScaleSet { fn pink() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Pink, + scale: "Pink", light: [ "#fffcfeff", "#fef7fbff", @@ -1127,7 +1819,7 @@ fn pink() -> DefaultColorScaleSet { fn plum() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Plum, + scale: "Plum", light: [ "#fefcffff", "#fdf7fdff", @@ -1189,7 +1881,7 @@ fn plum() -> DefaultColorScaleSet { fn purple() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Purple, + scale: "Purple", light: [ "#fefcfeff", "#fbf7feff", @@ -1251,7 +1943,7 @@ fn purple() -> DefaultColorScaleSet { fn violet() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Violet, + scale: "Violet", light: [ "#fdfcfeff", "#faf8ffff", @@ -1313,7 +2005,7 @@ fn violet() -> DefaultColorScaleSet { fn iris() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Iris, + scale: "Iris", light: [ "#fdfdffff", "#f8f8ffff", @@ -1375,7 +2067,7 @@ fn iris() -> DefaultColorScaleSet { fn indigo() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Indigo, + scale: "Indigo", light: [ "#fdfdfeff", "#f7f9ffff", @@ -1437,7 +2129,7 @@ fn indigo() -> DefaultColorScaleSet { fn blue() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Blue, + scale: "Blue", light: [ "#fbfdffff", "#f4faffff", @@ -1499,7 +2191,7 @@ fn blue() -> DefaultColorScaleSet { fn cyan() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Cyan, + scale: "Cyan", light: [ "#fafdfeff", "#f2fafbff", @@ -1561,7 +2253,7 @@ fn cyan() -> DefaultColorScaleSet { fn teal() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Teal, + scale: "Teal", light: [ "#fafefdff", "#f3fbf9ff", @@ -1623,7 +2315,7 @@ fn teal() -> DefaultColorScaleSet { fn jade() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Jade, + scale: "Jade", light: [ "#fbfefdff", "#f4fbf7ff", @@ -1685,7 +2377,7 @@ fn jade() -> DefaultColorScaleSet { fn green() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Green, + scale: "Green", light: [ "#fbfefcff", "#f4fbf6ff", @@ -1747,7 +2439,7 @@ fn green() -> DefaultColorScaleSet { fn grass() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Grass, + scale: "Grass", light: [ "#fbfefbff", "#f5fbf5ff", @@ -1809,7 +2501,7 @@ fn grass() -> DefaultColorScaleSet { fn lime() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Lime, + scale: "Lime", light: [ "#fcfdfaff", "#f8faf3ff", @@ -1871,7 +2563,7 @@ fn lime() -> DefaultColorScaleSet { fn mint() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Mint, + scale: "Mint", light: [ "#f9fefdff", "#f2fbf9ff", @@ -1933,7 +2625,7 @@ fn mint() -> DefaultColorScaleSet { fn sky() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Sky, + scale: "Sky", light: [ "#f9feffff", "#f1fafdff", @@ -1995,7 +2687,7 @@ fn sky() -> DefaultColorScaleSet { fn black() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::Black, + scale: "Black", light: [ "#0000000d", "#0000001a", @@ -2057,7 +2749,7 @@ fn black() -> DefaultColorScaleSet { fn white() -> DefaultColorScaleSet { DefaultColorScaleSet { - scale: ColorScaleName::White, + scale: "White", light: [ "#ffffff0d", "#ffffff1a", diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs new file mode 100644 index 0000000000..4b47e403d6 --- /dev/null +++ b/crates/theme2/src/default_theme.rs @@ -0,0 +1,58 @@ +use crate::{ + colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyle}, + default_color_scales, Appearance, SyntaxStyles, ThemeFamily, ThemeVariant, +}; + +fn zed_pro_daylight() -> ThemeVariant { + ThemeVariant { + id: "zed_pro_daylight".to_string(), + name: "Zed Pro Daylight".to_string(), + appearance: Appearance::Light, + styles: ThemeStyle { + system: SystemColors::default(), + colors: ThemeColors::default_light(), + status: StatusColors::default(), + git: GitStatusColors::default(), + player: PlayerColors::default(), + syntax: SyntaxStyles::default_light(), + }, + } +} + +pub(crate) fn zed_pro_moonlight() -> ThemeVariant { + ThemeVariant { + id: "zed_pro_moonlight".to_string(), + name: "Zed Pro Moonlight".to_string(), + appearance: Appearance::Light, + styles: ThemeStyle { + system: SystemColors::default(), + colors: ThemeColors::default_dark(), + status: StatusColors::default(), + git: GitStatusColors::default(), + player: PlayerColors::default(), + syntax: SyntaxStyles::default_dark(), + }, + } +} + +pub fn zed_pro_family() -> ThemeFamily { + ThemeFamily { + id: "zed_pro".to_string(), + name: "Zed Pro".to_string(), + author: "Zed Team".to_string(), + themes: vec![zed_pro_daylight(), zed_pro_moonlight()], + scales: default_color_scales(), + } +} + +impl Default for ThemeFamily { + fn default() -> Self { + zed_pro_family() + } +} + +impl Default for ThemeVariant { + fn default() -> Self { + zed_pro_daylight() + } +} diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index 22a607bf07..21c8592d81 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -1,98 +1,95 @@ -use gpui2::{AppContext, Hsla}; -use indexmap::IndexMap; +use gpui2::{AppContext, Hsla, SharedString}; -use crate::{theme, Appearance}; - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ColorScaleName { - Gray, - Mauve, - Slate, - Sage, - Olive, - Sand, - Gold, - Bronze, - Brown, - Yellow, - Amber, - Orange, - Tomato, - Red, - Ruby, - Crimson, - Pink, - Plum, - Purple, - Violet, - Iris, - Indigo, - Blue, - Cyan, - Teal, - Jade, - Green, - Grass, - Lime, - Mint, - Sky, - Black, - White, -} - -impl std::fmt::Display for ColorScaleName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Gray => "Gray", - Self::Mauve => "Mauve", - Self::Slate => "Slate", - Self::Sage => "Sage", - Self::Olive => "Olive", - Self::Sand => "Sand", - Self::Gold => "Gold", - Self::Bronze => "Bronze", - Self::Brown => "Brown", - Self::Yellow => "Yellow", - Self::Amber => "Amber", - Self::Orange => "Orange", - Self::Tomato => "Tomato", - Self::Red => "Red", - Self::Ruby => "Ruby", - Self::Crimson => "Crimson", - Self::Pink => "Pink", - Self::Plum => "Plum", - Self::Purple => "Purple", - Self::Violet => "Violet", - Self::Iris => "Iris", - Self::Indigo => "Indigo", - Self::Blue => "Blue", - Self::Cyan => "Cyan", - Self::Teal => "Teal", - Self::Jade => "Jade", - Self::Green => "Green", - Self::Grass => "Grass", - Self::Lime => "Lime", - Self::Mint => "Mint", - Self::Sky => "Sky", - Self::Black => "Black", - Self::White => "White", - } - ) - } -} +use crate::{ActiveTheme, Appearance}; pub type ColorScale = [Hsla; 12]; -pub type ColorScales = IndexMap; +pub struct ColorScales { + pub gray: ColorScaleSet, + pub mauve: ColorScaleSet, + pub slate: ColorScaleSet, + pub sage: ColorScaleSet, + pub olive: ColorScaleSet, + pub sand: ColorScaleSet, + pub gold: ColorScaleSet, + pub bronze: ColorScaleSet, + pub brown: ColorScaleSet, + pub yellow: ColorScaleSet, + pub amber: ColorScaleSet, + pub orange: ColorScaleSet, + pub tomato: ColorScaleSet, + pub red: ColorScaleSet, + pub ruby: ColorScaleSet, + pub crimson: ColorScaleSet, + pub pink: ColorScaleSet, + pub plum: ColorScaleSet, + pub purple: ColorScaleSet, + pub violet: ColorScaleSet, + pub iris: ColorScaleSet, + pub indigo: ColorScaleSet, + pub blue: ColorScaleSet, + pub cyan: ColorScaleSet, + pub teal: ColorScaleSet, + pub jade: ColorScaleSet, + pub green: ColorScaleSet, + pub grass: ColorScaleSet, + pub lime: ColorScaleSet, + pub mint: ColorScaleSet, + pub sky: ColorScaleSet, + pub black: ColorScaleSet, + pub white: ColorScaleSet, +} + +impl IntoIterator for ColorScales { + type Item = ColorScaleSet; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + vec![ + self.gray, + self.mauve, + self.slate, + self.sage, + self.olive, + self.sand, + self.gold, + self.bronze, + self.brown, + self.yellow, + self.amber, + self.orange, + self.tomato, + self.red, + self.ruby, + self.crimson, + self.pink, + self.plum, + self.purple, + self.violet, + self.iris, + self.indigo, + self.blue, + self.cyan, + self.teal, + self.jade, + self.green, + self.grass, + self.lime, + self.mint, + self.sky, + self.black, + self.white, + ] + .into_iter() + } +} /// A one-based step in a [`ColorScale`]. pub type ColorScaleStep = usize; pub struct ColorScaleSet { - name: ColorScaleName, + name: SharedString, light: ColorScale, dark: ColorScale, light_alpha: ColorScale, @@ -101,14 +98,14 @@ pub struct ColorScaleSet { impl ColorScaleSet { pub fn new( - name: ColorScaleName, + name: impl Into, light: ColorScale, light_alpha: ColorScale, dark: ColorScale, dark_alpha: ColorScale, ) -> Self { Self { - name, + name: name.into(), light, light_alpha, dark, @@ -116,8 +113,8 @@ impl ColorScaleSet { } } - pub fn name(&self) -> String { - self.name.to_string() + pub fn name(&self) -> &SharedString { + &self.name } pub fn light(&self, step: ColorScaleStep) -> Hsla { @@ -136,27 +133,15 @@ impl ColorScaleSet { self.dark_alpha[step - 1] } - fn current_appearance(cx: &AppContext) -> Appearance { - let theme = theme(cx); - if theme.metadata.is_light { - Appearance::Light - } else { - Appearance::Dark - } - } - pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { - let appearance = Self::current_appearance(cx); - - match appearance { + match cx.theme().appearance { Appearance::Light => self.light(step), Appearance::Dark => self.dark(step), } } pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { - let appearance = Self::current_appearance(cx); - match appearance { + match cx.theme().appearance { Appearance::Light => self.light_alpha(step), Appearance::Dark => self.dark_alpha(step), } diff --git a/crates/theme2/src/settings.rs b/crates/theme2/src/settings.rs index 379b01dd4b..3a61bbbe1e 100644 --- a/crates/theme2/src/settings.rs +++ b/crates/theme2/src/settings.rs @@ -1,4 +1,4 @@ -use crate::{Theme, ThemeRegistry}; +use crate::{zed_pro_moonlight, Theme, ThemeRegistry, ThemeVariant}; use anyhow::Result; use gpui2::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; use schemars::{ @@ -20,7 +20,8 @@ pub struct ThemeSettings { pub buffer_font: Font, pub buffer_font_size: Pixels, pub buffer_line_height: BufferLineHeight, - pub active_theme: Arc, + pub active_theme: Arc, + pub old_active_theme: Arc, } #[derive(Default)] @@ -123,7 +124,8 @@ impl settings2::Settings for ThemeSettings { }, buffer_font_size: defaults.buffer_font_size.unwrap().into(), buffer_line_height: defaults.buffer_line_height.unwrap(), - active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + active_theme: Arc::new(zed_pro_moonlight()), + old_active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; for value in user_values.into_iter().copied().cloned() { @@ -136,7 +138,7 @@ impl settings2::Settings for ThemeSettings { if let Some(value) = &value.theme { if let Some(theme) = themes.get(value).log_err() { - this.active_theme = theme; + this.old_active_theme = theme; } } diff --git a/crates/theme2/src/syntax.rs b/crates/theme2/src/syntax.rs new file mode 100644 index 0000000000..82c4c87796 --- /dev/null +++ b/crates/theme2/src/syntax.rs @@ -0,0 +1,227 @@ +use gpui2::{FontWeight, Hsla, SharedString}; +use indexmap::IndexMap; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum SyntaxStyleName { + Comment, + CommentDoc, + Primary, + Predictive, + Hint, + Emphasis, + EmphasisStrong, + Title, + LinkUri, + LinkText, + TextLiteral, + Punctuation, + PunctuationBracket, + PunctuationDelimiter, + PunctuationSpecial, + PunctuationListMarker, + String, + StringSpecial, + StringSpecialSymbol, + StringEscape, + StringRegex, + Constructor, + Variant, + Type, + TypeBuiltin, + Variable, + VariableSpecial, + Label, + Tag, + Attribute, + Property, + Constant, + Keyword, + Enum, + Operator, + Number, + Boolean, + ConstantBuiltin, + Function, + FunctionBuiltin, + FunctionDefinition, + FunctionSpecialDefinition, + FunctionMethod, + FunctionMethodBuiltin, + Preproc, + Embedded, + Custom(SharedString), +} + +impl std::str::FromStr for SyntaxStyleName { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "attribute" => Self::Attribute, + "boolean" => Self::Boolean, + "comment" => Self::Comment, + "comment.doc" => Self::CommentDoc, + "constant" => Self::Constant, + "constructor" => Self::Constructor, + "embedded" => Self::Embedded, + "emphasis" => Self::Emphasis, + "emphasis.strong" => Self::EmphasisStrong, + "enum" => Self::Enum, + "function" => Self::Function, + "function.builtin" => Self::FunctionBuiltin, + "function.definition" => Self::FunctionDefinition, + "function.special_definition" => Self::FunctionSpecialDefinition, + "function.method" => Self::FunctionMethod, + "function.method_builtin" => Self::FunctionMethodBuiltin, + "hint" => Self::Hint, + "keyword" => Self::Keyword, + "label" => Self::Label, + "link_text" => Self::LinkText, + "link_uri" => Self::LinkUri, + "number" => Self::Number, + "operator" => Self::Operator, + "predictive" => Self::Predictive, + "preproc" => Self::Preproc, + "primary" => Self::Primary, + "property" => Self::Property, + "punctuation" => Self::Punctuation, + "punctuation.bracket" => Self::PunctuationBracket, + "punctuation.delimiter" => Self::PunctuationDelimiter, + "punctuation.list_marker" => Self::PunctuationListMarker, + "punctuation.special" => Self::PunctuationSpecial, + "string" => Self::String, + "string.escape" => Self::StringEscape, + "string.regex" => Self::StringRegex, + "string.special" => Self::StringSpecial, + "string.special.symbol" => Self::StringSpecialSymbol, + "tag" => Self::Tag, + "text.literal" => Self::TextLiteral, + "title" => Self::Title, + "type" => Self::Type, + "type.builtin" => Self::TypeBuiltin, + "variable" => Self::Variable, + "variable.special" => Self::VariableSpecial, + "constant.builtin" => Self::ConstantBuiltin, + "variant" => Self::Variant, + name => Self::Custom(name.to_string().into()), + }) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct SyntaxStyle { + pub color: Hsla, + pub weight: FontWeight, + pub underline: bool, + pub italic: bool, + // Nate: In the future I'd like to enable using background highlights for syntax highlighting + // pub highlight: Hsla, +} + +impl SyntaxStyle { + pub fn builder() -> SyntaxStyleBuilder { + SyntaxStyleBuilder::new() + } +} + +impl Default for SyntaxStyle { + fn default() -> Self { + Self { + color: gpui2::black(), + weight: FontWeight::default(), + italic: false, + underline: false, + } + } +} + +pub struct SyntaxStyleBuilder { + pub color: Hsla, + pub weight: FontWeight, + pub underline: bool, + pub italic: bool, +} + +impl SyntaxStyleBuilder { + pub fn new() -> Self { + SyntaxStyleBuilder { + color: gpui2::black(), + weight: FontWeight::default(), + underline: false, + italic: false, + } + } + + pub fn color(mut self, color: Hsla) -> Self { + self.color = color; + self + } + + pub fn weight(mut self, weight: FontWeight) -> Self { + self.weight = weight; + self + } + + pub fn underline(mut self, underline: bool) -> Self { + self.underline = underline; + self + } + + pub fn italic(mut self, italic: bool) -> Self { + self.italic = italic; + self + } + + pub fn build(self) -> SyntaxStyle { + SyntaxStyle { + color: self.color, + weight: self.weight, + underline: self.underline, + italic: self.italic, + } + } +} + +pub struct SyntaxStyles(pub IndexMap); + +impl SyntaxStyles { + // TOOD: Get this working with `#[cfg(test)]`. Why isn't it? + pub fn new_test(colors: impl IntoIterator) -> Self { + Self(IndexMap::from_iter(colors.into_iter().map( + |(name, color)| { + ( + name.parse().unwrap(), + SyntaxStyle::builder().color(color).build(), + ) + }, + ))) + } + + pub fn get(&self, name: &str) -> SyntaxStyle { + self.0 + .get(&name.parse::().unwrap()) + .cloned() + .unwrap_or_default() + } + + pub fn color(&self, name: &str) -> Hsla { + self.get(name).color + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_syntax_style_name() { + let name = "comment".parse::().unwrap(); + assert_eq!(name, SyntaxStyleName::Comment); + } + + #[test] + fn create_custom_syntax_style_name() { + let name = "custom".parse::().unwrap(); + assert_eq!(name, SyntaxStyleName::Custom("custom".into())); + } +} diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index b96a23c338..ea1ad5b26c 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -1,17 +1,25 @@ -mod default; +mod colors; +mod default_colors; +mod default_theme; mod registry; mod scale; mod settings; +mod syntax; mod themes; +mod utils; -pub use default::*; +pub use colors::*; +pub use default_colors::*; +pub use default_theme::*; pub use registry::*; pub use scale::*; pub use settings::*; +pub use syntax::*; + +use std::sync::Arc; use gpui2::{AppContext, HighlightStyle, Hsla, SharedString}; use settings2::Settings; -use std::sync::Arc; #[derive(Debug, Clone, PartialEq)] pub enum Appearance { @@ -24,12 +32,53 @@ pub fn init(cx: &mut AppContext) { ThemeSettings::register(cx); } -pub fn active_theme<'a>(cx: &'a AppContext) -> &'a Arc { - &ThemeSettings::get_global(cx).active_theme +pub trait ActiveTheme { + fn theme(&self) -> &ThemeVariant; } -pub fn theme(cx: &AppContext) -> Arc { - active_theme(cx).clone() +impl ActiveTheme for AppContext { + fn theme(&self) -> &ThemeVariant { + &ThemeSettings::get_global(self).active_theme + } +} + +pub struct ThemeFamily { + #[allow(dead_code)] + pub(crate) id: String, + pub name: String, + pub author: String, + pub themes: Vec, + pub scales: ColorScales, +} + +impl ThemeFamily {} + +pub struct ThemeVariant { + #[allow(dead_code)] + pub(crate) id: String, + pub name: String, + pub appearance: Appearance, + pub styles: ThemeStyle, +} + +impl ThemeVariant { + /// Returns the [`ThemeColors`] for the theme. + #[inline(always)] + pub fn colors(&self) -> &ThemeColors { + &self.styles.colors + } + + /// Returns the [`SyntaxStyles`] for the theme. + #[inline(always)] + pub fn syntax(&self) -> &SyntaxStyles { + &self.styles.syntax + } + + /// Returns the color for the syntax node with the given name. + #[inline(always)] + pub fn syntax_color(&self, name: &str) -> Hsla { + self.syntax().color(name) + } } pub struct Theme { diff --git a/crates/theme2/src/utils.rs b/crates/theme2/src/utils.rs new file mode 100644 index 0000000000..ccdcde4274 --- /dev/null +++ b/crates/theme2/src/utils.rs @@ -0,0 +1,43 @@ +/// This macro generates a struct and a corresponding struct with optional fields. +/// +/// It takes as input the name of the struct to be generated, the name of the struct with optional fields, +/// and a list of field names along with their types. +/// +/// # Example +/// ``` +/// generate_struct_with_overrides!( +/// MyStruct, +/// MyStructOverride, +/// field1: i32, +/// field2: String +/// ); +/// ``` +/// This will generate the following structs: +/// ``` +/// pub struct MyStruct { +/// pub field1: i32, +/// pub field2: String, +/// } +/// +/// pub struct MyStructOverride { +/// pub field1: Option, +/// pub field2: Option, +/// } +/// ``` +#[macro_export] +macro_rules! generate_struct_with_overrides { + ($struct_name:ident, $struct_override_name:ident, $($field:ident: $type:ty),*) => { + pub struct $struct_name { + $( + pub $field: $type, + )* + } + + #[allow(dead_code)] + pub struct $struct_override_name { + $( + pub $field: Option<$type>, + )* + } + }; +} diff --git a/crates/ui2/src/components/breadcrumb.rs b/crates/ui2/src/components/breadcrumb.rs index 6b2dfe1cce..163dfabfb0 100644 --- a/crates/ui2/src/components/breadcrumb.rs +++ b/crates/ui2/src/components/breadcrumb.rs @@ -19,24 +19,22 @@ impl Breadcrumb { } fn render_separator(&self, cx: &WindowContext) -> Div { - let theme = theme(cx); - - div().child(" › ").text_color(theme.text_muted) + div() + .child(" › ") + .text_color(cx.theme().colors().text_muted) } fn render(self, view_state: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let symbols_len = self.symbols.len(); h_stack() .id("breadcrumb") .px_1() .text_sm() - .text_color(theme.text_muted) + .text_color(cx.theme().colors().text_muted) .rounded_md() - .hover(|style| style.bg(theme.ghost_element_hover)) - .active(|style| style.bg(theme.ghost_element_active)) + .hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .active(|style| style.bg(cx.theme().colors().ghost_element_active)) .child(self.path.clone().to_str().unwrap().to_string()) .child(if !self.symbols.is_empty() { self.render_separator(cx) @@ -84,8 +82,6 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, Breadcrumb>(cx)) .child(Story::label(cx, "Default")) @@ -95,21 +91,21 @@ mod stories { Symbol(vec![ HighlightedText { text: "impl ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "BreadcrumbStory".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), Symbol(vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "render".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), ], diff --git a/crates/ui2/src/components/buffer.rs b/crates/ui2/src/components/buffer.rs index 33a98b6ea9..2b3db676ce 100644 --- a/crates/ui2/src/components/buffer.rs +++ b/crates/ui2/src/components/buffer.rs @@ -155,18 +155,16 @@ impl Buffer { } fn render_row(row: BufferRow, cx: &WindowContext) -> impl Component { - let theme = theme(cx); - let line_background = if row.current { - theme.editor_active_line + cx.theme().colors().editor_active_line } else { - theme.transparent + cx.theme().styles.system.transparent }; let line_number_color = if row.current { - theme.text + cx.theme().colors().text } else { - theme.syntax.get("comment").color.unwrap_or_default() + cx.theme().syntax_color("comment") }; h_stack() @@ -216,14 +214,13 @@ impl Buffer { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let rows = self.render_rows(cx); v_stack() .flex_1() .w_full() .h_full() - .bg(theme.editor) + .bg(cx.theme().colors().editor) .children(rows) } } @@ -246,8 +243,6 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, Buffer>(cx)) .child(Story::label(cx, "Default")) @@ -257,14 +252,14 @@ mod stories { div() .w(rems(64.)) .h_96() - .child(hello_world_rust_buffer_example(&theme)), + .child(hello_world_rust_buffer_example(cx)), ) .child(Story::label(cx, "Hello World (Rust) with Status")) .child( div() .w(rems(64.)) .h_96() - .child(hello_world_rust_buffer_with_status_example(&theme)), + .child(hello_world_rust_buffer_with_status_example(cx)), ) } } diff --git a/crates/ui2/src/components/buffer_search.rs b/crates/ui2/src/components/buffer_search.rs index c5539f0a4a..5d7de1b408 100644 --- a/crates/ui2/src/components/buffer_search.rs +++ b/crates/ui2/src/components/buffer_search.rs @@ -30,9 +30,7 @@ impl Render for BufferSearch { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - let theme = theme(cx); - - h_stack().bg(theme.toolbar).p_2().child( + h_stack().bg(cx.theme().colors().toolbar).p_2().child( h_stack().child(Input::new("Search")).child( IconButton::::new("replace", Icon::Replace) .when(self.is_replace_open, |this| this.color(IconColor::Accent)) diff --git a/crates/ui2/src/components/collab_panel.rs b/crates/ui2/src/components/collab_panel.rs index a8552c0f23..a0e3b55f63 100644 --- a/crates/ui2/src/components/collab_panel.rs +++ b/crates/ui2/src/components/collab_panel.rs @@ -15,27 +15,29 @@ impl CollabPanel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .id(self.id.clone()) .h_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( v_stack() .id("crdb") .w_full() .overflow_y_scroll() .child( - div().pb_1().border_color(theme.border).border_b().child( - List::new(static_collab_panel_current_call()) - .header( - ListHeader::new("CRDB") - .left_icon(Icon::Hash.into()) - .toggle(ToggleState::Toggled), - ) - .toggle(ToggleState::Toggled), - ), + div() + .pb_1() + .border_color(cx.theme().colors().border) + .border_b() + .child( + List::new(static_collab_panel_current_call()) + .header( + ListHeader::new("CRDB") + .left_icon(Icon::Hash.into()) + .toggle(ToggleState::Toggled), + ) + .toggle(ToggleState::Toggled), + ), ) .child( v_stack().id("channels").py_1().child( @@ -71,13 +73,13 @@ impl CollabPanel { .h_7() .px_2() .border_t() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .flex() .items_center() .child( div() .text_sm() - .text_color(theme.text_placeholder) + .text_color(cx.theme().colors().text_placeholder) .child("Find..."), ), ) diff --git a/crates/ui2/src/components/context_menu.rs b/crates/ui2/src/components/context_menu.rs index 812221036a..8345be1b35 100644 --- a/crates/ui2/src/components/context_menu.rs +++ b/crates/ui2/src/components/context_menu.rs @@ -44,13 +44,11 @@ impl ContextMenu { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .flex() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .border() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .child( List::new( self.items diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index 980a1c98aa..06e242b1ef 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -66,8 +66,6 @@ impl IconButton { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let icon_color = match (self.state, self.color) { (InteractionState::Disabled, _) => IconColor::Disabled, _ => self.color, @@ -75,14 +73,14 @@ impl IconButton { let (bg_color, bg_hover_color, bg_active_color) = match self.variant { ButtonVariant::Filled => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), ButtonVariant::Ghost => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), }; diff --git a/crates/ui2/src/components/keybinding.rs b/crates/ui2/src/components/keybinding.rs index 455cfe5b59..88cabbdc88 100644 --- a/crates/ui2/src/components/keybinding.rs +++ b/crates/ui2/src/components/keybinding.rs @@ -60,15 +60,13 @@ impl Key { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() .px_2() .py_0() .rounded_md() .text_sm() - .text_color(theme.text) - .bg(theme.filled_element) + .text_color(cx.theme().colors().text) + .bg(cx.theme().colors().element) .child(self.key.clone()) } } diff --git a/crates/ui2/src/components/list.rs b/crates/ui2/src/components/list.rs index 9557e68d7f..1668592a38 100644 --- a/crates/ui2/src/components/list.rs +++ b/crates/ui2/src/components/list.rs @@ -89,8 +89,6 @@ impl ListHeader { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let is_toggleable = self.toggleable != Toggleable::NotToggleable; let is_toggled = self.toggleable.is_toggled(); @@ -99,9 +97,10 @@ impl ListHeader { h_stack() .flex_1() .w_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .when(self.state == InteractionState::Focused, |this| { - this.border().border_color(theme.border_focused) + this.border() + .border_color(cx.theme().colors().border_focused) }) .relative() .child( @@ -363,7 +362,6 @@ impl ListEntry { fn render(mut self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let settings = user_settings(cx); - let theme = theme(cx); let left_content = match self.left_content.clone() { Some(LeftContent::Icon(i)) => Some( @@ -385,9 +383,10 @@ impl ListEntry { div() .relative() .group("") - .bg(theme.surface) + .bg(cx.theme().colors().surface) .when(self.state == InteractionState::Focused, |this| { - this.border().border_color(theme.border_focused) + this.border() + .border_color(cx.theme().colors().border_focused) }) .child( sized_item @@ -399,11 +398,11 @@ impl ListEntry { .h_full() .flex() .justify_center() - .group_hover("", |style| style.bg(theme.border_focused)) + .group_hover("", |style| style.bg(cx.theme().colors().border_focused)) .child( h_stack() .child(div().w_px().h_full()) - .child(div().w_px().h_full().bg(theme.border)), + .child(div().w_px().h_full().bg(cx.theme().colors().border)), ) })) .flex() @@ -472,19 +471,18 @@ impl ListDetailsEntry { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let settings = user_settings(cx); let (item_bg, item_bg_hover, item_bg_active) = match self.seen { true => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), false => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), }; @@ -524,9 +522,7 @@ impl ListSeparator { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - - div().h_px().w_full().bg(theme.border) + div().h_px().w_full().bg(cx.theme().colors().border) } } diff --git a/crates/ui2/src/components/modal.rs b/crates/ui2/src/components/modal.rs index 7c3efe79ba..26986474e0 100644 --- a/crates/ui2/src/components/modal.rs +++ b/crates/ui2/src/components/modal.rs @@ -39,22 +39,20 @@ impl Modal { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .id(self.id.clone()) .w_96() // .rounded_xl() - .bg(theme.background) + .bg(cx.theme().colors().background) .border() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .shadow_2xl() .child( h_stack() .justify_between() .p_1() .border_b() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .child(div().children(self.title.clone().map(|t| Label::new(t)))) .child(IconButton::new("close", Icon::Close)), ) @@ -65,7 +63,7 @@ impl Modal { this.child( h_stack() .border_t() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .p_1() .justify_end() .children(self.secondary_action) diff --git a/crates/ui2/src/components/multi_buffer.rs b/crates/ui2/src/components/multi_buffer.rs index 696fc77a62..ea130f20bd 100644 --- a/crates/ui2/src/components/multi_buffer.rs +++ b/crates/ui2/src/components/multi_buffer.rs @@ -12,8 +12,6 @@ impl MultiBuffer { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .w_full() .h_full() @@ -26,7 +24,7 @@ impl MultiBuffer { .items_center() .justify_between() .p_4() - .bg(theme.editor_subheader) + .bg(cx.theme().colors().editor_subheader) .child(Label::new("main.rs")) .child(IconButton::new("arrow_up_right", Icon::ArrowUpRight)), ) @@ -50,17 +48,15 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, MultiBuffer>(cx)) .child(Story::label(cx, "Default")) .child(MultiBuffer::new(vec![ - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), - hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), + hello_world_rust_buffer_example(cx), ])) } } diff --git a/crates/ui2/src/components/notification_toast.rs b/crates/ui2/src/components/notification_toast.rs index f7d280ed16..59078c98f4 100644 --- a/crates/ui2/src/components/notification_toast.rs +++ b/crates/ui2/src/components/notification_toast.rs @@ -1,6 +1,7 @@ use gpui2::rems; -use crate::{h_stack, prelude::*, Icon}; +use crate::prelude::*; +use crate::{h_stack, Icon}; #[derive(Component)] pub struct NotificationToast { @@ -22,8 +23,6 @@ impl NotificationToast { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - h_stack() .z_index(5) .absolute() @@ -35,7 +34,7 @@ impl NotificationToast { .px_1p5() .rounded_lg() .shadow_md() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .child(div().size_full().child(self.label.clone())) } } diff --git a/crates/ui2/src/components/notifications_panel.rs b/crates/ui2/src/components/notifications_panel.rs index 6872f116e9..10b0e07af6 100644 --- a/crates/ui2/src/components/notifications_panel.rs +++ b/crates/ui2/src/components/notifications_panel.rs @@ -12,15 +12,13 @@ impl NotificationsPanel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() .id(self.id.clone()) .flex() .flex_col() .w_full() .h_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( div() .id("header") diff --git a/crates/ui2/src/components/palette.rs b/crates/ui2/src/components/palette.rs index e47f6a4cea..a1f3eb7e1c 100644 --- a/crates/ui2/src/components/palette.rs +++ b/crates/ui2/src/components/palette.rs @@ -43,22 +43,20 @@ impl Palette { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .id(self.id.clone()) .w_96() .rounded_lg() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .border() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .child( v_stack() .gap_px() .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child( Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder), ))) - .child(div().h_px().w_full().bg(theme.filled_element)) + .child(div().h_px().w_full().bg(cx.theme().colors().element)) .child( v_stack() .id("items") @@ -88,8 +86,12 @@ impl Palette { .px_2() .py_0p5() .rounded_lg() - .hover(|style| style.bg(theme.ghost_element_hover)) - .active(|style| style.bg(theme.ghost_element_active)) + .hover(|style| { + style.bg(cx.theme().colors().ghost_element_hover) + }) + .active(|style| { + style.bg(cx.theme().colors().ghost_element_active) + }) .child(item) })), ), diff --git a/crates/ui2/src/components/panel.rs b/crates/ui2/src/components/panel.rs index 12d2207ffd..c4a9ac5111 100644 --- a/crates/ui2/src/components/panel.rs +++ b/crates/ui2/src/components/panel.rs @@ -93,8 +93,6 @@ impl Panel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let current_size = self.width.unwrap_or(self.initial_width); v_stack() @@ -111,8 +109,8 @@ impl Panel { .when(self.current_side == PanelSide::Bottom, |this| { this.border_b().w_full().h(current_size) }) - .bg(theme.surface) - .border_color(theme.border) + .bg(cx.theme().colors().surface) + .border_color(cx.theme().colors().border) .children(self.children) } } diff --git a/crates/ui2/src/components/panes.rs b/crates/ui2/src/components/panes.rs index 854786ebaa..167e837083 100644 --- a/crates/ui2/src/components/panes.rs +++ b/crates/ui2/src/components/panes.rs @@ -90,8 +90,6 @@ impl PaneGroup { } fn render(self, view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - if !self.panes.is_empty() { let el = div() .flex() @@ -115,7 +113,7 @@ impl PaneGroup { .gap_px() .w_full() .h_full() - .bg(theme.editor) + .bg(cx.theme().colors().editor) .children(self.groups.into_iter().map(|group| group.render(view, cx))); if self.split_direction == SplitDirection::Horizontal { diff --git a/crates/ui2/src/components/player_stack.rs b/crates/ui2/src/components/player_stack.rs index ced761a086..1a1231e6c4 100644 --- a/crates/ui2/src/components/player_stack.rs +++ b/crates/ui2/src/components/player_stack.rs @@ -14,9 +14,7 @@ impl PlayerStack { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let player = self.player_with_call_status.get_player(); - self.player_with_call_status.get_call_status(); let followers = self .player_with_call_status @@ -50,7 +48,7 @@ impl PlayerStack { .pl_1() .rounded_lg() .bg(if followers.is_none() { - theme.transparent + cx.theme().styles.system.transparent } else { player.selection_color(cx) }) diff --git a/crates/ui2/src/components/project_panel.rs b/crates/ui2/src/components/project_panel.rs index 84c68119fe..76fa50d338 100644 --- a/crates/ui2/src/components/project_panel.rs +++ b/crates/ui2/src/components/project_panel.rs @@ -14,15 +14,13 @@ impl ProjectPanel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() .id(self.id.clone()) .flex() .flex_col() .w_full() .h_full() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( div() .id("project-panel-contents") diff --git a/crates/ui2/src/components/status_bar.rs b/crates/ui2/src/components/status_bar.rs index a23040193f..136472f605 100644 --- a/crates/ui2/src/components/status_bar.rs +++ b/crates/ui2/src/components/status_bar.rs @@ -86,8 +86,6 @@ impl StatusBar { view: &mut Workspace, cx: &mut ViewContext, ) -> impl Component { - let theme = theme(cx); - div() .py_0p5() .px_1() @@ -95,7 +93,7 @@ impl StatusBar { .items_center() .justify_between() .w_full() - .bg(theme.status_bar) + .bg(cx.theme().colors().status_bar) .child(self.left_tools(view, cx)) .child(self.right_tools(view, cx)) } diff --git a/crates/ui2/src/components/tab.rs b/crates/ui2/src/components/tab.rs index d784ec0174..5f20af0955 100644 --- a/crates/ui2/src/components/tab.rs +++ b/crates/ui2/src/components/tab.rs @@ -87,7 +87,6 @@ impl Tab { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict; let is_deleted = self.fs_status == FileSystemStatus::Deleted; @@ -110,14 +109,14 @@ impl Tab { let (tab_bg, tab_hover_bg, tab_active_bg) = match self.current { true => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), false => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), }; diff --git a/crates/ui2/src/components/tab_bar.rs b/crates/ui2/src/components/tab_bar.rs index da0a41a1bf..550105b98e 100644 --- a/crates/ui2/src/components/tab_bar.rs +++ b/crates/ui2/src/components/tab_bar.rs @@ -24,15 +24,13 @@ impl TabBar { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let (can_navigate_back, can_navigate_forward) = self.can_navigate; div() .id(self.id.clone()) .w_full() .flex() - .bg(theme.tab_bar) + .bg(cx.theme().colors().tab_bar) // Left Side .child( div() diff --git a/crates/ui2/src/components/terminal.rs b/crates/ui2/src/components/terminal.rs index a751d47dfc..051ebf7315 100644 --- a/crates/ui2/src/components/terminal.rs +++ b/crates/ui2/src/components/terminal.rs @@ -12,8 +12,6 @@ impl Terminal { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let can_navigate_back = true; let can_navigate_forward = false; @@ -26,7 +24,7 @@ impl Terminal { div() .w_full() .flex() - .bg(theme.surface) + .bg(cx.theme().colors().surface) .child( div().px_1().flex().flex_none().gap_2().child( div() @@ -73,7 +71,7 @@ impl Terminal { height: rems(36.).into(), }, ) - .child(crate::static_data::terminal_buffer(&theme)), + .child(crate::static_data::terminal_buffer(cx)), ) } } diff --git a/crates/ui2/src/components/title_bar.rs b/crates/ui2/src/components/title_bar.rs index 4b3b125dea..2fa201440a 100644 --- a/crates/ui2/src/components/title_bar.rs +++ b/crates/ui2/src/components/title_bar.rs @@ -89,7 +89,6 @@ impl Render for TitleBar { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - let theme = theme(cx); let settings = user_settings(cx); // let has_focus = cx.window_is_active(); @@ -106,7 +105,7 @@ impl Render for TitleBar { .items_center() .justify_between() .w_full() - .bg(theme.background) + .bg(cx.theme().colors().background) .py_1() .child( div() diff --git a/crates/ui2/src/components/toast.rs b/crates/ui2/src/components/toast.rs index 814e91c498..3b81ac42b4 100644 --- a/crates/ui2/src/components/toast.rs +++ b/crates/ui2/src/components/toast.rs @@ -37,8 +37,6 @@ impl Toast { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let mut div = div(); if self.origin == ToastOrigin::Bottom { @@ -56,7 +54,7 @@ impl Toast { .rounded_lg() .shadow_md() .overflow_hidden() - .bg(theme.elevated_surface) + .bg(cx.theme().colors().elevated_surface) .children(self.children) } } diff --git a/crates/ui2/src/components/toolbar.rs b/crates/ui2/src/components/toolbar.rs index 4b35e2d9d2..05a5c991d6 100644 --- a/crates/ui2/src/components/toolbar.rs +++ b/crates/ui2/src/components/toolbar.rs @@ -55,10 +55,8 @@ impl Toolbar { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - div() - .bg(theme.toolbar) + .bg(cx.theme().colors().toolbar) .p_2() .flex() .justify_between() @@ -87,8 +85,6 @@ mod stories { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Self::Element { - let theme = theme(cx); - Story::container(cx) .child(Story::title_for::<_, Toolbar>(cx)) .child(Story::label(cx, "Default")) @@ -100,21 +96,21 @@ mod stories { Symbol(vec![ HighlightedText { text: "impl ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "ToolbarStory".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), Symbol(vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "render".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ]), ], diff --git a/crates/ui2/src/components/traffic_lights.rs b/crates/ui2/src/components/traffic_lights.rs index 8ee19d26f5..9080276cdd 100644 --- a/crates/ui2/src/components/traffic_lights.rs +++ b/crates/ui2/src/components/traffic_lights.rs @@ -22,13 +22,13 @@ impl TrafficLight { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); + let system_colors = &cx.theme().styles.system; let fill = match (self.window_has_focus, self.color) { - (true, TrafficLightColor::Red) => theme.mac_os_traffic_light_red, - (true, TrafficLightColor::Yellow) => theme.mac_os_traffic_light_yellow, - (true, TrafficLightColor::Green) => theme.mac_os_traffic_light_green, - (false, _) => theme.filled_element, + (true, TrafficLightColor::Red) => system_colors.mac_os_traffic_light_red, + (true, TrafficLightColor::Yellow) => system_colors.mac_os_traffic_light_yellow, + (true, TrafficLightColor::Green) => system_colors.mac_os_traffic_light_green, + (false, _) => cx.theme().colors().element, }; div().w_3().h_3().rounded_full().bg(fill) diff --git a/crates/ui2/src/components/workspace.rs b/crates/ui2/src/components/workspace.rs index 78ab6232a8..0e31c6b9ad 100644 --- a/crates/ui2/src/components/workspace.rs +++ b/crates/ui2/src/components/workspace.rs @@ -179,8 +179,6 @@ impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - let theme = theme(cx); - // HACK: This should happen inside of `debug_toggle_user_settings`, but // we don't have `cx.global::()` in event handlers at the moment. // Need to talk with Nathan/Antonio about this. @@ -216,8 +214,8 @@ impl Render for Workspace { .gap_0() .justify_start() .items_start() - .text_color(theme.text) - .bg(theme.background) + .text_color(cx.theme().colors().text) + .bg(cx.theme().colors().background) .child(self.title_bar.clone()) .child( div() @@ -228,7 +226,7 @@ impl Render for Workspace { .overflow_hidden() .border_t() .border_b() - .border_color(theme.border) + .border_color(cx.theme().colors().border) .children( Some( Panel::new("project-panel-outer", cx) diff --git a/crates/ui2/src/elements/avatar.rs b/crates/ui2/src/elements/avatar.rs index f008eeb479..ff92021b11 100644 --- a/crates/ui2/src/elements/avatar.rs +++ b/crates/ui2/src/elements/avatar.rs @@ -22,8 +22,6 @@ impl Avatar { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let mut img = img(); if self.shape == Shape::Circle { @@ -34,7 +32,8 @@ impl Avatar { img.uri(self.src.clone()) .size_4() - .bg(theme.image_fallback_background) + // todo!(Pull the avatar fallback background from the theme.) + .bg(gpui2::red()) } } diff --git a/crates/ui2/src/elements/button.rs b/crates/ui2/src/elements/button.rs index d27a0537d8..e63269197c 100644 --- a/crates/ui2/src/elements/button.rs +++ b/crates/ui2/src/elements/button.rs @@ -21,29 +21,23 @@ pub enum ButtonVariant { impl ButtonVariant { pub fn bg_color(&self, cx: &mut WindowContext) -> Hsla { - let theme = theme(cx); - match self { - ButtonVariant::Ghost => theme.ghost_element, - ButtonVariant::Filled => theme.filled_element, + ButtonVariant::Ghost => cx.theme().colors().ghost_element, + ButtonVariant::Filled => cx.theme().colors().element, } } pub fn bg_color_hover(&self, cx: &mut WindowContext) -> Hsla { - let theme = theme(cx); - match self { - ButtonVariant::Ghost => theme.ghost_element_hover, - ButtonVariant::Filled => theme.filled_element_hover, + ButtonVariant::Ghost => cx.theme().colors().ghost_element_hover, + ButtonVariant::Filled => cx.theme().colors().element_hover, } } pub fn bg_color_active(&self, cx: &mut WindowContext) -> Hsla { - let theme = theme(cx); - match self { - ButtonVariant::Ghost => theme.ghost_element_active, - ButtonVariant::Filled => theme.filled_element_active, + ButtonVariant::Ghost => cx.theme().colors().ghost_element_active, + ButtonVariant::Filled => cx.theme().colors().element_active, } } } diff --git a/crates/ui2/src/elements/details.rs b/crates/ui2/src/elements/details.rs index eca7798c82..1d22c81774 100644 --- a/crates/ui2/src/elements/details.rs +++ b/crates/ui2/src/elements/details.rs @@ -1,4 +1,5 @@ -use crate::{prelude::*, v_stack, ButtonGroup}; +use crate::prelude::*; +use crate::{v_stack, ButtonGroup}; #[derive(Component)] pub struct Details { @@ -27,13 +28,11 @@ impl Details { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - v_stack() .p_1() .gap_0p5() .text_xs() - .text_color(theme.text) + .text_color(cx.theme().colors().text) .size_full() .child(self.text) .children(self.meta.map(|m| m)) diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index 4e4ec2bce7..6c1b3a4f08 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -26,13 +26,14 @@ pub enum IconColor { impl IconColor { pub fn color(self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); + let theme_colors = cx.theme().colors(); + match self { - IconColor::Default => gpui2::red(), - IconColor::Muted => gpui2::red(), - IconColor::Disabled => gpui2::red(), - IconColor::Placeholder => gpui2::red(), - IconColor::Accent => gpui2::red(), + IconColor::Default => theme_colors.icon, + IconColor::Muted => theme_colors.icon_muted, + IconColor::Disabled => theme_colors.icon_disabled, + IconColor::Placeholder => theme_colors.icon_placeholder, + IconColor::Accent => theme_colors.icon_accent, IconColor::Error => gpui2::red(), IconColor::Warning => gpui2::red(), IconColor::Success => gpui2::red(), diff --git a/crates/ui2/src/elements/input.rs b/crates/ui2/src/elements/input.rs index e9e92dd0a6..3f82512b84 100644 --- a/crates/ui2/src/elements/input.rs +++ b/crates/ui2/src/elements/input.rs @@ -57,18 +57,16 @@ impl Input { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - let (input_bg, input_hover_bg, input_active_bg) = match self.variant { InputVariant::Ghost => ( - theme.ghost_element, - theme.ghost_element_hover, - theme.ghost_element_active, + cx.theme().colors().ghost_element, + cx.theme().colors().ghost_element_hover, + cx.theme().colors().ghost_element_active, ), InputVariant::Filled => ( - theme.filled_element, - theme.filled_element_hover, - theme.filled_element_active, + cx.theme().colors().element, + cx.theme().colors().element_hover, + cx.theme().colors().element_active, ), }; @@ -90,7 +88,7 @@ impl Input { .w_full() .px_2() .border() - .border_color(theme.transparent) + .border_color(cx.theme().styles.system.transparent) .bg(input_bg) .hover(|style| style.bg(input_hover_bg)) .active(|style| style.bg(input_active_bg)) diff --git a/crates/ui2/src/elements/label.rs b/crates/ui2/src/elements/label.rs index 4d336345fb..ee8ac9a636 100644 --- a/crates/ui2/src/elements/label.rs +++ b/crates/ui2/src/elements/label.rs @@ -18,18 +18,16 @@ pub enum LabelColor { impl LabelColor { pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - match self { - Self::Default => theme.text, - Self::Muted => theme.text_muted, + Self::Default => cx.theme().colors().text, + Self::Muted => cx.theme().colors().text_muted, Self::Created => gpui2::red(), Self::Modified => gpui2::red(), Self::Deleted => gpui2::red(), - Self::Disabled => theme.text_disabled, + Self::Disabled => cx.theme().colors().text_disabled, Self::Hidden => gpui2::red(), - Self::Placeholder => theme.text_placeholder, - Self::Accent => gpui2::red(), + Self::Placeholder => cx.theme().colors().text_placeholder, + Self::Accent => cx.theme().colors().text_accent, } } } @@ -126,9 +124,7 @@ impl HighlightedLabel { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - - let highlight_color = theme.text_accent; + let highlight_color = cx.theme().colors().text_accent; let mut highlight_indices = self.highlight_indices.iter().copied().peekable(); diff --git a/crates/ui2/src/elements/player.rs b/crates/ui2/src/elements/player.rs index 5bf890b8bb..8e3ad5c3a8 100644 --- a/crates/ui2/src/elements/player.rs +++ b/crates/ui2/src/elements/player.rs @@ -1,6 +1,6 @@ use gpui2::{Hsla, ViewContext}; -use crate::theme; +use crate::prelude::*; #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub enum PlayerStatus { @@ -139,13 +139,11 @@ impl Player { } pub fn cursor_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - theme.players[self.index].cursor + cx.theme().styles.player.0[self.index].cursor } pub fn selection_color(&self, cx: &mut ViewContext) -> Hsla { - let theme = theme(cx); - theme.players[self.index].selection + cx.theme().styles.player.0[self.index].selection } pub fn avatar_src(&self) -> &str { diff --git a/crates/ui2/src/elements/tool_divider.rs b/crates/ui2/src/elements/tool_divider.rs index e1ebb294a0..8a9bbad97f 100644 --- a/crates/ui2/src/elements/tool_divider.rs +++ b/crates/ui2/src/elements/tool_divider.rs @@ -9,8 +9,6 @@ impl ToolDivider { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let theme = theme(cx); - - div().w_px().h_3().bg(theme.border) + div().w_px().h_3().bg(cx.theme().colors().border) } } diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index 63405fc2cb..b424ce6123 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -6,7 +6,7 @@ pub use gpui2::{ pub use crate::elevation::*; use crate::settings::user_settings; pub use crate::ButtonVariant; -pub use theme2::theme; +pub use theme2::ActiveTheme; use gpui2::{rems, Hsla, Rems}; use strum::EnumIter; @@ -54,15 +54,13 @@ pub enum GitStatus { impl GitStatus { pub fn hsla(&self, cx: &WindowContext) -> Hsla { - let theme = theme(cx); - match self { - Self::None => theme.transparent, - Self::Created => theme.git_created, - Self::Modified => theme.git_modified, - Self::Deleted => theme.git_deleted, - Self::Conflict => theme.git_conflict, - Self::Renamed => theme.git_renamed, + Self::None => cx.theme().styles.system.transparent, + Self::Created => cx.theme().styles.git.created, + Self::Modified => cx.theme().styles.git.modified, + Self::Deleted => cx.theme().styles.git.deleted, + Self::Conflict => cx.theme().styles.git.conflict, + Self::Renamed => cx.theme().styles.git.renamed, } } } diff --git a/crates/ui2/src/static_data.rs b/crates/ui2/src/static_data.rs index 68f1e36b2c..7062c81954 100644 --- a/crates/ui2/src/static_data.rs +++ b/crates/ui2/src/static_data.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; use std::str::FromStr; -use gpui2::ViewContext; +use gpui2::{AppContext, ViewContext}; use rand::Rng; -use theme2::Theme; +use theme2::ActiveTheme; use crate::{ - theme, Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, + Buffer, BufferRow, BufferRows, Button, EditorPane, FileSystemStatus, GitStatus, HighlightedLine, Icon, Keybinding, Label, LabelColor, ListEntry, ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, ToggleState, VideoStatus, @@ -643,8 +643,6 @@ pub fn empty_buffer_example() -> Buffer { } pub fn hello_world_rust_editor_example(cx: &mut ViewContext) -> EditorPane { - let theme = theme(cx); - EditorPane::new( cx, static_tabs_example(), @@ -652,29 +650,29 @@ pub fn hello_world_rust_editor_example(cx: &mut ViewContext) -> Edit vec![Symbol(vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "main".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, ])], - hello_world_rust_buffer_example(&theme), + hello_world_rust_buffer_example(cx), ) } -pub fn hello_world_rust_buffer_example(theme: &Theme) -> Buffer { +pub fn hello_world_rust_buffer_example(cx: &AppContext) -> Buffer { Buffer::new("hello-world-rust-buffer") .set_title("hello_world.rs".to_string()) .set_path("src/hello_world.rs".to_string()) .set_language("rust".to_string()) .set_rows(Some(BufferRows { show_line_numbers: true, - rows: hello_world_rust_buffer_rows(theme), + rows: hello_world_rust_buffer_rows(cx), })) } -pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { +pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec { let show_line_number = true; vec![ @@ -686,15 +684,15 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "main".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "() {".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -710,7 +708,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![HighlightedText { text: " // Statements here are executed when the compiled binary is called." .to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -733,7 +731,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: " // Print text to the console.".to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -748,15 +746,15 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![ HighlightedText { text: " println!(".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "\"Hello, world!\"".to_string(), - color: theme.syntax.color("string"), + color: cx.theme().syntax_color("string"), }, HighlightedText { text: ");".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -771,7 +769,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "}".to_string(), - color: theme.text, + color: cx.theme().colors().text, }], }), cursors: None, @@ -782,8 +780,6 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec { } pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext) -> EditorPane { - let theme = theme(cx); - EditorPane::new( cx, static_tabs_example(), @@ -791,29 +787,29 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext Buffer { +pub fn hello_world_rust_buffer_with_status_example(cx: &AppContext) -> Buffer { Buffer::new("hello-world-rust-buffer-with-status") .set_title("hello_world.rs".to_string()) .set_path("src/hello_world.rs".to_string()) .set_language("rust".to_string()) .set_rows(Some(BufferRows { show_line_numbers: true, - rows: hello_world_rust_with_status_buffer_rows(theme), + rows: hello_world_rust_with_status_buffer_rows(cx), })) } -pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec { +pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec { let show_line_number = true; vec![ @@ -825,15 +821,15 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec highlighted_texts: vec![ HighlightedText { text: "fn ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "main".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "() {".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -849,7 +845,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec highlighted_texts: vec![HighlightedText { text: "// Statements here are executed when the compiled binary is called." .to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -872,7 +868,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: " // Print text to the console.".to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -887,15 +883,15 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec highlighted_texts: vec![ HighlightedText { text: " println!(".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "\"Hello, world!\"".to_string(), - color: theme.syntax.color("string"), + color: cx.theme().syntax_color("string"), }, HighlightedText { text: ");".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, ], }), @@ -910,7 +906,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "}".to_string(), - color: theme.text, + color: cx.theme().colors().text, }], }), cursors: None, @@ -924,7 +920,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "".to_string(), - color: theme.text, + color: cx.theme().colors().text, }], }), cursors: None, @@ -938,7 +934,7 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "// Marshall and Nate were here".to_string(), - color: theme.syntax.color("comment"), + color: cx.theme().syntax_color("comment"), }], }), cursors: None, @@ -948,16 +944,16 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec ] } -pub fn terminal_buffer(theme: &Theme) -> Buffer { +pub fn terminal_buffer(cx: &AppContext) -> Buffer { Buffer::new("terminal") .set_title("zed — fish".to_string()) .set_rows(Some(BufferRows { show_line_numbers: false, - rows: terminal_buffer_rows(theme), + rows: terminal_buffer_rows(cx), })) } -pub fn terminal_buffer_rows(theme: &Theme) -> Vec { +pub fn terminal_buffer_rows(cx: &AppContext) -> Vec { let show_line_number = false; vec![ @@ -969,31 +965,31 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec { highlighted_texts: vec![ HighlightedText { text: "maxdeviant ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, HighlightedText { text: "in ".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "profaned-capital ".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "in ".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: "~/p/zed ".to_string(), - color: theme.syntax.color("function"), + color: cx.theme().syntax_color("function"), }, HighlightedText { text: "on ".to_string(), - color: theme.text, + color: cx.theme().colors().text, }, HighlightedText { text: " gpui2-ui ".to_string(), - color: theme.syntax.color("keyword"), + color: cx.theme().syntax_color("keyword"), }, ], }), @@ -1008,7 +1004,7 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec { line: Some(HighlightedLine { highlighted_texts: vec![HighlightedText { text: "λ ".to_string(), - color: theme.syntax.color("string"), + color: cx.theme().syntax_color("string"), }], }), cursors: None, diff --git a/crates/ui2/src/story.rs b/crates/ui2/src/story.rs index d2813bd174..dea4e342b4 100644 --- a/crates/ui2/src/story.rs +++ b/crates/ui2/src/story.rs @@ -6,8 +6,6 @@ pub struct Story {} impl Story { pub fn container(cx: &mut ViewContext) -> Div { - let theme = theme(cx); - div() .size_full() .flex() @@ -15,15 +13,13 @@ impl Story { .pt_2() .px_4() .font("Zed Mono") - .bg(theme.background) + .bg(cx.theme().colors().background) } pub fn title(cx: &mut ViewContext, title: &str) -> impl Component { - let theme = theme(cx); - div() .text_xl() - .text_color(theme.text) + .text_color(cx.theme().colors().text) .child(title.to_owned()) } @@ -32,13 +28,11 @@ impl Story { } pub fn label(cx: &mut ViewContext, label: &str) -> impl Component { - let theme = theme(cx); - div() .mt_4() .mb_2() .text_xs() - .text_color(theme.text) + .text_color(cx.theme().colors().text) .child(label.to_owned()) } } From 36a73d657a6fe1d53e5d0f81f60bfa83d00b77ff Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 04:05:50 +0100 Subject: [PATCH 05/15] Remove old `Theme` definition (#3195) This PR removes the old `Theme` definition in favor of the new `ThemeVariant`s. The new `SyntaxStyles` have been reverted to the old `SyntaxTheme` that operates by storing the syntax styles as a vector of `gpui2::HighlightStyle`s. This is necessary for the intended usage by `language2`, where we find the longest key in the theme's syntax styles that matches the capture name: https://github.com/zed-industries/zed/blob/18431051d9d750d9e66284a71f7a55a1e31c1374/crates/language2/src/highlight_map.rs#L15-L41 --- Cargo.lock | 15 - Cargo.toml | 1 - crates/language2/src/language2.rs | 10 +- crates/storybook2/src/storybook2.rs | 4 +- crates/theme2/src/colors.rs | 4 +- crates/theme2/src/default_colors.rs | 642 +++--------------- crates/theme2/src/default_theme.rs | 16 +- crates/theme2/src/registry.rs | 63 +- crates/theme2/src/settings.rs | 10 +- crates/theme2/src/syntax.rs | 238 +------ crates/theme2/src/theme2.rs | 137 +--- crates/theme2/src/themes/andromeda.rs | 130 ---- crates/theme2/src/themes/atelier_cave_dark.rs | 136 ---- .../theme2/src/themes/atelier_cave_light.rs | 136 ---- crates/theme2/src/themes/atelier_dune_dark.rs | 136 ---- .../theme2/src/themes/atelier_dune_light.rs | 136 ---- .../theme2/src/themes/atelier_estuary_dark.rs | 136 ---- .../src/themes/atelier_estuary_light.rs | 136 ---- .../theme2/src/themes/atelier_forest_dark.rs | 136 ---- .../theme2/src/themes/atelier_forest_light.rs | 136 ---- .../theme2/src/themes/atelier_heath_dark.rs | 136 ---- .../theme2/src/themes/atelier_heath_light.rs | 136 ---- .../src/themes/atelier_lakeside_dark.rs | 136 ---- .../src/themes/atelier_lakeside_light.rs | 136 ---- .../theme2/src/themes/atelier_plateau_dark.rs | 136 ---- .../src/themes/atelier_plateau_light.rs | 136 ---- .../theme2/src/themes/atelier_savanna_dark.rs | 136 ---- .../src/themes/atelier_savanna_light.rs | 136 ---- .../theme2/src/themes/atelier_seaside_dark.rs | 136 ---- .../src/themes/atelier_seaside_light.rs | 136 ---- .../src/themes/atelier_sulphurpool_dark.rs | 136 ---- .../src/themes/atelier_sulphurpool_light.rs | 136 ---- crates/theme2/src/themes/ayu_dark.rs | 130 ---- crates/theme2/src/themes/ayu_light.rs | 130 ---- crates/theme2/src/themes/ayu_mirage.rs | 130 ---- crates/theme2/src/themes/gruvbox_dark.rs | 131 ---- crates/theme2/src/themes/gruvbox_dark_hard.rs | 131 ---- crates/theme2/src/themes/gruvbox_dark_soft.rs | 131 ---- crates/theme2/src/themes/gruvbox_light.rs | 131 ---- .../theme2/src/themes/gruvbox_light_hard.rs | 131 ---- .../theme2/src/themes/gruvbox_light_soft.rs | 131 ---- crates/theme2/src/themes/mod.rs | 79 --- crates/theme2/src/themes/one_dark.rs | 131 ---- crates/theme2/src/themes/one_light.rs | 131 ---- crates/theme2/src/themes/rose_pine.rs | 132 ---- crates/theme2/src/themes/rose_pine_dawn.rs | 132 ---- crates/theme2/src/themes/rose_pine_moon.rs | 132 ---- crates/theme2/src/themes/sandcastle.rs | 130 ---- crates/theme2/src/themes/solarized_dark.rs | 130 ---- crates/theme2/src/themes/solarized_light.rs | 130 ---- crates/theme2/src/themes/summercamp.rs | 130 ---- crates/theme_converter/Cargo.toml | 18 - crates/theme_converter/src/main.rs | 390 ----------- crates/theme_converter/src/theme_printer.rs | 174 ----- 54 files changed, 173 insertions(+), 6832 deletions(-) delete mode 100644 crates/theme2/src/themes/andromeda.rs delete mode 100644 crates/theme2/src/themes/atelier_cave_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_cave_light.rs delete mode 100644 crates/theme2/src/themes/atelier_dune_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_dune_light.rs delete mode 100644 crates/theme2/src/themes/atelier_estuary_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_estuary_light.rs delete mode 100644 crates/theme2/src/themes/atelier_forest_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_forest_light.rs delete mode 100644 crates/theme2/src/themes/atelier_heath_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_heath_light.rs delete mode 100644 crates/theme2/src/themes/atelier_lakeside_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_lakeside_light.rs delete mode 100644 crates/theme2/src/themes/atelier_plateau_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_plateau_light.rs delete mode 100644 crates/theme2/src/themes/atelier_savanna_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_savanna_light.rs delete mode 100644 crates/theme2/src/themes/atelier_seaside_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_seaside_light.rs delete mode 100644 crates/theme2/src/themes/atelier_sulphurpool_dark.rs delete mode 100644 crates/theme2/src/themes/atelier_sulphurpool_light.rs delete mode 100644 crates/theme2/src/themes/ayu_dark.rs delete mode 100644 crates/theme2/src/themes/ayu_light.rs delete mode 100644 crates/theme2/src/themes/ayu_mirage.rs delete mode 100644 crates/theme2/src/themes/gruvbox_dark.rs delete mode 100644 crates/theme2/src/themes/gruvbox_dark_hard.rs delete mode 100644 crates/theme2/src/themes/gruvbox_dark_soft.rs delete mode 100644 crates/theme2/src/themes/gruvbox_light.rs delete mode 100644 crates/theme2/src/themes/gruvbox_light_hard.rs delete mode 100644 crates/theme2/src/themes/gruvbox_light_soft.rs delete mode 100644 crates/theme2/src/themes/mod.rs delete mode 100644 crates/theme2/src/themes/one_dark.rs delete mode 100644 crates/theme2/src/themes/one_light.rs delete mode 100644 crates/theme2/src/themes/rose_pine.rs delete mode 100644 crates/theme2/src/themes/rose_pine_dawn.rs delete mode 100644 crates/theme2/src/themes/rose_pine_moon.rs delete mode 100644 crates/theme2/src/themes/sandcastle.rs delete mode 100644 crates/theme2/src/themes/solarized_dark.rs delete mode 100644 crates/theme2/src/themes/solarized_light.rs delete mode 100644 crates/theme2/src/themes/summercamp.rs delete mode 100644 crates/theme_converter/Cargo.toml delete mode 100644 crates/theme_converter/src/main.rs delete mode 100644 crates/theme_converter/src/theme_printer.rs diff --git a/Cargo.lock b/Cargo.lock index 272320895d..5691729766 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8769,21 +8769,6 @@ dependencies = [ "util", ] -[[package]] -name = "theme_converter" -version = "0.1.0" -dependencies = [ - "anyhow", - "clap 4.4.4", - "convert_case 0.6.0", - "gpui2", - "log", - "rust-embed", - "serde", - "simplelog", - "theme2", -] - [[package]] name = "theme_selector" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ac490ce935..cb0e12cc40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,7 +92,6 @@ members = [ "crates/text", "crates/theme", "crates/theme2", - "crates/theme_converter", "crates/theme_selector", "crates/ui2", "crates/util", diff --git a/crates/language2/src/language2.rs b/crates/language2/src/language2.rs index 717a80619b..21746bf43c 100644 --- a/crates/language2/src/language2.rs +++ b/crates/language2/src/language2.rs @@ -42,7 +42,7 @@ use std::{ }, }; use syntax_map::SyntaxSnapshot; -use theme2::{SyntaxTheme, Theme}; +use theme2::{SyntaxTheme, ThemeVariant}; use tree_sitter::{self, Query}; use unicase::UniCase; use util::{http::HttpClient, paths::PathExt}; @@ -642,7 +642,7 @@ struct LanguageRegistryState { next_available_language_id: AvailableLanguageId, loading_languages: HashMap>>>>, subscription: (watch::Sender<()>, watch::Receiver<()>), - theme: Option>, + theme: Option>, version: usize, reload_count: usize, } @@ -743,11 +743,11 @@ impl LanguageRegistry { self.state.read().reload_count } - pub fn set_theme(&self, theme: Arc) { + pub fn set_theme(&self, theme: Arc) { let mut state = self.state.write(); state.theme = Some(theme.clone()); for language in &state.languages { - language.set_theme(&theme.syntax); + language.set_theme(&theme.syntax()); } } @@ -1048,7 +1048,7 @@ impl LanguageRegistryState { fn add(&mut self, language: Arc) { if let Some(theme) = self.theme.as_ref() { - language.set_theme(&theme.syntax); + language.set_theme(&theme.syntax()); } self.languages.push(language); self.version += 1; diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 6028695d7f..411fe18071 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -48,7 +48,7 @@ fn main() { let args = Args::parse(); let story_selector = args.story.clone(); - let theme_name = args.theme.unwrap_or("One Dark".to_string()); + let theme_name = args.theme.unwrap_or("Zed Pro Moonlight".to_string()); let asset_source = Arc::new(Assets); gpui2::App::production(asset_source).run(move |cx| { @@ -68,7 +68,7 @@ fn main() { let theme_registry = cx.global::(); let mut theme_settings = ThemeSettings::get_global(cx).clone(); - theme_settings.old_active_theme = theme_registry.get(&theme_name).unwrap(); + theme_settings.active_theme = theme_registry.get(&theme_name).unwrap(); ThemeSettings::override_global(theme_settings, cx); ui::settings::init(cx); diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index d23fde1ee0..2a59fa41bd 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -1,7 +1,7 @@ use gpui2::Hsla; use refineable::Refineable; -use crate::{generate_struct_with_overrides, SyntaxStyles}; +use crate::{generate_struct_with_overrides, SyntaxTheme}; pub struct SystemColors { pub transparent: Hsla, @@ -94,7 +94,7 @@ generate_struct_with_overrides! { status: StatusColors, git: GitStatusColors, player: PlayerColors, - syntax: SyntaxStyles + syntax: SyntaxTheme } #[cfg(test)] diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index e8146cdeaa..5ef93d036f 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,11 +1,9 @@ -use gpui2::{hsla, FontWeight, Rgba}; -use indexmap::IndexMap; +use gpui2::{hsla, Rgba}; use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, - syntax::{SyntaxStyleName, SyntaxStyles}, - SyntaxStyle, + syntax::SyntaxTheme, }; impl Default for SystemColors { @@ -77,541 +75,115 @@ impl Default for PlayerColors { } } -impl SyntaxStyles { +impl SyntaxTheme { pub fn default_light() -> Self { - use SyntaxStyleName::*; - - let neutral: ColorScaleSet = slate().into(); - - Self(IndexMap::from_iter([ - ( - Comment, - SyntaxStyle::builder().color(neutral.light(11)).build(), - ), - ( - CommentDoc, - SyntaxStyle::builder().color(neutral.light(11)).build(), - ), - ( - Primary, - SyntaxStyle::builder().color(neutral.light(12)).build(), - ), - ( - Predictive, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - Hint, - SyntaxStyle::builder() - .color(ColorScaleSet::from(cyan()).light(10)) - .build(), - ), - ( - Emphasis, - SyntaxStyle::builder().weight(FontWeight(600.0)).build(), - ), - ( - EmphasisStrong, - SyntaxStyle::builder().weight(FontWeight(800.0)).build(), - ), - ( - Title, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - LinkUri, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).light(12)) - .build(), - ), - ( - LinkText, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).light(12)) - .build(), - ), - ( - TextLiteral, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).light(12)) - .build(), - ), - ( - Punctuation, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - PunctuationBracket, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - PunctuationDelimiter, - SyntaxStyle::builder().color(neutral.light(10)).build(), - ), - ( - PunctuationSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - PunctuationListMarker, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).light(12)) - .build(), - ), - ( - String, - SyntaxStyle::builder() - .color(ColorScaleSet::from(green()).light(12)) - .build(), - ), - ( - StringSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - StringSpecialSymbol, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - StringEscape, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).light(12)) - .build(), - ), - ( - StringRegex, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).light(12)) - .build(), - ), - ( - Constructor, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).light(12)) - .build(), - ), - // TODO: Continue assigning syntax colors from here - ( - Variant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Type, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - TypeBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Variable, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - VariableSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Label, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Tag, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Attribute, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Property, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Constant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Keyword, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Enum, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Operator, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Number, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Boolean, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - ConstantBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Function, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionSpecialDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionMethod, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - FunctionMethodBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Preproc, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ( - Embedded, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).light(12)) - .build(), - ), - ])) + Self { + highlights: vec![ + ( + "string.special.symbol".into(), + gpui2::rgba(0xad6e26ff).into(), + ), + ("hint".into(), gpui2::rgba(0x9294beff).into()), + ("link_uri".into(), gpui2::rgba(0x3882b7ff).into()), + ("type".into(), gpui2::rgba(0x3882b7ff).into()), + ("string.regex".into(), gpui2::rgba(0xad6e26ff).into()), + ("constant".into(), gpui2::rgba(0x669f59ff).into()), + ("function".into(), gpui2::rgba(0x5b79e3ff).into()), + ("string.special".into(), gpui2::rgba(0xad6e26ff).into()), + ("punctuation.bracket".into(), gpui2::rgba(0x4d4f52ff).into()), + ("variable".into(), gpui2::rgba(0x383a41ff).into()), + ("punctuation".into(), gpui2::rgba(0x383a41ff).into()), + ("property".into(), gpui2::rgba(0xd3604fff).into()), + ("string".into(), gpui2::rgba(0x649f57ff).into()), + ("predictive".into(), gpui2::rgba(0x9b9ec6ff).into()), + ("attribute".into(), gpui2::rgba(0x5c78e2ff).into()), + ("number".into(), gpui2::rgba(0xad6e25ff).into()), + ("constructor".into(), gpui2::rgba(0x5c78e2ff).into()), + ("embedded".into(), gpui2::rgba(0x383a41ff).into()), + ("title".into(), gpui2::rgba(0xd3604fff).into()), + ("tag".into(), gpui2::rgba(0x5c78e2ff).into()), + ("boolean".into(), gpui2::rgba(0xad6e25ff).into()), + ( + "punctuation.list_marker".into(), + gpui2::rgba(0xd3604fff).into(), + ), + ("variant".into(), gpui2::rgba(0x5b79e3ff).into()), + ("emphasis".into(), gpui2::rgba(0x5c78e2ff).into()), + ("link_text".into(), gpui2::rgba(0x5b79e3ff).into()), + ("comment".into(), gpui2::rgba(0xa2a3a7ff).into()), + ("punctuation.special".into(), gpui2::rgba(0xb92b46ff).into()), + ("emphasis.strong".into(), gpui2::rgba(0xad6e25ff).into()), + ("primary".into(), gpui2::rgba(0x383a41ff).into()), + ( + "punctuation.delimiter".into(), + gpui2::rgba(0x4d4f52ff).into(), + ), + ("label".into(), gpui2::rgba(0x5c78e2ff).into()), + ("keyword".into(), gpui2::rgba(0xa449abff).into()), + ("string.escape".into(), gpui2::rgba(0x7c7e86ff).into()), + ("text.literal".into(), gpui2::rgba(0x649f57ff).into()), + ("variable.special".into(), gpui2::rgba(0xad6e25ff).into()), + ("comment.doc".into(), gpui2::rgba(0x7c7e86ff).into()), + ("enum".into(), gpui2::rgba(0xd3604fff).into()), + ("operator".into(), gpui2::rgba(0x3882b7ff).into()), + ("preproc".into(), gpui2::rgba(0x383a41ff).into()), + ], + } } pub fn default_dark() -> Self { - use SyntaxStyleName::*; - - let neutral: ColorScaleSet = slate().into(); - - Self(IndexMap::from_iter([ - ( - Comment, - SyntaxStyle::builder().color(neutral.dark(11)).build(), - ), - ( - CommentDoc, - SyntaxStyle::builder().color(neutral.dark(11)).build(), - ), - ( - Primary, - SyntaxStyle::builder().color(neutral.dark(12)).build(), - ), - ( - Predictive, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - Hint, - SyntaxStyle::builder() - .color(ColorScaleSet::from(cyan()).dark(10)) - .build(), - ), - ( - Emphasis, - SyntaxStyle::builder().weight(FontWeight(600.0)).build(), - ), - ( - EmphasisStrong, - SyntaxStyle::builder().weight(FontWeight(800.0)).build(), - ), - ( - Title, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - LinkUri, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).dark(12)) - .build(), - ), - ( - LinkText, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).dark(12)) - .build(), - ), - ( - TextLiteral, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).dark(12)) - .build(), - ), - ( - Punctuation, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - PunctuationBracket, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - PunctuationDelimiter, - SyntaxStyle::builder().color(neutral.dark(10)).build(), - ), - ( - PunctuationSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - PunctuationListMarker, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).dark(12)) - .build(), - ), - ( - String, - SyntaxStyle::builder() - .color(ColorScaleSet::from(green()).dark(12)) - .build(), - ), - ( - StringSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - StringSpecialSymbol, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - StringEscape, - SyntaxStyle::builder() - .color(ColorScaleSet::from(blue()).dark(12)) - .build(), - ), - ( - StringRegex, - SyntaxStyle::builder() - .color(ColorScaleSet::from(orange()).dark(12)) - .build(), - ), - ( - Constructor, - SyntaxStyle::builder() - .color(ColorScaleSet::from(purple()).dark(12)) - .build(), - ), - // TODO: Continue assigning syntax colors from here - ( - Variant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Type, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - TypeBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Variable, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - VariableSpecial, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Label, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Tag, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Attribute, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Property, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Constant, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Keyword, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Enum, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Operator, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Number, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Boolean, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - ConstantBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Function, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionSpecialDefinition, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionMethod, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - FunctionMethodBuiltin, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Preproc, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ( - Embedded, - SyntaxStyle::builder() - .color(ColorScaleSet::from(red()).dark(12)) - .build(), - ), - ])) + Self { + highlights: vec![ + ("keyword".into(), gpui2::rgba(0xb477cfff).into()), + ("comment.doc".into(), gpui2::rgba(0x878e98ff).into()), + ("variant".into(), gpui2::rgba(0x73ade9ff).into()), + ("property".into(), gpui2::rgba(0xd07277ff).into()), + ("function".into(), gpui2::rgba(0x73ade9ff).into()), + ("type".into(), gpui2::rgba(0x6eb4bfff).into()), + ("tag".into(), gpui2::rgba(0x74ade8ff).into()), + ("string.escape".into(), gpui2::rgba(0x878e98ff).into()), + ("punctuation.bracket".into(), gpui2::rgba(0xb2b9c6ff).into()), + ("hint".into(), gpui2::rgba(0x5a6f89ff).into()), + ("punctuation".into(), gpui2::rgba(0xacb2beff).into()), + ("comment".into(), gpui2::rgba(0x5d636fff).into()), + ("emphasis".into(), gpui2::rgba(0x74ade8ff).into()), + ("punctuation.special".into(), gpui2::rgba(0xb1574bff).into()), + ("link_uri".into(), gpui2::rgba(0x6eb4bfff).into()), + ("string.regex".into(), gpui2::rgba(0xbf956aff).into()), + ("constructor".into(), gpui2::rgba(0x73ade9ff).into()), + ("operator".into(), gpui2::rgba(0x6eb4bfff).into()), + ("constant".into(), gpui2::rgba(0xdfc184ff).into()), + ("string.special".into(), gpui2::rgba(0xbf956aff).into()), + ("emphasis.strong".into(), gpui2::rgba(0xbf956aff).into()), + ( + "string.special.symbol".into(), + gpui2::rgba(0xbf956aff).into(), + ), + ("primary".into(), gpui2::rgba(0xacb2beff).into()), + ("preproc".into(), gpui2::rgba(0xc8ccd4ff).into()), + ("string".into(), gpui2::rgba(0xa1c181ff).into()), + ( + "punctuation.delimiter".into(), + gpui2::rgba(0xb2b9c6ff).into(), + ), + ("embedded".into(), gpui2::rgba(0xc8ccd4ff).into()), + ("enum".into(), gpui2::rgba(0xd07277ff).into()), + ("variable.special".into(), gpui2::rgba(0xbf956aff).into()), + ("text.literal".into(), gpui2::rgba(0xa1c181ff).into()), + ("attribute".into(), gpui2::rgba(0x74ade8ff).into()), + ("link_text".into(), gpui2::rgba(0x73ade9ff).into()), + ("title".into(), gpui2::rgba(0xd07277ff).into()), + ("predictive".into(), gpui2::rgba(0x5a6a87ff).into()), + ("number".into(), gpui2::rgba(0xbf956aff).into()), + ("label".into(), gpui2::rgba(0x74ade8ff).into()), + ("variable".into(), gpui2::rgba(0xc8ccd4ff).into()), + ("boolean".into(), gpui2::rgba(0xbf956aff).into()), + ( + "punctuation.list_marker".into(), + gpui2::rgba(0xd07277ff).into(), + ), + ], + } } } diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs index 4b47e403d6..26a55b5e0d 100644 --- a/crates/theme2/src/default_theme.rs +++ b/crates/theme2/src/default_theme.rs @@ -1,12 +1,12 @@ use crate::{ colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyle}, - default_color_scales, Appearance, SyntaxStyles, ThemeFamily, ThemeVariant, + default_color_scales, Appearance, SyntaxTheme, ThemeFamily, ThemeVariant, }; fn zed_pro_daylight() -> ThemeVariant { ThemeVariant { id: "zed_pro_daylight".to_string(), - name: "Zed Pro Daylight".to_string(), + name: "Zed Pro Daylight".into(), appearance: Appearance::Light, styles: ThemeStyle { system: SystemColors::default(), @@ -14,7 +14,7 @@ fn zed_pro_daylight() -> ThemeVariant { status: StatusColors::default(), git: GitStatusColors::default(), player: PlayerColors::default(), - syntax: SyntaxStyles::default_light(), + syntax: SyntaxTheme::default_light(), }, } } @@ -22,15 +22,15 @@ fn zed_pro_daylight() -> ThemeVariant { pub(crate) fn zed_pro_moonlight() -> ThemeVariant { ThemeVariant { id: "zed_pro_moonlight".to_string(), - name: "Zed Pro Moonlight".to_string(), - appearance: Appearance::Light, + name: "Zed Pro Moonlight".into(), + appearance: Appearance::Dark, styles: ThemeStyle { system: SystemColors::default(), colors: ThemeColors::default_dark(), status: StatusColors::default(), git: GitStatusColors::default(), player: PlayerColors::default(), - syntax: SyntaxStyles::default_dark(), + syntax: SyntaxTheme::default_dark(), }, } } @@ -38,8 +38,8 @@ pub(crate) fn zed_pro_moonlight() -> ThemeVariant { pub fn zed_pro_family() -> ThemeFamily { ThemeFamily { id: "zed_pro".to_string(), - name: "Zed Pro".to_string(), - author: "Zed Team".to_string(), + name: "Zed Pro".into(), + author: "Zed Team".into(), themes: vec![zed_pro_daylight(), zed_pro_moonlight()], scales: default_color_scales(), } diff --git a/crates/theme2/src/registry.rs b/crates/theme2/src/registry.rs index eec82ef5a7..f30f5ead91 100644 --- a/crates/theme2/src/registry.rs +++ b/crates/theme2/src/registry.rs @@ -1,17 +1,22 @@ -use crate::{themes, Theme, ThemeMetadata}; +use crate::{zed_pro_family, ThemeFamily, ThemeVariant}; use anyhow::{anyhow, Result}; use gpui2::SharedString; use std::{collections::HashMap, sync::Arc}; pub struct ThemeRegistry { - themes: HashMap>, + themes: HashMap>, } impl ThemeRegistry { - fn insert_themes(&mut self, themes: impl IntoIterator) { + fn insert_theme_families(&mut self, families: impl IntoIterator) { + for family in families.into_iter() { + self.insert_themes(family.themes); + } + } + + fn insert_themes(&mut self, themes: impl IntoIterator) { for theme in themes.into_iter() { - self.themes - .insert(theme.metadata.name.clone(), Arc::new(theme)); + self.themes.insert(theme.name.clone(), Arc::new(theme)); } } @@ -19,11 +24,11 @@ impl ThemeRegistry { self.themes.keys().cloned() } - pub fn list(&self, _staff: bool) -> impl Iterator + '_ { - self.themes.values().map(|theme| theme.metadata.clone()) + pub fn list(&self, _staff: bool) -> impl Iterator + '_ { + self.themes.values().map(|theme| theme.name.clone()) } - pub fn get(&self, name: &str) -> Result> { + pub fn get(&self, name: &str) -> Result> { self.themes .get(name) .ok_or_else(|| anyhow!("theme not found: {}", name)) @@ -37,47 +42,7 @@ impl Default for ThemeRegistry { themes: HashMap::default(), }; - this.insert_themes([ - themes::andromeda(), - themes::atelier_cave_dark(), - themes::atelier_cave_light(), - themes::atelier_dune_dark(), - themes::atelier_dune_light(), - themes::atelier_estuary_dark(), - themes::atelier_estuary_light(), - themes::atelier_forest_dark(), - themes::atelier_forest_light(), - themes::atelier_heath_dark(), - themes::atelier_heath_light(), - themes::atelier_lakeside_dark(), - themes::atelier_lakeside_light(), - themes::atelier_plateau_dark(), - themes::atelier_plateau_light(), - themes::atelier_savanna_dark(), - themes::atelier_savanna_light(), - themes::atelier_seaside_dark(), - themes::atelier_seaside_light(), - themes::atelier_sulphurpool_dark(), - themes::atelier_sulphurpool_light(), - themes::ayu_dark(), - themes::ayu_light(), - themes::ayu_mirage(), - themes::gruvbox_dark(), - themes::gruvbox_dark_hard(), - themes::gruvbox_dark_soft(), - themes::gruvbox_light(), - themes::gruvbox_light_hard(), - themes::gruvbox_light_soft(), - themes::one_dark(), - themes::one_light(), - themes::rose_pine(), - themes::rose_pine_dawn(), - themes::rose_pine_moon(), - themes::sandcastle(), - themes::solarized_dark(), - themes::solarized_light(), - themes::summercamp(), - ]); + this.insert_theme_families([zed_pro_family()]); this } diff --git a/crates/theme2/src/settings.rs b/crates/theme2/src/settings.rs index 3a61bbbe1e..bad00ee196 100644 --- a/crates/theme2/src/settings.rs +++ b/crates/theme2/src/settings.rs @@ -1,4 +1,4 @@ -use crate::{zed_pro_moonlight, Theme, ThemeRegistry, ThemeVariant}; +use crate::{ThemeRegistry, ThemeVariant}; use anyhow::Result; use gpui2::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels}; use schemars::{ @@ -21,7 +21,6 @@ pub struct ThemeSettings { pub buffer_font_size: Pixels, pub buffer_line_height: BufferLineHeight, pub active_theme: Arc, - pub old_active_theme: Arc, } #[derive(Default)] @@ -124,8 +123,9 @@ impl settings2::Settings for ThemeSettings { }, buffer_font_size: defaults.buffer_font_size.unwrap().into(), buffer_line_height: defaults.buffer_line_height.unwrap(), - active_theme: Arc::new(zed_pro_moonlight()), - old_active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + active_theme: themes.get("Zed Pro Moonlight").unwrap(), + // todo!(Read the theme name from the settings) + // active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; for value in user_values.into_iter().copied().cloned() { @@ -138,7 +138,7 @@ impl settings2::Settings for ThemeSettings { if let Some(value) = &value.theme { if let Some(theme) = themes.get(value).log_err() { - this.old_active_theme = theme; + this.active_theme = theme; } } diff --git a/crates/theme2/src/syntax.rs b/crates/theme2/src/syntax.rs index 82c4c87796..1cf8564bca 100644 --- a/crates/theme2/src/syntax.rs +++ b/crates/theme2/src/syntax.rs @@ -1,227 +1,37 @@ -use gpui2::{FontWeight, Hsla, SharedString}; -use indexmap::IndexMap; +use gpui2::{HighlightStyle, Hsla}; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum SyntaxStyleName { - Comment, - CommentDoc, - Primary, - Predictive, - Hint, - Emphasis, - EmphasisStrong, - Title, - LinkUri, - LinkText, - TextLiteral, - Punctuation, - PunctuationBracket, - PunctuationDelimiter, - PunctuationSpecial, - PunctuationListMarker, - String, - StringSpecial, - StringSpecialSymbol, - StringEscape, - StringRegex, - Constructor, - Variant, - Type, - TypeBuiltin, - Variable, - VariableSpecial, - Label, - Tag, - Attribute, - Property, - Constant, - Keyword, - Enum, - Operator, - Number, - Boolean, - ConstantBuiltin, - Function, - FunctionBuiltin, - FunctionDefinition, - FunctionSpecialDefinition, - FunctionMethod, - FunctionMethodBuiltin, - Preproc, - Embedded, - Custom(SharedString), +#[derive(Clone)] +pub struct SyntaxTheme { + pub highlights: Vec<(String, HighlightStyle)>, } -impl std::str::FromStr for SyntaxStyleName { - type Err = (); - - fn from_str(s: &str) -> Result { - Ok(match s { - "attribute" => Self::Attribute, - "boolean" => Self::Boolean, - "comment" => Self::Comment, - "comment.doc" => Self::CommentDoc, - "constant" => Self::Constant, - "constructor" => Self::Constructor, - "embedded" => Self::Embedded, - "emphasis" => Self::Emphasis, - "emphasis.strong" => Self::EmphasisStrong, - "enum" => Self::Enum, - "function" => Self::Function, - "function.builtin" => Self::FunctionBuiltin, - "function.definition" => Self::FunctionDefinition, - "function.special_definition" => Self::FunctionSpecialDefinition, - "function.method" => Self::FunctionMethod, - "function.method_builtin" => Self::FunctionMethodBuiltin, - "hint" => Self::Hint, - "keyword" => Self::Keyword, - "label" => Self::Label, - "link_text" => Self::LinkText, - "link_uri" => Self::LinkUri, - "number" => Self::Number, - "operator" => Self::Operator, - "predictive" => Self::Predictive, - "preproc" => Self::Preproc, - "primary" => Self::Primary, - "property" => Self::Property, - "punctuation" => Self::Punctuation, - "punctuation.bracket" => Self::PunctuationBracket, - "punctuation.delimiter" => Self::PunctuationDelimiter, - "punctuation.list_marker" => Self::PunctuationListMarker, - "punctuation.special" => Self::PunctuationSpecial, - "string" => Self::String, - "string.escape" => Self::StringEscape, - "string.regex" => Self::StringRegex, - "string.special" => Self::StringSpecial, - "string.special.symbol" => Self::StringSpecialSymbol, - "tag" => Self::Tag, - "text.literal" => Self::TextLiteral, - "title" => Self::Title, - "type" => Self::Type, - "type.builtin" => Self::TypeBuiltin, - "variable" => Self::Variable, - "variable.special" => Self::VariableSpecial, - "constant.builtin" => Self::ConstantBuiltin, - "variant" => Self::Variant, - name => Self::Custom(name.to_string().into()), - }) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct SyntaxStyle { - pub color: Hsla, - pub weight: FontWeight, - pub underline: bool, - pub italic: bool, - // Nate: In the future I'd like to enable using background highlights for syntax highlighting - // pub highlight: Hsla, -} - -impl SyntaxStyle { - pub fn builder() -> SyntaxStyleBuilder { - SyntaxStyleBuilder::new() - } -} - -impl Default for SyntaxStyle { - fn default() -> Self { - Self { - color: gpui2::black(), - weight: FontWeight::default(), - italic: false, - underline: false, - } - } -} - -pub struct SyntaxStyleBuilder { - pub color: Hsla, - pub weight: FontWeight, - pub underline: bool, - pub italic: bool, -} - -impl SyntaxStyleBuilder { - pub fn new() -> Self { - SyntaxStyleBuilder { - color: gpui2::black(), - weight: FontWeight::default(), - underline: false, - italic: false, - } - } - - pub fn color(mut self, color: Hsla) -> Self { - self.color = color; - self - } - - pub fn weight(mut self, weight: FontWeight) -> Self { - self.weight = weight; - self - } - - pub fn underline(mut self, underline: bool) -> Self { - self.underline = underline; - self - } - - pub fn italic(mut self, italic: bool) -> Self { - self.italic = italic; - self - } - - pub fn build(self) -> SyntaxStyle { - SyntaxStyle { - color: self.color, - weight: self.weight, - underline: self.underline, - italic: self.italic, - } - } -} - -pub struct SyntaxStyles(pub IndexMap); - -impl SyntaxStyles { +impl SyntaxTheme { // TOOD: Get this working with `#[cfg(test)]`. Why isn't it? pub fn new_test(colors: impl IntoIterator) -> Self { - Self(IndexMap::from_iter(colors.into_iter().map( - |(name, color)| { - ( - name.parse().unwrap(), - SyntaxStyle::builder().color(color).build(), - ) - }, - ))) + SyntaxTheme { + highlights: colors + .into_iter() + .map(|(key, color)| { + ( + key.to_owned(), + HighlightStyle { + color: Some(color), + ..Default::default() + }, + ) + }) + .collect(), + } } - pub fn get(&self, name: &str) -> SyntaxStyle { - self.0 - .get(&name.parse::().unwrap()) - .cloned() + pub fn get(&self, name: &str) -> HighlightStyle { + self.highlights + .iter() + .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None }) .unwrap_or_default() } pub fn color(&self, name: &str) -> Hsla { - self.get(name).color - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parse_syntax_style_name() { - let name = "comment".parse::().unwrap(); - assert_eq!(name, SyntaxStyleName::Comment); - } - - #[test] - fn create_custom_syntax_style_name() { - let name = "custom".parse::().unwrap(); - assert_eq!(name, SyntaxStyleName::Custom("custom".into())); + self.get(name).color.unwrap_or_default() } } diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index ea1ad5b26c..372e976bd3 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -5,7 +5,6 @@ mod registry; mod scale; mod settings; mod syntax; -mod themes; mod utils; pub use colors::*; @@ -16,9 +15,7 @@ pub use scale::*; pub use settings::*; pub use syntax::*; -use std::sync::Arc; - -use gpui2::{AppContext, HighlightStyle, Hsla, SharedString}; +use gpui2::{AppContext, Hsla, SharedString}; use settings2::Settings; #[derive(Debug, Clone, PartialEq)] @@ -45,8 +42,8 @@ impl ActiveTheme for AppContext { pub struct ThemeFamily { #[allow(dead_code)] pub(crate) id: String, - pub name: String, - pub author: String, + pub name: SharedString, + pub author: SharedString, pub themes: Vec, pub scales: ColorScales, } @@ -56,7 +53,7 @@ impl ThemeFamily {} pub struct ThemeVariant { #[allow(dead_code)] pub(crate) id: String, - pub name: String, + pub name: SharedString, pub appearance: Appearance, pub styles: ThemeStyle, } @@ -68,9 +65,9 @@ impl ThemeVariant { &self.styles.colors } - /// Returns the [`SyntaxStyles`] for the theme. + /// Returns the [`SyntaxTheme`] for the theme. #[inline(always)] - pub fn syntax(&self) -> &SyntaxStyles { + pub fn syntax(&self) -> &SyntaxTheme { &self.styles.syntax } @@ -80,125 +77,3 @@ impl ThemeVariant { self.syntax().color(name) } } - -pub struct Theme { - pub metadata: ThemeMetadata, - - pub transparent: Hsla, - pub mac_os_traffic_light_red: Hsla, - pub mac_os_traffic_light_yellow: Hsla, - pub mac_os_traffic_light_green: Hsla, - pub border: Hsla, - pub border_variant: Hsla, - pub border_focused: Hsla, - pub border_transparent: Hsla, - /// The background color of an elevated surface, like a modal, tooltip or toast. - pub elevated_surface: Hsla, - pub surface: Hsla, - /// Window background color of the base app - pub background: Hsla, - /// Default background for elements like filled buttons, - /// text fields, checkboxes, radio buttons, etc. - /// - TODO: Map to step 3. - pub filled_element: Hsla, - /// The background color of a hovered element, like a button being hovered - /// with a mouse, or hovered on a touch screen. - /// - TODO: Map to step 4. - pub filled_element_hover: Hsla, - /// The background color of an active element, like a button being pressed, - /// or tapped on a touch screen. - /// - TODO: Map to step 5. - pub filled_element_active: Hsla, - /// The background color of a selected element, like a selected tab, - /// a button toggled on, or a checkbox that is checked. - pub filled_element_selected: Hsla, - pub filled_element_disabled: Hsla, - pub ghost_element: Hsla, - /// The background color of a hovered element with no default background, - /// like a ghost-style button or an interactable list item. - /// - TODO: Map to step 3. - pub ghost_element_hover: Hsla, - /// - TODO: Map to step 4. - pub ghost_element_active: Hsla, - pub ghost_element_selected: Hsla, - pub ghost_element_disabled: Hsla, - pub text: Hsla, - pub text_muted: Hsla, - pub text_placeholder: Hsla, - pub text_disabled: Hsla, - pub text_accent: Hsla, - pub icon_muted: Hsla, - pub syntax: SyntaxTheme, - - pub status_bar: Hsla, - pub title_bar: Hsla, - pub toolbar: Hsla, - pub tab_bar: Hsla, - /// The background of the editor - pub editor: Hsla, - pub editor_subheader: Hsla, - pub editor_active_line: Hsla, - pub terminal: Hsla, - pub image_fallback_background: Hsla, - - pub git_created: Hsla, - pub git_modified: Hsla, - pub git_deleted: Hsla, - pub git_conflict: Hsla, - pub git_ignored: Hsla, - pub git_renamed: Hsla, - - pub players: [PlayerTheme; 8], -} - -#[derive(Clone)] -pub struct SyntaxTheme { - pub highlights: Vec<(String, HighlightStyle)>, -} - -impl SyntaxTheme { - // TOOD: Get this working with `#[cfg(test)]`. Why isn't it? - pub fn new_test(colors: impl IntoIterator) -> Self { - SyntaxTheme { - highlights: colors - .into_iter() - .map(|(key, color)| { - ( - key.to_owned(), - HighlightStyle { - color: Some(color), - ..Default::default() - }, - ) - }) - .collect(), - } - } - - pub fn get(&self, name: &str) -> HighlightStyle { - self.highlights - .iter() - .find_map(|entry| if entry.0 == name { Some(entry.1) } else { None }) - .unwrap_or_default() - } - - pub fn color(&self, name: &str) -> Hsla { - self.get(name).color.unwrap_or_default() - } -} - -#[derive(Clone, Copy)] -pub struct PlayerTheme { - pub cursor: Hsla, - pub selection: Hsla, -} - -#[derive(Clone)] -pub struct ThemeMetadata { - pub name: SharedString, - pub is_light: bool, -} - -pub struct Editor { - pub syntax: Arc, -} diff --git a/crates/theme2/src/themes/andromeda.rs b/crates/theme2/src/themes/andromeda.rs deleted file mode 100644 index 6afd7edd4d..0000000000 --- a/crates/theme2/src/themes/andromeda.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn andromeda() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Andromeda".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x2b2f38ff).into(), - border_variant: rgba(0x2b2f38ff).into(), - border_focused: rgba(0x183934ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x262933ff).into(), - surface: rgba(0x21242bff).into(), - background: rgba(0x262933ff).into(), - filled_element: rgba(0x262933ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x12231fff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x12231fff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf7f7f8ff).into(), - text_muted: rgba(0xaca8aeff).into(), - text_placeholder: rgba(0xf82871ff).into(), - text_disabled: rgba(0x6b6b73ff).into(), - text_accent: rgba(0x10a793ff).into(), - icon_muted: rgba(0xaca8aeff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("emphasis".into(), rgba(0x10a793ff).into()), - ("punctuation.bracket".into(), rgba(0xd8d5dbff).into()), - ("attribute".into(), rgba(0x10a793ff).into()), - ("variable".into(), rgba(0xf7f7f8ff).into()), - ("predictive".into(), rgba(0x315f70ff).into()), - ("property".into(), rgba(0x10a793ff).into()), - ("variant".into(), rgba(0x10a793ff).into()), - ("embedded".into(), rgba(0xf7f7f8ff).into()), - ("string.special".into(), rgba(0xf29c14ff).into()), - ("keyword".into(), rgba(0x10a793ff).into()), - ("tag".into(), rgba(0x10a793ff).into()), - ("enum".into(), rgba(0xf29c14ff).into()), - ("link_text".into(), rgba(0xf29c14ff).into()), - ("primary".into(), rgba(0xf7f7f8ff).into()), - ("punctuation".into(), rgba(0xd8d5dbff).into()), - ("punctuation.special".into(), rgba(0xd8d5dbff).into()), - ("function".into(), rgba(0xfee56cff).into()), - ("number".into(), rgba(0x96df71ff).into()), - ("preproc".into(), rgba(0xf7f7f8ff).into()), - ("operator".into(), rgba(0xf29c14ff).into()), - ("constructor".into(), rgba(0x10a793ff).into()), - ("string.escape".into(), rgba(0xafabb1ff).into()), - ("string.special.symbol".into(), rgba(0xf29c14ff).into()), - ("string".into(), rgba(0xf29c14ff).into()), - ("comment".into(), rgba(0xafabb1ff).into()), - ("hint".into(), rgba(0x618399ff).into()), - ("type".into(), rgba(0x08e7c5ff).into()), - ("label".into(), rgba(0x10a793ff).into()), - ("comment.doc".into(), rgba(0xafabb1ff).into()), - ("text.literal".into(), rgba(0xf29c14ff).into()), - ("constant".into(), rgba(0x96df71ff).into()), - ("string.regex".into(), rgba(0xf29c14ff).into()), - ("emphasis.strong".into(), rgba(0x10a793ff).into()), - ("title".into(), rgba(0xf7f7f8ff).into()), - ("punctuation.delimiter".into(), rgba(0xd8d5dbff).into()), - ("link_uri".into(), rgba(0x96df71ff).into()), - ("boolean".into(), rgba(0x96df71ff).into()), - ("punctuation.list_marker".into(), rgba(0xd8d5dbff).into()), - ], - }, - status_bar: rgba(0x262933ff).into(), - title_bar: rgba(0x262933ff).into(), - toolbar: rgba(0x1e2025ff).into(), - tab_bar: rgba(0x21242bff).into(), - editor: rgba(0x1e2025ff).into(), - editor_subheader: rgba(0x21242bff).into(), - editor_active_line: rgba(0x21242bff).into(), - terminal: rgba(0x1e2025ff).into(), - image_fallback_background: rgba(0x262933ff).into(), - git_created: rgba(0x96df71ff).into(), - git_modified: rgba(0x10a793ff).into(), - git_deleted: rgba(0xf82871ff).into(), - git_conflict: rgba(0xfee56cff).into(), - git_ignored: rgba(0x6b6b73ff).into(), - git_renamed: rgba(0xfee56cff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x10a793ff).into(), - selection: rgba(0x10a7933d).into(), - }, - PlayerTheme { - cursor: rgba(0x96df71ff).into(), - selection: rgba(0x96df713d).into(), - }, - PlayerTheme { - cursor: rgba(0xc74cecff).into(), - selection: rgba(0xc74cec3d).into(), - }, - PlayerTheme { - cursor: rgba(0xf29c14ff).into(), - selection: rgba(0xf29c143d).into(), - }, - PlayerTheme { - cursor: rgba(0x893ea6ff).into(), - selection: rgba(0x893ea63d).into(), - }, - PlayerTheme { - cursor: rgba(0x08e7c5ff).into(), - selection: rgba(0x08e7c53d).into(), - }, - PlayerTheme { - cursor: rgba(0xf82871ff).into(), - selection: rgba(0xf828713d).into(), - }, - PlayerTheme { - cursor: rgba(0xfee56cff).into(), - selection: rgba(0xfee56c3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_cave_dark.rs b/crates/theme2/src/themes/atelier_cave_dark.rs deleted file mode 100644 index c5190f4e98..0000000000 --- a/crates/theme2/src/themes/atelier_cave_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_cave_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Cave Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x56505eff).into(), - border_variant: rgba(0x56505eff).into(), - border_focused: rgba(0x222953ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3a353fff).into(), - surface: rgba(0x221f26ff).into(), - background: rgba(0x3a353fff).into(), - filled_element: rgba(0x3a353fff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x161a35ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x161a35ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xefecf4ff).into(), - text_muted: rgba(0x898591ff).into(), - text_placeholder: rgba(0xbe4677ff).into(), - text_disabled: rgba(0x756f7eff).into(), - text_accent: rgba(0x566ddaff).into(), - icon_muted: rgba(0x898591ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("comment.doc".into(), rgba(0x8b8792ff).into()), - ("tag".into(), rgba(0x566ddaff).into()), - ("link_text".into(), rgba(0xaa563bff).into()), - ("constructor".into(), rgba(0x566ddaff).into()), - ("punctuation".into(), rgba(0xe2dfe7ff).into()), - ("punctuation.special".into(), rgba(0xbf3fbfff).into()), - ("string.special.symbol".into(), rgba(0x299292ff).into()), - ("string.escape".into(), rgba(0x8b8792ff).into()), - ("emphasis".into(), rgba(0x566ddaff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("punctuation.delimiter".into(), rgba(0x8b8792ff).into()), - ("variant".into(), rgba(0xa06d3aff).into()), - ("variable.special".into(), rgba(0x9559e7ff).into()), - ("text.literal".into(), rgba(0xaa563bff).into()), - ("punctuation.list_marker".into(), rgba(0xe2dfe7ff).into()), - ("comment".into(), rgba(0x655f6dff).into()), - ("function.method".into(), rgba(0x576cdbff).into()), - ("property".into(), rgba(0xbe4677ff).into()), - ("operator".into(), rgba(0x8b8792ff).into()), - ("emphasis.strong".into(), rgba(0x566ddaff).into()), - ("label".into(), rgba(0x566ddaff).into()), - ("enum".into(), rgba(0xaa563bff).into()), - ("number".into(), rgba(0xaa563bff).into()), - ("primary".into(), rgba(0xe2dfe7ff).into()), - ("keyword".into(), rgba(0x9559e7ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("punctuation.bracket".into(), rgba(0x8b8792ff).into()), - ("constant".into(), rgba(0x2b9292ff).into()), - ("string.special".into(), rgba(0xbf3fbfff).into()), - ("title".into(), rgba(0xefecf4ff).into()), - ("preproc".into(), rgba(0xefecf4ff).into()), - ("link_uri".into(), rgba(0x2b9292ff).into()), - ("string".into(), rgba(0x299292ff).into()), - ("embedded".into(), rgba(0xefecf4ff).into()), - ("hint".into(), rgba(0x706897ff).into()), - ("boolean".into(), rgba(0x2b9292ff).into()), - ("variable".into(), rgba(0xe2dfe7ff).into()), - ("predictive".into(), rgba(0x615787ff).into()), - ("string.regex".into(), rgba(0x388bc6ff).into()), - ("function".into(), rgba(0x576cdbff).into()), - ("attribute".into(), rgba(0x566ddaff).into()), - ], - }, - status_bar: rgba(0x3a353fff).into(), - title_bar: rgba(0x3a353fff).into(), - toolbar: rgba(0x19171cff).into(), - tab_bar: rgba(0x221f26ff).into(), - editor: rgba(0x19171cff).into(), - editor_subheader: rgba(0x221f26ff).into(), - editor_active_line: rgba(0x221f26ff).into(), - terminal: rgba(0x19171cff).into(), - image_fallback_background: rgba(0x3a353fff).into(), - git_created: rgba(0x2b9292ff).into(), - git_modified: rgba(0x566ddaff).into(), - git_deleted: rgba(0xbe4677ff).into(), - git_conflict: rgba(0xa06d3aff).into(), - git_ignored: rgba(0x756f7eff).into(), - git_renamed: rgba(0xa06d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x566ddaff).into(), - selection: rgba(0x566dda3d).into(), - }, - PlayerTheme { - cursor: rgba(0x2b9292ff).into(), - selection: rgba(0x2b92923d).into(), - }, - PlayerTheme { - cursor: rgba(0xbf41bfff).into(), - selection: rgba(0xbf41bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0xaa563bff).into(), - selection: rgba(0xaa563b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x955ae6ff).into(), - selection: rgba(0x955ae63d).into(), - }, - PlayerTheme { - cursor: rgba(0x3a8bc6ff).into(), - selection: rgba(0x3a8bc63d).into(), - }, - PlayerTheme { - cursor: rgba(0xbe4677ff).into(), - selection: rgba(0xbe46773d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06d3aff).into(), - selection: rgba(0xa06d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_cave_light.rs b/crates/theme2/src/themes/atelier_cave_light.rs deleted file mode 100644 index ae2e912f14..0000000000 --- a/crates/theme2/src/themes/atelier_cave_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_cave_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Cave Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8f8b96ff).into(), - border_variant: rgba(0x8f8b96ff).into(), - border_focused: rgba(0xc8c7f2ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xbfbcc5ff).into(), - surface: rgba(0xe6e3ebff).into(), - background: rgba(0xbfbcc5ff).into(), - filled_element: rgba(0xbfbcc5ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe1e0f9ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe1e0f9ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x19171cff).into(), - text_muted: rgba(0x5a5462ff).into(), - text_placeholder: rgba(0xbd4677ff).into(), - text_disabled: rgba(0x6e6876ff).into(), - text_accent: rgba(0x586cdaff).into(), - icon_muted: rgba(0x5a5462ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("link_text".into(), rgba(0xaa573cff).into()), - ("string".into(), rgba(0x299292ff).into()), - ("emphasis".into(), rgba(0x586cdaff).into()), - ("label".into(), rgba(0x586cdaff).into()), - ("property".into(), rgba(0xbe4677ff).into()), - ("emphasis.strong".into(), rgba(0x586cdaff).into()), - ("constant".into(), rgba(0x2b9292ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("embedded".into(), rgba(0x19171cff).into()), - ("punctuation.special".into(), rgba(0xbf3fbfff).into()), - ("function".into(), rgba(0x576cdbff).into()), - ("tag".into(), rgba(0x586cdaff).into()), - ("number".into(), rgba(0xaa563bff).into()), - ("primary".into(), rgba(0x26232aff).into()), - ("text.literal".into(), rgba(0xaa573cff).into()), - ("variant".into(), rgba(0xa06d3aff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("punctuation".into(), rgba(0x26232aff).into()), - ("string.escape".into(), rgba(0x585260ff).into()), - ("keyword".into(), rgba(0x9559e7ff).into()), - ("title".into(), rgba(0x19171cff).into()), - ("constructor".into(), rgba(0x586cdaff).into()), - ("punctuation.list_marker".into(), rgba(0x26232aff).into()), - ("string.special".into(), rgba(0xbf3fbfff).into()), - ("operator".into(), rgba(0x585260ff).into()), - ("function.method".into(), rgba(0x576cdbff).into()), - ("link_uri".into(), rgba(0x2b9292ff).into()), - ("variable.special".into(), rgba(0x9559e7ff).into()), - ("hint".into(), rgba(0x776d9dff).into()), - ("punctuation.bracket".into(), rgba(0x585260ff).into()), - ("string.special.symbol".into(), rgba(0x299292ff).into()), - ("predictive".into(), rgba(0x887fafff).into()), - ("attribute".into(), rgba(0x586cdaff).into()), - ("enum".into(), rgba(0xaa573cff).into()), - ("preproc".into(), rgba(0x19171cff).into()), - ("boolean".into(), rgba(0x2b9292ff).into()), - ("variable".into(), rgba(0x26232aff).into()), - ("comment.doc".into(), rgba(0x585260ff).into()), - ("string.regex".into(), rgba(0x388bc6ff).into()), - ("punctuation.delimiter".into(), rgba(0x585260ff).into()), - ("comment".into(), rgba(0x7d7787ff).into()), - ], - }, - status_bar: rgba(0xbfbcc5ff).into(), - title_bar: rgba(0xbfbcc5ff).into(), - toolbar: rgba(0xefecf4ff).into(), - tab_bar: rgba(0xe6e3ebff).into(), - editor: rgba(0xefecf4ff).into(), - editor_subheader: rgba(0xe6e3ebff).into(), - editor_active_line: rgba(0xe6e3ebff).into(), - terminal: rgba(0xefecf4ff).into(), - image_fallback_background: rgba(0xbfbcc5ff).into(), - git_created: rgba(0x2b9292ff).into(), - git_modified: rgba(0x586cdaff).into(), - git_deleted: rgba(0xbd4677ff).into(), - git_conflict: rgba(0xa06e3bff).into(), - git_ignored: rgba(0x6e6876ff).into(), - git_renamed: rgba(0xa06e3bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x586cdaff).into(), - selection: rgba(0x586cda3d).into(), - }, - PlayerTheme { - cursor: rgba(0x2b9292ff).into(), - selection: rgba(0x2b92923d).into(), - }, - PlayerTheme { - cursor: rgba(0xbf41bfff).into(), - selection: rgba(0xbf41bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0xaa573cff).into(), - selection: rgba(0xaa573c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x955ae6ff).into(), - selection: rgba(0x955ae63d).into(), - }, - PlayerTheme { - cursor: rgba(0x3a8bc6ff).into(), - selection: rgba(0x3a8bc63d).into(), - }, - PlayerTheme { - cursor: rgba(0xbd4677ff).into(), - selection: rgba(0xbd46773d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06e3bff).into(), - selection: rgba(0xa06e3b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_dune_dark.rs b/crates/theme2/src/themes/atelier_dune_dark.rs deleted file mode 100644 index 03d0c5eea0..0000000000 --- a/crates/theme2/src/themes/atelier_dune_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_dune_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Dune Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x6c695cff).into(), - border_variant: rgba(0x6c695cff).into(), - border_focused: rgba(0x262f56ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x45433bff).into(), - surface: rgba(0x262622ff).into(), - background: rgba(0x45433bff).into(), - filled_element: rgba(0x45433bff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x171e38ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x171e38ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfefbecff).into(), - text_muted: rgba(0xa4a08bff).into(), - text_placeholder: rgba(0xd73837ff).into(), - text_disabled: rgba(0x8f8b77ff).into(), - text_accent: rgba(0x6684e0ff).into(), - icon_muted: rgba(0xa4a08bff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("constructor".into(), rgba(0x6684e0ff).into()), - ("punctuation".into(), rgba(0xe8e4cfff).into()), - ("punctuation.delimiter".into(), rgba(0xa6a28cff).into()), - ("string.special".into(), rgba(0xd43451ff).into()), - ("string.escape".into(), rgba(0xa6a28cff).into()), - ("comment".into(), rgba(0x7d7a68ff).into()), - ("enum".into(), rgba(0xb65611ff).into()), - ("variable.special".into(), rgba(0xb854d4ff).into()), - ("primary".into(), rgba(0xe8e4cfff).into()), - ("comment.doc".into(), rgba(0xa6a28cff).into()), - ("label".into(), rgba(0x6684e0ff).into()), - ("operator".into(), rgba(0xa6a28cff).into()), - ("string".into(), rgba(0x5fac38ff).into()), - ("variant".into(), rgba(0xae9512ff).into()), - ("variable".into(), rgba(0xe8e4cfff).into()), - ("function.method".into(), rgba(0x6583e1ff).into()), - ( - "function.special.definition".into(), - rgba(0xae9512ff).into(), - ), - ("string.regex".into(), rgba(0x1ead82ff).into()), - ("emphasis.strong".into(), rgba(0x6684e0ff).into()), - ("punctuation.special".into(), rgba(0xd43451ff).into()), - ("punctuation.bracket".into(), rgba(0xa6a28cff).into()), - ("link_text".into(), rgba(0xb65611ff).into()), - ("link_uri".into(), rgba(0x5fac39ff).into()), - ("boolean".into(), rgba(0x5fac39ff).into()), - ("hint".into(), rgba(0xb17272ff).into()), - ("tag".into(), rgba(0x6684e0ff).into()), - ("function".into(), rgba(0x6583e1ff).into()), - ("title".into(), rgba(0xfefbecff).into()), - ("property".into(), rgba(0xd73737ff).into()), - ("type".into(), rgba(0xae9512ff).into()), - ("constant".into(), rgba(0x5fac39ff).into()), - ("attribute".into(), rgba(0x6684e0ff).into()), - ("predictive".into(), rgba(0x9c6262ff).into()), - ("string.special.symbol".into(), rgba(0x5fac38ff).into()), - ("punctuation.list_marker".into(), rgba(0xe8e4cfff).into()), - ("emphasis".into(), rgba(0x6684e0ff).into()), - ("keyword".into(), rgba(0xb854d4ff).into()), - ("text.literal".into(), rgba(0xb65611ff).into()), - ("number".into(), rgba(0xb65610ff).into()), - ("preproc".into(), rgba(0xfefbecff).into()), - ("embedded".into(), rgba(0xfefbecff).into()), - ], - }, - status_bar: rgba(0x45433bff).into(), - title_bar: rgba(0x45433bff).into(), - toolbar: rgba(0x20201dff).into(), - tab_bar: rgba(0x262622ff).into(), - editor: rgba(0x20201dff).into(), - editor_subheader: rgba(0x262622ff).into(), - editor_active_line: rgba(0x262622ff).into(), - terminal: rgba(0x20201dff).into(), - image_fallback_background: rgba(0x45433bff).into(), - git_created: rgba(0x5fac39ff).into(), - git_modified: rgba(0x6684e0ff).into(), - git_deleted: rgba(0xd73837ff).into(), - git_conflict: rgba(0xae9414ff).into(), - git_ignored: rgba(0x8f8b77ff).into(), - git_renamed: rgba(0xae9414ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x6684e0ff).into(), - selection: rgba(0x6684e03d).into(), - }, - PlayerTheme { - cursor: rgba(0x5fac39ff).into(), - selection: rgba(0x5fac393d).into(), - }, - PlayerTheme { - cursor: rgba(0xd43651ff).into(), - selection: rgba(0xd436513d).into(), - }, - PlayerTheme { - cursor: rgba(0xb65611ff).into(), - selection: rgba(0xb656113d).into(), - }, - PlayerTheme { - cursor: rgba(0xb854d3ff).into(), - selection: rgba(0xb854d33d).into(), - }, - PlayerTheme { - cursor: rgba(0x20ad83ff).into(), - selection: rgba(0x20ad833d).into(), - }, - PlayerTheme { - cursor: rgba(0xd73837ff).into(), - selection: rgba(0xd738373d).into(), - }, - PlayerTheme { - cursor: rgba(0xae9414ff).into(), - selection: rgba(0xae94143d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_dune_light.rs b/crates/theme2/src/themes/atelier_dune_light.rs deleted file mode 100644 index 1d0f944916..0000000000 --- a/crates/theme2/src/themes/atelier_dune_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_dune_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Dune Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xa8a48eff).into(), - border_variant: rgba(0xa8a48eff).into(), - border_focused: rgba(0xcdd1f5ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xcecab4ff).into(), - surface: rgba(0xeeebd7ff).into(), - background: rgba(0xcecab4ff).into(), - filled_element: rgba(0xcecab4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe3e5faff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe3e5faff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x20201dff).into(), - text_muted: rgba(0x706d5fff).into(), - text_placeholder: rgba(0xd73737ff).into(), - text_disabled: rgba(0x878471ff).into(), - text_accent: rgba(0x6684dfff).into(), - icon_muted: rgba(0x706d5fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("primary".into(), rgba(0x292824ff).into()), - ("comment".into(), rgba(0x999580ff).into()), - ("type".into(), rgba(0xae9512ff).into()), - ("variant".into(), rgba(0xae9512ff).into()), - ("label".into(), rgba(0x6684dfff).into()), - ("function.method".into(), rgba(0x6583e1ff).into()), - ("variable.special".into(), rgba(0xb854d4ff).into()), - ("string.regex".into(), rgba(0x1ead82ff).into()), - ("property".into(), rgba(0xd73737ff).into()), - ("keyword".into(), rgba(0xb854d4ff).into()), - ("number".into(), rgba(0xb65610ff).into()), - ("punctuation.list_marker".into(), rgba(0x292824ff).into()), - ( - "function.special.definition".into(), - rgba(0xae9512ff).into(), - ), - ("punctuation.special".into(), rgba(0xd43451ff).into()), - ("punctuation".into(), rgba(0x292824ff).into()), - ("punctuation.delimiter".into(), rgba(0x6e6b5eff).into()), - ("tag".into(), rgba(0x6684dfff).into()), - ("link_text".into(), rgba(0xb65712ff).into()), - ("boolean".into(), rgba(0x61ac39ff).into()), - ("hint".into(), rgba(0xb37979ff).into()), - ("operator".into(), rgba(0x6e6b5eff).into()), - ("constant".into(), rgba(0x61ac39ff).into()), - ("function".into(), rgba(0x6583e1ff).into()), - ("text.literal".into(), rgba(0xb65712ff).into()), - ("string.special.symbol".into(), rgba(0x5fac38ff).into()), - ("attribute".into(), rgba(0x6684dfff).into()), - ("emphasis".into(), rgba(0x6684dfff).into()), - ("preproc".into(), rgba(0x20201dff).into()), - ("comment.doc".into(), rgba(0x6e6b5eff).into()), - ("punctuation.bracket".into(), rgba(0x6e6b5eff).into()), - ("string".into(), rgba(0x5fac38ff).into()), - ("enum".into(), rgba(0xb65712ff).into()), - ("variable".into(), rgba(0x292824ff).into()), - ("string.special".into(), rgba(0xd43451ff).into()), - ("embedded".into(), rgba(0x20201dff).into()), - ("emphasis.strong".into(), rgba(0x6684dfff).into()), - ("predictive".into(), rgba(0xc88a8aff).into()), - ("title".into(), rgba(0x20201dff).into()), - ("constructor".into(), rgba(0x6684dfff).into()), - ("link_uri".into(), rgba(0x61ac39ff).into()), - ("string.escape".into(), rgba(0x6e6b5eff).into()), - ], - }, - status_bar: rgba(0xcecab4ff).into(), - title_bar: rgba(0xcecab4ff).into(), - toolbar: rgba(0xfefbecff).into(), - tab_bar: rgba(0xeeebd7ff).into(), - editor: rgba(0xfefbecff).into(), - editor_subheader: rgba(0xeeebd7ff).into(), - editor_active_line: rgba(0xeeebd7ff).into(), - terminal: rgba(0xfefbecff).into(), - image_fallback_background: rgba(0xcecab4ff).into(), - git_created: rgba(0x61ac39ff).into(), - git_modified: rgba(0x6684dfff).into(), - git_deleted: rgba(0xd73737ff).into(), - git_conflict: rgba(0xae9414ff).into(), - git_ignored: rgba(0x878471ff).into(), - git_renamed: rgba(0xae9414ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x6684dfff).into(), - selection: rgba(0x6684df3d).into(), - }, - PlayerTheme { - cursor: rgba(0x61ac39ff).into(), - selection: rgba(0x61ac393d).into(), - }, - PlayerTheme { - cursor: rgba(0xd43652ff).into(), - selection: rgba(0xd436523d).into(), - }, - PlayerTheme { - cursor: rgba(0xb65712ff).into(), - selection: rgba(0xb657123d).into(), - }, - PlayerTheme { - cursor: rgba(0xb755d3ff).into(), - selection: rgba(0xb755d33d).into(), - }, - PlayerTheme { - cursor: rgba(0x21ad82ff).into(), - selection: rgba(0x21ad823d).into(), - }, - PlayerTheme { - cursor: rgba(0xd73737ff).into(), - selection: rgba(0xd737373d).into(), - }, - PlayerTheme { - cursor: rgba(0xae9414ff).into(), - selection: rgba(0xae94143d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_estuary_dark.rs b/crates/theme2/src/themes/atelier_estuary_dark.rs deleted file mode 100644 index ad5c9fbc1e..0000000000 --- a/crates/theme2/src/themes/atelier_estuary_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_estuary_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Estuary Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5d5c4cff).into(), - border_variant: rgba(0x5d5c4cff).into(), - border_focused: rgba(0x1c3927ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x424136ff).into(), - surface: rgba(0x2c2b23ff).into(), - background: rgba(0x424136ff).into(), - filled_element: rgba(0x424136ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x142319ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x142319ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf4f3ecff).into(), - text_muted: rgba(0x91907fff).into(), - text_placeholder: rgba(0xba6136ff).into(), - text_disabled: rgba(0x7d7c6aff).into(), - text_accent: rgba(0x36a165ff).into(), - icon_muted: rgba(0x91907fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special.symbol".into(), rgba(0x7c9725ff).into()), - ("comment".into(), rgba(0x6c6b5aff).into()), - ("operator".into(), rgba(0x929181ff).into()), - ("punctuation.delimiter".into(), rgba(0x929181ff).into()), - ("keyword".into(), rgba(0x5f9182ff).into()), - ("punctuation.special".into(), rgba(0x9d6b7bff).into()), - ("preproc".into(), rgba(0xf4f3ecff).into()), - ("title".into(), rgba(0xf4f3ecff).into()), - ("string.escape".into(), rgba(0x929181ff).into()), - ("boolean".into(), rgba(0x7d9726ff).into()), - ("punctuation.bracket".into(), rgba(0x929181ff).into()), - ("emphasis.strong".into(), rgba(0x36a165ff).into()), - ("string".into(), rgba(0x7c9725ff).into()), - ("constant".into(), rgba(0x7d9726ff).into()), - ("link_text".into(), rgba(0xae7214ff).into()), - ("tag".into(), rgba(0x36a165ff).into()), - ("hint".into(), rgba(0x6f815aff).into()), - ("punctuation".into(), rgba(0xe7e6dfff).into()), - ("string.regex".into(), rgba(0x5a9d47ff).into()), - ("variant".into(), rgba(0xa5980cff).into()), - ("type".into(), rgba(0xa5980cff).into()), - ("attribute".into(), rgba(0x36a165ff).into()), - ("emphasis".into(), rgba(0x36a165ff).into()), - ("enum".into(), rgba(0xae7214ff).into()), - ("number".into(), rgba(0xae7312ff).into()), - ("property".into(), rgba(0xba6135ff).into()), - ("predictive".into(), rgba(0x5f724cff).into()), - ( - "function.special.definition".into(), - rgba(0xa5980cff).into(), - ), - ("link_uri".into(), rgba(0x7d9726ff).into()), - ("variable.special".into(), rgba(0x5f9182ff).into()), - ("text.literal".into(), rgba(0xae7214ff).into()), - ("label".into(), rgba(0x36a165ff).into()), - ("primary".into(), rgba(0xe7e6dfff).into()), - ("variable".into(), rgba(0xe7e6dfff).into()), - ("embedded".into(), rgba(0xf4f3ecff).into()), - ("function.method".into(), rgba(0x35a166ff).into()), - ("comment.doc".into(), rgba(0x929181ff).into()), - ("string.special".into(), rgba(0x9d6b7bff).into()), - ("constructor".into(), rgba(0x36a165ff).into()), - ("punctuation.list_marker".into(), rgba(0xe7e6dfff).into()), - ("function".into(), rgba(0x35a166ff).into()), - ], - }, - status_bar: rgba(0x424136ff).into(), - title_bar: rgba(0x424136ff).into(), - toolbar: rgba(0x22221bff).into(), - tab_bar: rgba(0x2c2b23ff).into(), - editor: rgba(0x22221bff).into(), - editor_subheader: rgba(0x2c2b23ff).into(), - editor_active_line: rgba(0x2c2b23ff).into(), - terminal: rgba(0x22221bff).into(), - image_fallback_background: rgba(0x424136ff).into(), - git_created: rgba(0x7d9726ff).into(), - git_modified: rgba(0x36a165ff).into(), - git_deleted: rgba(0xba6136ff).into(), - git_conflict: rgba(0xa5980fff).into(), - git_ignored: rgba(0x7d7c6aff).into(), - git_renamed: rgba(0xa5980fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x36a165ff).into(), - selection: rgba(0x36a1653d).into(), - }, - PlayerTheme { - cursor: rgba(0x7d9726ff).into(), - selection: rgba(0x7d97263d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d6b7bff).into(), - selection: rgba(0x9d6b7b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xae7214ff).into(), - selection: rgba(0xae72143d).into(), - }, - PlayerTheme { - cursor: rgba(0x5f9182ff).into(), - selection: rgba(0x5f91823d).into(), - }, - PlayerTheme { - cursor: rgba(0x5a9d47ff).into(), - selection: rgba(0x5a9d473d).into(), - }, - PlayerTheme { - cursor: rgba(0xba6136ff).into(), - selection: rgba(0xba61363d).into(), - }, - PlayerTheme { - cursor: rgba(0xa5980fff).into(), - selection: rgba(0xa5980f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_estuary_light.rs b/crates/theme2/src/themes/atelier_estuary_light.rs deleted file mode 100644 index 91eaa88fab..0000000000 --- a/crates/theme2/src/themes/atelier_estuary_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_estuary_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Estuary Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x969585ff).into(), - border_variant: rgba(0x969585ff).into(), - border_focused: rgba(0xbbddc6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc5c4b9ff).into(), - surface: rgba(0xebeae3ff).into(), - background: rgba(0xc5c4b9ff).into(), - filled_element: rgba(0xc5c4b9ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd9ecdfff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd9ecdfff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x22221bff).into(), - text_muted: rgba(0x61604fff).into(), - text_placeholder: rgba(0xba6336ff).into(), - text_disabled: rgba(0x767463ff).into(), - text_accent: rgba(0x37a165ff).into(), - icon_muted: rgba(0x61604fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special".into(), rgba(0x9d6b7bff).into()), - ("link_text".into(), rgba(0xae7214ff).into()), - ("emphasis.strong".into(), rgba(0x37a165ff).into()), - ("tag".into(), rgba(0x37a165ff).into()), - ("primary".into(), rgba(0x302f27ff).into()), - ("emphasis".into(), rgba(0x37a165ff).into()), - ("hint".into(), rgba(0x758961ff).into()), - ("title".into(), rgba(0x22221bff).into()), - ("string.regex".into(), rgba(0x5a9d47ff).into()), - ("attribute".into(), rgba(0x37a165ff).into()), - ("string.escape".into(), rgba(0x5f5e4eff).into()), - ("embedded".into(), rgba(0x22221bff).into()), - ("punctuation.bracket".into(), rgba(0x5f5e4eff).into()), - ( - "function.special.definition".into(), - rgba(0xa5980cff).into(), - ), - ("operator".into(), rgba(0x5f5e4eff).into()), - ("constant".into(), rgba(0x7c9728ff).into()), - ("comment.doc".into(), rgba(0x5f5e4eff).into()), - ("label".into(), rgba(0x37a165ff).into()), - ("variable".into(), rgba(0x302f27ff).into()), - ("punctuation".into(), rgba(0x302f27ff).into()), - ("punctuation.delimiter".into(), rgba(0x5f5e4eff).into()), - ("comment".into(), rgba(0x878573ff).into()), - ("punctuation.special".into(), rgba(0x9d6b7bff).into()), - ("string.special.symbol".into(), rgba(0x7c9725ff).into()), - ("enum".into(), rgba(0xae7214ff).into()), - ("variable.special".into(), rgba(0x5f9182ff).into()), - ("link_uri".into(), rgba(0x7c9728ff).into()), - ("punctuation.list_marker".into(), rgba(0x302f27ff).into()), - ("number".into(), rgba(0xae7312ff).into()), - ("function".into(), rgba(0x35a166ff).into()), - ("text.literal".into(), rgba(0xae7214ff).into()), - ("boolean".into(), rgba(0x7c9728ff).into()), - ("predictive".into(), rgba(0x879a72ff).into()), - ("type".into(), rgba(0xa5980cff).into()), - ("constructor".into(), rgba(0x37a165ff).into()), - ("property".into(), rgba(0xba6135ff).into()), - ("keyword".into(), rgba(0x5f9182ff).into()), - ("function.method".into(), rgba(0x35a166ff).into()), - ("variant".into(), rgba(0xa5980cff).into()), - ("string".into(), rgba(0x7c9725ff).into()), - ("preproc".into(), rgba(0x22221bff).into()), - ], - }, - status_bar: rgba(0xc5c4b9ff).into(), - title_bar: rgba(0xc5c4b9ff).into(), - toolbar: rgba(0xf4f3ecff).into(), - tab_bar: rgba(0xebeae3ff).into(), - editor: rgba(0xf4f3ecff).into(), - editor_subheader: rgba(0xebeae3ff).into(), - editor_active_line: rgba(0xebeae3ff).into(), - terminal: rgba(0xf4f3ecff).into(), - image_fallback_background: rgba(0xc5c4b9ff).into(), - git_created: rgba(0x7c9728ff).into(), - git_modified: rgba(0x37a165ff).into(), - git_deleted: rgba(0xba6336ff).into(), - git_conflict: rgba(0xa5980fff).into(), - git_ignored: rgba(0x767463ff).into(), - git_renamed: rgba(0xa5980fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x37a165ff).into(), - selection: rgba(0x37a1653d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c9728ff).into(), - selection: rgba(0x7c97283d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d6b7bff).into(), - selection: rgba(0x9d6b7b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xae7214ff).into(), - selection: rgba(0xae72143d).into(), - }, - PlayerTheme { - cursor: rgba(0x5f9182ff).into(), - selection: rgba(0x5f91823d).into(), - }, - PlayerTheme { - cursor: rgba(0x5c9d49ff).into(), - selection: rgba(0x5c9d493d).into(), - }, - PlayerTheme { - cursor: rgba(0xba6336ff).into(), - selection: rgba(0xba63363d).into(), - }, - PlayerTheme { - cursor: rgba(0xa5980fff).into(), - selection: rgba(0xa5980f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_forest_dark.rs b/crates/theme2/src/themes/atelier_forest_dark.rs deleted file mode 100644 index 83228e671f..0000000000 --- a/crates/theme2/src/themes/atelier_forest_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_forest_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Forest Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x665f5cff).into(), - border_variant: rgba(0x665f5cff).into(), - border_focused: rgba(0x182d5bff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x443c39ff).into(), - surface: rgba(0x27211eff).into(), - background: rgba(0x443c39ff).into(), - filled_element: rgba(0x443c39ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0f1c3dff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0f1c3dff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf0eeedff).into(), - text_muted: rgba(0xa79f9dff).into(), - text_placeholder: rgba(0xf22c3fff).into(), - text_disabled: rgba(0x8e8683ff).into(), - text_accent: rgba(0x407ee6ff).into(), - icon_muted: rgba(0xa79f9dff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("link_uri".into(), rgba(0x7a9726ff).into()), - ("punctuation.list_marker".into(), rgba(0xe6e2e0ff).into()), - ("type".into(), rgba(0xc38417ff).into()), - ("punctuation.bracket".into(), rgba(0xa8a19fff).into()), - ("punctuation".into(), rgba(0xe6e2e0ff).into()), - ("preproc".into(), rgba(0xf0eeedff).into()), - ("punctuation.special".into(), rgba(0xc33ff3ff).into()), - ("variable.special".into(), rgba(0x6666eaff).into()), - ("tag".into(), rgba(0x407ee6ff).into()), - ("constructor".into(), rgba(0x407ee6ff).into()), - ("title".into(), rgba(0xf0eeedff).into()), - ("hint".into(), rgba(0xa77087ff).into()), - ("constant".into(), rgba(0x7a9726ff).into()), - ("number".into(), rgba(0xdf521fff).into()), - ("emphasis.strong".into(), rgba(0x407ee6ff).into()), - ("boolean".into(), rgba(0x7a9726ff).into()), - ("comment".into(), rgba(0x766e6bff).into()), - ("string.special".into(), rgba(0xc33ff3ff).into()), - ("text.literal".into(), rgba(0xdf5321ff).into()), - ("string.regex".into(), rgba(0x3c96b8ff).into()), - ("enum".into(), rgba(0xdf5321ff).into()), - ("operator".into(), rgba(0xa8a19fff).into()), - ("embedded".into(), rgba(0xf0eeedff).into()), - ("string.special.symbol".into(), rgba(0x7a9725ff).into()), - ("predictive".into(), rgba(0x8f5b70ff).into()), - ("comment.doc".into(), rgba(0xa8a19fff).into()), - ("variant".into(), rgba(0xc38417ff).into()), - ("label".into(), rgba(0x407ee6ff).into()), - ("property".into(), rgba(0xf22c40ff).into()), - ("keyword".into(), rgba(0x6666eaff).into()), - ("function".into(), rgba(0x3f7ee7ff).into()), - ("string.escape".into(), rgba(0xa8a19fff).into()), - ("string".into(), rgba(0x7a9725ff).into()), - ("primary".into(), rgba(0xe6e2e0ff).into()), - ("function.method".into(), rgba(0x3f7ee7ff).into()), - ("link_text".into(), rgba(0xdf5321ff).into()), - ("attribute".into(), rgba(0x407ee6ff).into()), - ("emphasis".into(), rgba(0x407ee6ff).into()), - ( - "function.special.definition".into(), - rgba(0xc38417ff).into(), - ), - ("variable".into(), rgba(0xe6e2e0ff).into()), - ("punctuation.delimiter".into(), rgba(0xa8a19fff).into()), - ], - }, - status_bar: rgba(0x443c39ff).into(), - title_bar: rgba(0x443c39ff).into(), - toolbar: rgba(0x1b1918ff).into(), - tab_bar: rgba(0x27211eff).into(), - editor: rgba(0x1b1918ff).into(), - editor_subheader: rgba(0x27211eff).into(), - editor_active_line: rgba(0x27211eff).into(), - terminal: rgba(0x1b1918ff).into(), - image_fallback_background: rgba(0x443c39ff).into(), - git_created: rgba(0x7a9726ff).into(), - git_modified: rgba(0x407ee6ff).into(), - git_deleted: rgba(0xf22c3fff).into(), - git_conflict: rgba(0xc38418ff).into(), - git_ignored: rgba(0x8e8683ff).into(), - git_renamed: rgba(0xc38418ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x407ee6ff).into(), - selection: rgba(0x407ee63d).into(), - }, - PlayerTheme { - cursor: rgba(0x7a9726ff).into(), - selection: rgba(0x7a97263d).into(), - }, - PlayerTheme { - cursor: rgba(0xc340f2ff).into(), - selection: rgba(0xc340f23d).into(), - }, - PlayerTheme { - cursor: rgba(0xdf5321ff).into(), - selection: rgba(0xdf53213d).into(), - }, - PlayerTheme { - cursor: rgba(0x6565e9ff).into(), - selection: rgba(0x6565e93d).into(), - }, - PlayerTheme { - cursor: rgba(0x3d97b8ff).into(), - selection: rgba(0x3d97b83d).into(), - }, - PlayerTheme { - cursor: rgba(0xf22c3fff).into(), - selection: rgba(0xf22c3f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xc38418ff).into(), - selection: rgba(0xc384183d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_forest_light.rs b/crates/theme2/src/themes/atelier_forest_light.rs deleted file mode 100644 index 882d5c2fcb..0000000000 --- a/crates/theme2/src/themes/atelier_forest_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_forest_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Forest Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xaaa3a1ff).into(), - border_variant: rgba(0xaaa3a1ff).into(), - border_focused: rgba(0xc6cef7ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xccc7c5ff).into(), - surface: rgba(0xe9e6e4ff).into(), - background: rgba(0xccc7c5ff).into(), - filled_element: rgba(0xccc7c5ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdfe3fbff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdfe3fbff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x1b1918ff).into(), - text_muted: rgba(0x6a6360ff).into(), - text_placeholder: rgba(0xf22e40ff).into(), - text_disabled: rgba(0x837b78ff).into(), - text_accent: rgba(0x407ee6ff).into(), - icon_muted: rgba(0x6a6360ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.special".into(), rgba(0xc33ff3ff).into()), - ("text.literal".into(), rgba(0xdf5421ff).into()), - ("string.escape".into(), rgba(0x68615eff).into()), - ("string.regex".into(), rgba(0x3c96b8ff).into()), - ("number".into(), rgba(0xdf521fff).into()), - ("preproc".into(), rgba(0x1b1918ff).into()), - ("keyword".into(), rgba(0x6666eaff).into()), - ("variable.special".into(), rgba(0x6666eaff).into()), - ("punctuation.delimiter".into(), rgba(0x68615eff).into()), - ("emphasis.strong".into(), rgba(0x407ee6ff).into()), - ("boolean".into(), rgba(0x7a9728ff).into()), - ("variant".into(), rgba(0xc38417ff).into()), - ("predictive".into(), rgba(0xbe899eff).into()), - ("tag".into(), rgba(0x407ee6ff).into()), - ("property".into(), rgba(0xf22c40ff).into()), - ("enum".into(), rgba(0xdf5421ff).into()), - ("attribute".into(), rgba(0x407ee6ff).into()), - ("function.method".into(), rgba(0x3f7ee7ff).into()), - ("function".into(), rgba(0x3f7ee7ff).into()), - ("emphasis".into(), rgba(0x407ee6ff).into()), - ("primary".into(), rgba(0x2c2421ff).into()), - ("variable".into(), rgba(0x2c2421ff).into()), - ("constant".into(), rgba(0x7a9728ff).into()), - ("title".into(), rgba(0x1b1918ff).into()), - ("comment.doc".into(), rgba(0x68615eff).into()), - ("constructor".into(), rgba(0x407ee6ff).into()), - ("type".into(), rgba(0xc38417ff).into()), - ("punctuation.list_marker".into(), rgba(0x2c2421ff).into()), - ("punctuation".into(), rgba(0x2c2421ff).into()), - ("string".into(), rgba(0x7a9725ff).into()), - ("label".into(), rgba(0x407ee6ff).into()), - ("string.special".into(), rgba(0xc33ff3ff).into()), - ("embedded".into(), rgba(0x1b1918ff).into()), - ("link_text".into(), rgba(0xdf5421ff).into()), - ("punctuation.bracket".into(), rgba(0x68615eff).into()), - ("comment".into(), rgba(0x9c9491ff).into()), - ( - "function.special.definition".into(), - rgba(0xc38417ff).into(), - ), - ("link_uri".into(), rgba(0x7a9728ff).into()), - ("operator".into(), rgba(0x68615eff).into()), - ("hint".into(), rgba(0xa67287ff).into()), - ("string.special.symbol".into(), rgba(0x7a9725ff).into()), - ], - }, - status_bar: rgba(0xccc7c5ff).into(), - title_bar: rgba(0xccc7c5ff).into(), - toolbar: rgba(0xf0eeedff).into(), - tab_bar: rgba(0xe9e6e4ff).into(), - editor: rgba(0xf0eeedff).into(), - editor_subheader: rgba(0xe9e6e4ff).into(), - editor_active_line: rgba(0xe9e6e4ff).into(), - terminal: rgba(0xf0eeedff).into(), - image_fallback_background: rgba(0xccc7c5ff).into(), - git_created: rgba(0x7a9728ff).into(), - git_modified: rgba(0x407ee6ff).into(), - git_deleted: rgba(0xf22e40ff).into(), - git_conflict: rgba(0xc38419ff).into(), - git_ignored: rgba(0x837b78ff).into(), - git_renamed: rgba(0xc38419ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x407ee6ff).into(), - selection: rgba(0x407ee63d).into(), - }, - PlayerTheme { - cursor: rgba(0x7a9728ff).into(), - selection: rgba(0x7a97283d).into(), - }, - PlayerTheme { - cursor: rgba(0xc340f2ff).into(), - selection: rgba(0xc340f23d).into(), - }, - PlayerTheme { - cursor: rgba(0xdf5421ff).into(), - selection: rgba(0xdf54213d).into(), - }, - PlayerTheme { - cursor: rgba(0x6765e9ff).into(), - selection: rgba(0x6765e93d).into(), - }, - PlayerTheme { - cursor: rgba(0x3e96b8ff).into(), - selection: rgba(0x3e96b83d).into(), - }, - PlayerTheme { - cursor: rgba(0xf22e40ff).into(), - selection: rgba(0xf22e403d).into(), - }, - PlayerTheme { - cursor: rgba(0xc38419ff).into(), - selection: rgba(0xc384193d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_heath_dark.rs b/crates/theme2/src/themes/atelier_heath_dark.rs deleted file mode 100644 index 354c98069f..0000000000 --- a/crates/theme2/src/themes/atelier_heath_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_heath_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Heath Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x675b67ff).into(), - border_variant: rgba(0x675b67ff).into(), - border_focused: rgba(0x192961ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x433a43ff).into(), - surface: rgba(0x252025ff).into(), - background: rgba(0x433a43ff).into(), - filled_element: rgba(0x433a43ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0d1a43ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0d1a43ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf7f3f7ff).into(), - text_muted: rgba(0xa899a8ff).into(), - text_placeholder: rgba(0xca3f2bff).into(), - text_disabled: rgba(0x908190ff).into(), - text_accent: rgba(0x5169ebff).into(), - icon_muted: rgba(0xa899a8ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("preproc".into(), rgba(0xf7f3f7ff).into()), - ("number".into(), rgba(0xa65825ff).into()), - ("boolean".into(), rgba(0x918b3aff).into()), - ("embedded".into(), rgba(0xf7f3f7ff).into()), - ("variable.special".into(), rgba(0x7b58bfff).into()), - ("operator".into(), rgba(0xab9babff).into()), - ("punctuation.delimiter".into(), rgba(0xab9babff).into()), - ("primary".into(), rgba(0xd8cad8ff).into()), - ("punctuation.bracket".into(), rgba(0xab9babff).into()), - ("comment.doc".into(), rgba(0xab9babff).into()), - ("variant".into(), rgba(0xbb8a34ff).into()), - ("attribute".into(), rgba(0x5169ebff).into()), - ("property".into(), rgba(0xca3f2aff).into()), - ("keyword".into(), rgba(0x7b58bfff).into()), - ("hint".into(), rgba(0x8d70a8ff).into()), - ("string.special.symbol".into(), rgba(0x918b3aff).into()), - ("punctuation.special".into(), rgba(0xcc32ccff).into()), - ("link_uri".into(), rgba(0x918b3aff).into()), - ("link_text".into(), rgba(0xa65827ff).into()), - ("enum".into(), rgba(0xa65827ff).into()), - ("function".into(), rgba(0x506aecff).into()), - ( - "function.special.definition".into(), - rgba(0xbb8a34ff).into(), - ), - ("constant".into(), rgba(0x918b3aff).into()), - ("title".into(), rgba(0xf7f3f7ff).into()), - ("string.regex".into(), rgba(0x149393ff).into()), - ("variable".into(), rgba(0xd8cad8ff).into()), - ("comment".into(), rgba(0x776977ff).into()), - ("predictive".into(), rgba(0x75588fff).into()), - ("function.method".into(), rgba(0x506aecff).into()), - ("type".into(), rgba(0xbb8a34ff).into()), - ("punctuation".into(), rgba(0xd8cad8ff).into()), - ("emphasis".into(), rgba(0x5169ebff).into()), - ("emphasis.strong".into(), rgba(0x5169ebff).into()), - ("tag".into(), rgba(0x5169ebff).into()), - ("text.literal".into(), rgba(0xa65827ff).into()), - ("string".into(), rgba(0x918b3aff).into()), - ("string.escape".into(), rgba(0xab9babff).into()), - ("constructor".into(), rgba(0x5169ebff).into()), - ("label".into(), rgba(0x5169ebff).into()), - ("punctuation.list_marker".into(), rgba(0xd8cad8ff).into()), - ("string.special".into(), rgba(0xcc32ccff).into()), - ], - }, - status_bar: rgba(0x433a43ff).into(), - title_bar: rgba(0x433a43ff).into(), - toolbar: rgba(0x1b181bff).into(), - tab_bar: rgba(0x252025ff).into(), - editor: rgba(0x1b181bff).into(), - editor_subheader: rgba(0x252025ff).into(), - editor_active_line: rgba(0x252025ff).into(), - terminal: rgba(0x1b181bff).into(), - image_fallback_background: rgba(0x433a43ff).into(), - git_created: rgba(0x918b3aff).into(), - git_modified: rgba(0x5169ebff).into(), - git_deleted: rgba(0xca3f2bff).into(), - git_conflict: rgba(0xbb8a35ff).into(), - git_ignored: rgba(0x908190ff).into(), - git_renamed: rgba(0xbb8a35ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5169ebff).into(), - selection: rgba(0x5169eb3d).into(), - }, - PlayerTheme { - cursor: rgba(0x918b3aff).into(), - selection: rgba(0x918b3a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xcc34ccff).into(), - selection: rgba(0xcc34cc3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa65827ff).into(), - selection: rgba(0xa658273d).into(), - }, - PlayerTheme { - cursor: rgba(0x7b58bfff).into(), - selection: rgba(0x7b58bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0x189393ff).into(), - selection: rgba(0x1893933d).into(), - }, - PlayerTheme { - cursor: rgba(0xca3f2bff).into(), - selection: rgba(0xca3f2b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbb8a35ff).into(), - selection: rgba(0xbb8a353d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_heath_light.rs b/crates/theme2/src/themes/atelier_heath_light.rs deleted file mode 100644 index f1a9e4d8c6..0000000000 --- a/crates/theme2/src/themes/atelier_heath_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_heath_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Heath Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xad9dadff).into(), - border_variant: rgba(0xad9dadff).into(), - border_focused: rgba(0xcac7faff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc6b8c6ff).into(), - surface: rgba(0xe0d5e0ff).into(), - background: rgba(0xc6b8c6ff).into(), - filled_element: rgba(0xc6b8c6ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe2dffcff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe2dffcff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x1b181bff).into(), - text_muted: rgba(0x6b5e6bff).into(), - text_placeholder: rgba(0xca402bff).into(), - text_disabled: rgba(0x857785ff).into(), - text_accent: rgba(0x5169ebff).into(), - icon_muted: rgba(0x6b5e6bff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("enum".into(), rgba(0xa65927ff).into()), - ("string.escape".into(), rgba(0x695d69ff).into()), - ("link_uri".into(), rgba(0x918b3bff).into()), - ("function.method".into(), rgba(0x506aecff).into()), - ("comment.doc".into(), rgba(0x695d69ff).into()), - ("property".into(), rgba(0xca3f2aff).into()), - ("string.special".into(), rgba(0xcc32ccff).into()), - ("tag".into(), rgba(0x5169ebff).into()), - ("embedded".into(), rgba(0x1b181bff).into()), - ("primary".into(), rgba(0x292329ff).into()), - ("punctuation".into(), rgba(0x292329ff).into()), - ("punctuation.special".into(), rgba(0xcc32ccff).into()), - ("type".into(), rgba(0xbb8a34ff).into()), - ("number".into(), rgba(0xa65825ff).into()), - ("function".into(), rgba(0x506aecff).into()), - ("preproc".into(), rgba(0x1b181bff).into()), - ("punctuation.bracket".into(), rgba(0x695d69ff).into()), - ("punctuation.delimiter".into(), rgba(0x695d69ff).into()), - ("variable".into(), rgba(0x292329ff).into()), - ( - "function.special.definition".into(), - rgba(0xbb8a34ff).into(), - ), - ("label".into(), rgba(0x5169ebff).into()), - ("constructor".into(), rgba(0x5169ebff).into()), - ("emphasis.strong".into(), rgba(0x5169ebff).into()), - ("constant".into(), rgba(0x918b3bff).into()), - ("keyword".into(), rgba(0x7b58bfff).into()), - ("variable.special".into(), rgba(0x7b58bfff).into()), - ("variant".into(), rgba(0xbb8a34ff).into()), - ("title".into(), rgba(0x1b181bff).into()), - ("attribute".into(), rgba(0x5169ebff).into()), - ("comment".into(), rgba(0x9e8f9eff).into()), - ("string.special.symbol".into(), rgba(0x918b3aff).into()), - ("predictive".into(), rgba(0xa487bfff).into()), - ("link_text".into(), rgba(0xa65927ff).into()), - ("punctuation.list_marker".into(), rgba(0x292329ff).into()), - ("boolean".into(), rgba(0x918b3bff).into()), - ("text.literal".into(), rgba(0xa65927ff).into()), - ("emphasis".into(), rgba(0x5169ebff).into()), - ("string.regex".into(), rgba(0x149393ff).into()), - ("hint".into(), rgba(0x8c70a6ff).into()), - ("string".into(), rgba(0x918b3aff).into()), - ("operator".into(), rgba(0x695d69ff).into()), - ], - }, - status_bar: rgba(0xc6b8c6ff).into(), - title_bar: rgba(0xc6b8c6ff).into(), - toolbar: rgba(0xf7f3f7ff).into(), - tab_bar: rgba(0xe0d5e0ff).into(), - editor: rgba(0xf7f3f7ff).into(), - editor_subheader: rgba(0xe0d5e0ff).into(), - editor_active_line: rgba(0xe0d5e0ff).into(), - terminal: rgba(0xf7f3f7ff).into(), - image_fallback_background: rgba(0xc6b8c6ff).into(), - git_created: rgba(0x918b3bff).into(), - git_modified: rgba(0x5169ebff).into(), - git_deleted: rgba(0xca402bff).into(), - git_conflict: rgba(0xbb8a35ff).into(), - git_ignored: rgba(0x857785ff).into(), - git_renamed: rgba(0xbb8a35ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5169ebff).into(), - selection: rgba(0x5169eb3d).into(), - }, - PlayerTheme { - cursor: rgba(0x918b3bff).into(), - selection: rgba(0x918b3b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xcc34ccff).into(), - selection: rgba(0xcc34cc3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa65927ff).into(), - selection: rgba(0xa659273d).into(), - }, - PlayerTheme { - cursor: rgba(0x7a5ac0ff).into(), - selection: rgba(0x7a5ac03d).into(), - }, - PlayerTheme { - cursor: rgba(0x189393ff).into(), - selection: rgba(0x1893933d).into(), - }, - PlayerTheme { - cursor: rgba(0xca402bff).into(), - selection: rgba(0xca402b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbb8a35ff).into(), - selection: rgba(0xbb8a353d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_lakeside_dark.rs b/crates/theme2/src/themes/atelier_lakeside_dark.rs deleted file mode 100644 index 61b78864b7..0000000000 --- a/crates/theme2/src/themes/atelier_lakeside_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_lakeside_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Lakeside Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x4f6a78ff).into(), - border_variant: rgba(0x4f6a78ff).into(), - border_focused: rgba(0x1a2f3cff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x33444dff).into(), - surface: rgba(0x1c2529ff).into(), - background: rgba(0x33444dff).into(), - filled_element: rgba(0x33444dff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x121c24ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x121c24ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xebf8ffff).into(), - text_muted: rgba(0x7c9fb3ff).into(), - text_placeholder: rgba(0xd22e72ff).into(), - text_disabled: rgba(0x688c9dff).into(), - text_accent: rgba(0x267eadff).into(), - icon_muted: rgba(0x7c9fb3ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.bracket".into(), rgba(0x7ea2b4ff).into()), - ("punctuation.special".into(), rgba(0xb72cd2ff).into()), - ("property".into(), rgba(0xd22c72ff).into()), - ("function.method".into(), rgba(0x247eadff).into()), - ("comment".into(), rgba(0x5a7b8cff).into()), - ("constructor".into(), rgba(0x267eadff).into()), - ("boolean".into(), rgba(0x558c3aff).into()), - ("hint".into(), rgba(0x52809aff).into()), - ("label".into(), rgba(0x267eadff).into()), - ("string.special".into(), rgba(0xb72cd2ff).into()), - ("title".into(), rgba(0xebf8ffff).into()), - ("punctuation.list_marker".into(), rgba(0xc1e4f6ff).into()), - ("emphasis.strong".into(), rgba(0x267eadff).into()), - ("enum".into(), rgba(0x935b25ff).into()), - ("type".into(), rgba(0x8a8a0eff).into()), - ("tag".into(), rgba(0x267eadff).into()), - ("punctuation.delimiter".into(), rgba(0x7ea2b4ff).into()), - ("primary".into(), rgba(0xc1e4f6ff).into()), - ("link_text".into(), rgba(0x935b25ff).into()), - ("variable".into(), rgba(0xc1e4f6ff).into()), - ("variable.special".into(), rgba(0x6a6ab7ff).into()), - ("string.special.symbol".into(), rgba(0x558c3aff).into()), - ("link_uri".into(), rgba(0x558c3aff).into()), - ("function".into(), rgba(0x247eadff).into()), - ("predictive".into(), rgba(0x426f88ff).into()), - ("punctuation".into(), rgba(0xc1e4f6ff).into()), - ("string.escape".into(), rgba(0x7ea2b4ff).into()), - ("keyword".into(), rgba(0x6a6ab7ff).into()), - ("attribute".into(), rgba(0x267eadff).into()), - ("string.regex".into(), rgba(0x2c8f6eff).into()), - ("embedded".into(), rgba(0xebf8ffff).into()), - ("emphasis".into(), rgba(0x267eadff).into()), - ("string".into(), rgba(0x558c3aff).into()), - ("operator".into(), rgba(0x7ea2b4ff).into()), - ("text.literal".into(), rgba(0x935b25ff).into()), - ("constant".into(), rgba(0x558c3aff).into()), - ("comment.doc".into(), rgba(0x7ea2b4ff).into()), - ("number".into(), rgba(0x935c24ff).into()), - ("preproc".into(), rgba(0xebf8ffff).into()), - ( - "function.special.definition".into(), - rgba(0x8a8a0eff).into(), - ), - ("variant".into(), rgba(0x8a8a0eff).into()), - ], - }, - status_bar: rgba(0x33444dff).into(), - title_bar: rgba(0x33444dff).into(), - toolbar: rgba(0x161b1dff).into(), - tab_bar: rgba(0x1c2529ff).into(), - editor: rgba(0x161b1dff).into(), - editor_subheader: rgba(0x1c2529ff).into(), - editor_active_line: rgba(0x1c2529ff).into(), - terminal: rgba(0x161b1dff).into(), - image_fallback_background: rgba(0x33444dff).into(), - git_created: rgba(0x558c3aff).into(), - git_modified: rgba(0x267eadff).into(), - git_deleted: rgba(0xd22e72ff).into(), - git_conflict: rgba(0x8a8a10ff).into(), - git_ignored: rgba(0x688c9dff).into(), - git_renamed: rgba(0x8a8a10ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x267eadff).into(), - selection: rgba(0x267ead3d).into(), - }, - PlayerTheme { - cursor: rgba(0x558c3aff).into(), - selection: rgba(0x558c3a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xb72ed2ff).into(), - selection: rgba(0xb72ed23d).into(), - }, - PlayerTheme { - cursor: rgba(0x935b25ff).into(), - selection: rgba(0x935b253d).into(), - }, - PlayerTheme { - cursor: rgba(0x6a6ab7ff).into(), - selection: rgba(0x6a6ab73d).into(), - }, - PlayerTheme { - cursor: rgba(0x2d8f6fff).into(), - selection: rgba(0x2d8f6f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd22e72ff).into(), - selection: rgba(0xd22e723d).into(), - }, - PlayerTheme { - cursor: rgba(0x8a8a10ff).into(), - selection: rgba(0x8a8a103d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_lakeside_light.rs b/crates/theme2/src/themes/atelier_lakeside_light.rs deleted file mode 100644 index 64fb70dadb..0000000000 --- a/crates/theme2/src/themes/atelier_lakeside_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_lakeside_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Lakeside Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x80a4b6ff).into(), - border_variant: rgba(0x80a4b6ff).into(), - border_focused: rgba(0xb9cee0ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xa6cadcff).into(), - surface: rgba(0xcdeaf9ff).into(), - background: rgba(0xa6cadcff).into(), - filled_element: rgba(0xa6cadcff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd8e4eeff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd8e4eeff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x161b1dff).into(), - text_muted: rgba(0x526f7dff).into(), - text_placeholder: rgba(0xd22e71ff).into(), - text_disabled: rgba(0x628496ff).into(), - text_accent: rgba(0x267eadff).into(), - icon_muted: rgba(0x526f7dff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("emphasis".into(), rgba(0x267eadff).into()), - ("number".into(), rgba(0x935c24ff).into()), - ("embedded".into(), rgba(0x161b1dff).into()), - ("link_text".into(), rgba(0x935c25ff).into()), - ("string".into(), rgba(0x558c3aff).into()), - ("constructor".into(), rgba(0x267eadff).into()), - ("punctuation.list_marker".into(), rgba(0x1f292eff).into()), - ("string.special".into(), rgba(0xb72cd2ff).into()), - ("title".into(), rgba(0x161b1dff).into()), - ("variant".into(), rgba(0x8a8a0eff).into()), - ("tag".into(), rgba(0x267eadff).into()), - ("attribute".into(), rgba(0x267eadff).into()), - ("keyword".into(), rgba(0x6a6ab7ff).into()), - ("enum".into(), rgba(0x935c25ff).into()), - ("function".into(), rgba(0x247eadff).into()), - ("string.escape".into(), rgba(0x516d7bff).into()), - ("operator".into(), rgba(0x516d7bff).into()), - ("function.method".into(), rgba(0x247eadff).into()), - ( - "function.special.definition".into(), - rgba(0x8a8a0eff).into(), - ), - ("punctuation.delimiter".into(), rgba(0x516d7bff).into()), - ("comment".into(), rgba(0x7094a7ff).into()), - ("primary".into(), rgba(0x1f292eff).into()), - ("punctuation.bracket".into(), rgba(0x516d7bff).into()), - ("variable".into(), rgba(0x1f292eff).into()), - ("emphasis.strong".into(), rgba(0x267eadff).into()), - ("predictive".into(), rgba(0x6a97b2ff).into()), - ("punctuation.special".into(), rgba(0xb72cd2ff).into()), - ("hint".into(), rgba(0x5a87a0ff).into()), - ("text.literal".into(), rgba(0x935c25ff).into()), - ("string.special.symbol".into(), rgba(0x558c3aff).into()), - ("comment.doc".into(), rgba(0x516d7bff).into()), - ("constant".into(), rgba(0x568c3bff).into()), - ("boolean".into(), rgba(0x568c3bff).into()), - ("preproc".into(), rgba(0x161b1dff).into()), - ("variable.special".into(), rgba(0x6a6ab7ff).into()), - ("link_uri".into(), rgba(0x568c3bff).into()), - ("string.regex".into(), rgba(0x2c8f6eff).into()), - ("punctuation".into(), rgba(0x1f292eff).into()), - ("property".into(), rgba(0xd22c72ff).into()), - ("label".into(), rgba(0x267eadff).into()), - ("type".into(), rgba(0x8a8a0eff).into()), - ], - }, - status_bar: rgba(0xa6cadcff).into(), - title_bar: rgba(0xa6cadcff).into(), - toolbar: rgba(0xebf8ffff).into(), - tab_bar: rgba(0xcdeaf9ff).into(), - editor: rgba(0xebf8ffff).into(), - editor_subheader: rgba(0xcdeaf9ff).into(), - editor_active_line: rgba(0xcdeaf9ff).into(), - terminal: rgba(0xebf8ffff).into(), - image_fallback_background: rgba(0xa6cadcff).into(), - git_created: rgba(0x568c3bff).into(), - git_modified: rgba(0x267eadff).into(), - git_deleted: rgba(0xd22e71ff).into(), - git_conflict: rgba(0x8a8a10ff).into(), - git_ignored: rgba(0x628496ff).into(), - git_renamed: rgba(0x8a8a10ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x267eadff).into(), - selection: rgba(0x267ead3d).into(), - }, - PlayerTheme { - cursor: rgba(0x568c3bff).into(), - selection: rgba(0x568c3b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xb72ed2ff).into(), - selection: rgba(0xb72ed23d).into(), - }, - PlayerTheme { - cursor: rgba(0x935c25ff).into(), - selection: rgba(0x935c253d).into(), - }, - PlayerTheme { - cursor: rgba(0x6c6ab7ff).into(), - selection: rgba(0x6c6ab73d).into(), - }, - PlayerTheme { - cursor: rgba(0x2e8f6eff).into(), - selection: rgba(0x2e8f6e3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd22e71ff).into(), - selection: rgba(0xd22e713d).into(), - }, - PlayerTheme { - cursor: rgba(0x8a8a10ff).into(), - selection: rgba(0x8a8a103d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_plateau_dark.rs b/crates/theme2/src/themes/atelier_plateau_dark.rs deleted file mode 100644 index 0ba5a1659d..0000000000 --- a/crates/theme2/src/themes/atelier_plateau_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_plateau_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Plateau Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x564e4eff).into(), - border_variant: rgba(0x564e4eff).into(), - border_focused: rgba(0x2c2b45ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3b3535ff).into(), - surface: rgba(0x252020ff).into(), - background: rgba(0x3b3535ff).into(), - filled_element: rgba(0x3b3535ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1c1b29ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1c1b29ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf4ececff).into(), - text_muted: rgba(0x898383ff).into(), - text_placeholder: rgba(0xca4848ff).into(), - text_disabled: rgba(0x756e6eff).into(), - text_accent: rgba(0x7272caff).into(), - icon_muted: rgba(0x898383ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("variant".into(), rgba(0xa06d3aff).into()), - ("label".into(), rgba(0x7272caff).into()), - ("punctuation.delimiter".into(), rgba(0x8a8585ff).into()), - ("string.regex".into(), rgba(0x5485b6ff).into()), - ("variable.special".into(), rgba(0x8464c4ff).into()), - ("string".into(), rgba(0x4b8b8bff).into()), - ("property".into(), rgba(0xca4848ff).into()), - ("hint".into(), rgba(0x8a647aff).into()), - ("comment.doc".into(), rgba(0x8a8585ff).into()), - ("attribute".into(), rgba(0x7272caff).into()), - ("tag".into(), rgba(0x7272caff).into()), - ("constructor".into(), rgba(0x7272caff).into()), - ("boolean".into(), rgba(0x4b8b8bff).into()), - ("preproc".into(), rgba(0xf4ececff).into()), - ("constant".into(), rgba(0x4b8b8bff).into()), - ("punctuation.special".into(), rgba(0xbd5187ff).into()), - ("function.method".into(), rgba(0x7272caff).into()), - ("comment".into(), rgba(0x655d5dff).into()), - ("variable".into(), rgba(0xe7dfdfff).into()), - ("primary".into(), rgba(0xe7dfdfff).into()), - ("title".into(), rgba(0xf4ececff).into()), - ("emphasis".into(), rgba(0x7272caff).into()), - ("emphasis.strong".into(), rgba(0x7272caff).into()), - ("function".into(), rgba(0x7272caff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("operator".into(), rgba(0x8a8585ff).into()), - ("embedded".into(), rgba(0xf4ececff).into()), - ("predictive".into(), rgba(0x795369ff).into()), - ("punctuation".into(), rgba(0xe7dfdfff).into()), - ("link_text".into(), rgba(0xb4593bff).into()), - ("enum".into(), rgba(0xb4593bff).into()), - ("string.special".into(), rgba(0xbd5187ff).into()), - ("text.literal".into(), rgba(0xb4593bff).into()), - ("string.escape".into(), rgba(0x8a8585ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("keyword".into(), rgba(0x8464c4ff).into()), - ("link_uri".into(), rgba(0x4b8b8bff).into()), - ("number".into(), rgba(0xb4593bff).into()), - ("punctuation.bracket".into(), rgba(0x8a8585ff).into()), - ("string.special.symbol".into(), rgba(0x4b8b8bff).into()), - ("punctuation.list_marker".into(), rgba(0xe7dfdfff).into()), - ], - }, - status_bar: rgba(0x3b3535ff).into(), - title_bar: rgba(0x3b3535ff).into(), - toolbar: rgba(0x1b1818ff).into(), - tab_bar: rgba(0x252020ff).into(), - editor: rgba(0x1b1818ff).into(), - editor_subheader: rgba(0x252020ff).into(), - editor_active_line: rgba(0x252020ff).into(), - terminal: rgba(0x1b1818ff).into(), - image_fallback_background: rgba(0x3b3535ff).into(), - git_created: rgba(0x4b8b8bff).into(), - git_modified: rgba(0x7272caff).into(), - git_deleted: rgba(0xca4848ff).into(), - git_conflict: rgba(0xa06d3aff).into(), - git_ignored: rgba(0x756e6eff).into(), - git_renamed: rgba(0xa06d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x7272caff).into(), - selection: rgba(0x7272ca3d).into(), - }, - PlayerTheme { - cursor: rgba(0x4b8b8bff).into(), - selection: rgba(0x4b8b8b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbd5187ff).into(), - selection: rgba(0xbd51873d).into(), - }, - PlayerTheme { - cursor: rgba(0xb4593bff).into(), - selection: rgba(0xb4593b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8464c4ff).into(), - selection: rgba(0x8464c43d).into(), - }, - PlayerTheme { - cursor: rgba(0x5485b6ff).into(), - selection: rgba(0x5485b63d).into(), - }, - PlayerTheme { - cursor: rgba(0xca4848ff).into(), - selection: rgba(0xca48483d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06d3aff).into(), - selection: rgba(0xa06d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_plateau_light.rs b/crates/theme2/src/themes/atelier_plateau_light.rs deleted file mode 100644 index 68f100dd85..0000000000 --- a/crates/theme2/src/themes/atelier_plateau_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_plateau_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Plateau Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8e8989ff).into(), - border_variant: rgba(0x8e8989ff).into(), - border_focused: rgba(0xcecaecff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc1bbbbff).into(), - surface: rgba(0xebe3e3ff).into(), - background: rgba(0xc1bbbbff).into(), - filled_element: rgba(0xc1bbbbff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe4e1f5ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe4e1f5ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x1b1818ff).into(), - text_muted: rgba(0x5a5252ff).into(), - text_placeholder: rgba(0xca4a4aff).into(), - text_disabled: rgba(0x6e6666ff).into(), - text_accent: rgba(0x7272caff).into(), - icon_muted: rgba(0x5a5252ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("text.literal".into(), rgba(0xb45a3cff).into()), - ("punctuation.special".into(), rgba(0xbd5187ff).into()), - ("variant".into(), rgba(0xa06d3aff).into()), - ("punctuation".into(), rgba(0x292424ff).into()), - ("string.escape".into(), rgba(0x585050ff).into()), - ("emphasis".into(), rgba(0x7272caff).into()), - ("title".into(), rgba(0x1b1818ff).into()), - ("constructor".into(), rgba(0x7272caff).into()), - ("variable".into(), rgba(0x292424ff).into()), - ("predictive".into(), rgba(0xa27a91ff).into()), - ("label".into(), rgba(0x7272caff).into()), - ("function.method".into(), rgba(0x7272caff).into()), - ("link_uri".into(), rgba(0x4c8b8bff).into()), - ("punctuation.delimiter".into(), rgba(0x585050ff).into()), - ("link_text".into(), rgba(0xb45a3cff).into()), - ("hint".into(), rgba(0x91697fff).into()), - ("emphasis.strong".into(), rgba(0x7272caff).into()), - ("attribute".into(), rgba(0x7272caff).into()), - ("boolean".into(), rgba(0x4c8b8bff).into()), - ("string.special.symbol".into(), rgba(0x4b8b8bff).into()), - ("string".into(), rgba(0x4b8b8bff).into()), - ("type".into(), rgba(0xa06d3aff).into()), - ("string.regex".into(), rgba(0x5485b6ff).into()), - ("comment.doc".into(), rgba(0x585050ff).into()), - ("string.special".into(), rgba(0xbd5187ff).into()), - ("property".into(), rgba(0xca4848ff).into()), - ("preproc".into(), rgba(0x1b1818ff).into()), - ("embedded".into(), rgba(0x1b1818ff).into()), - ("comment".into(), rgba(0x7e7777ff).into()), - ("primary".into(), rgba(0x292424ff).into()), - ("number".into(), rgba(0xb4593bff).into()), - ("function".into(), rgba(0x7272caff).into()), - ("punctuation.bracket".into(), rgba(0x585050ff).into()), - ("tag".into(), rgba(0x7272caff).into()), - ("punctuation.list_marker".into(), rgba(0x292424ff).into()), - ( - "function.special.definition".into(), - rgba(0xa06d3aff).into(), - ), - ("enum".into(), rgba(0xb45a3cff).into()), - ("keyword".into(), rgba(0x8464c4ff).into()), - ("operator".into(), rgba(0x585050ff).into()), - ("variable.special".into(), rgba(0x8464c4ff).into()), - ("constant".into(), rgba(0x4c8b8bff).into()), - ], - }, - status_bar: rgba(0xc1bbbbff).into(), - title_bar: rgba(0xc1bbbbff).into(), - toolbar: rgba(0xf4ececff).into(), - tab_bar: rgba(0xebe3e3ff).into(), - editor: rgba(0xf4ececff).into(), - editor_subheader: rgba(0xebe3e3ff).into(), - editor_active_line: rgba(0xebe3e3ff).into(), - terminal: rgba(0xf4ececff).into(), - image_fallback_background: rgba(0xc1bbbbff).into(), - git_created: rgba(0x4c8b8bff).into(), - git_modified: rgba(0x7272caff).into(), - git_deleted: rgba(0xca4a4aff).into(), - git_conflict: rgba(0xa06e3bff).into(), - git_ignored: rgba(0x6e6666ff).into(), - git_renamed: rgba(0xa06e3bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x7272caff).into(), - selection: rgba(0x7272ca3d).into(), - }, - PlayerTheme { - cursor: rgba(0x4c8b8bff).into(), - selection: rgba(0x4c8b8b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xbd5186ff).into(), - selection: rgba(0xbd51863d).into(), - }, - PlayerTheme { - cursor: rgba(0xb45a3cff).into(), - selection: rgba(0xb45a3c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8464c4ff).into(), - selection: rgba(0x8464c43d).into(), - }, - PlayerTheme { - cursor: rgba(0x5485b5ff).into(), - selection: rgba(0x5485b53d).into(), - }, - PlayerTheme { - cursor: rgba(0xca4a4aff).into(), - selection: rgba(0xca4a4a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa06e3bff).into(), - selection: rgba(0xa06e3b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_savanna_dark.rs b/crates/theme2/src/themes/atelier_savanna_dark.rs deleted file mode 100644 index d4040db958..0000000000 --- a/crates/theme2/src/themes/atelier_savanna_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_savanna_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Savanna Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x505e55ff).into(), - border_variant: rgba(0x505e55ff).into(), - border_focused: rgba(0x1f3233ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x353f39ff).into(), - surface: rgba(0x1f2621ff).into(), - background: rgba(0x353f39ff).into(), - filled_element: rgba(0x353f39ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x151e20ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x151e20ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xecf4eeff).into(), - text_muted: rgba(0x859188ff).into(), - text_placeholder: rgba(0xb16038ff).into(), - text_disabled: rgba(0x6f7e74ff).into(), - text_accent: rgba(0x468b8fff).into(), - icon_muted: rgba(0x859188ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("function.method".into(), rgba(0x468b8fff).into()), - ("title".into(), rgba(0xecf4eeff).into()), - ("label".into(), rgba(0x468b8fff).into()), - ("text.literal".into(), rgba(0x9f703bff).into()), - ("boolean".into(), rgba(0x479962ff).into()), - ("punctuation.list_marker".into(), rgba(0xdfe7e2ff).into()), - ("string.escape".into(), rgba(0x87928aff).into()), - ("string.special".into(), rgba(0x857368ff).into()), - ("punctuation.delimiter".into(), rgba(0x87928aff).into()), - ("tag".into(), rgba(0x468b8fff).into()), - ("property".into(), rgba(0xb16038ff).into()), - ("preproc".into(), rgba(0xecf4eeff).into()), - ("primary".into(), rgba(0xdfe7e2ff).into()), - ("link_uri".into(), rgba(0x479962ff).into()), - ("comment".into(), rgba(0x5f6d64ff).into()), - ("type".into(), rgba(0xa07d3aff).into()), - ("hint".into(), rgba(0x607e76ff).into()), - ("punctuation".into(), rgba(0xdfe7e2ff).into()), - ("string.special.symbol".into(), rgba(0x479962ff).into()), - ("emphasis.strong".into(), rgba(0x468b8fff).into()), - ("keyword".into(), rgba(0x55859bff).into()), - ("comment.doc".into(), rgba(0x87928aff).into()), - ("punctuation.bracket".into(), rgba(0x87928aff).into()), - ("constant".into(), rgba(0x479962ff).into()), - ("link_text".into(), rgba(0x9f703bff).into()), - ("number".into(), rgba(0x9f703bff).into()), - ("function".into(), rgba(0x468b8fff).into()), - ("variable".into(), rgba(0xdfe7e2ff).into()), - ("emphasis".into(), rgba(0x468b8fff).into()), - ("punctuation.special".into(), rgba(0x857368ff).into()), - ("constructor".into(), rgba(0x468b8fff).into()), - ("variable.special".into(), rgba(0x55859bff).into()), - ("operator".into(), rgba(0x87928aff).into()), - ("enum".into(), rgba(0x9f703bff).into()), - ("string.regex".into(), rgba(0x1b9aa0ff).into()), - ("attribute".into(), rgba(0x468b8fff).into()), - ("predictive".into(), rgba(0x506d66ff).into()), - ("string".into(), rgba(0x479962ff).into()), - ("embedded".into(), rgba(0xecf4eeff).into()), - ("variant".into(), rgba(0xa07d3aff).into()), - ( - "function.special.definition".into(), - rgba(0xa07d3aff).into(), - ), - ], - }, - status_bar: rgba(0x353f39ff).into(), - title_bar: rgba(0x353f39ff).into(), - toolbar: rgba(0x171c19ff).into(), - tab_bar: rgba(0x1f2621ff).into(), - editor: rgba(0x171c19ff).into(), - editor_subheader: rgba(0x1f2621ff).into(), - editor_active_line: rgba(0x1f2621ff).into(), - terminal: rgba(0x171c19ff).into(), - image_fallback_background: rgba(0x353f39ff).into(), - git_created: rgba(0x479962ff).into(), - git_modified: rgba(0x468b8fff).into(), - git_deleted: rgba(0xb16038ff).into(), - git_conflict: rgba(0xa07d3aff).into(), - git_ignored: rgba(0x6f7e74ff).into(), - git_renamed: rgba(0xa07d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x468b8fff).into(), - selection: rgba(0x468b8f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x479962ff).into(), - selection: rgba(0x4799623d).into(), - }, - PlayerTheme { - cursor: rgba(0x857368ff).into(), - selection: rgba(0x8573683d).into(), - }, - PlayerTheme { - cursor: rgba(0x9f703bff).into(), - selection: rgba(0x9f703b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x55859bff).into(), - selection: rgba(0x55859b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1d9aa0ff).into(), - selection: rgba(0x1d9aa03d).into(), - }, - PlayerTheme { - cursor: rgba(0xb16038ff).into(), - selection: rgba(0xb160383d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3aff).into(), - selection: rgba(0xa07d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_savanna_light.rs b/crates/theme2/src/themes/atelier_savanna_light.rs deleted file mode 100644 index 08722cd91c..0000000000 --- a/crates/theme2/src/themes/atelier_savanna_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_savanna_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Savanna Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8b968eff).into(), - border_variant: rgba(0x8b968eff).into(), - border_focused: rgba(0xbed4d6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xbcc5bfff).into(), - surface: rgba(0xe3ebe6ff).into(), - background: rgba(0xbcc5bfff).into(), - filled_element: rgba(0xbcc5bfff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdae7e8ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdae7e8ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x171c19ff).into(), - text_muted: rgba(0x546259ff).into(), - text_placeholder: rgba(0xb16139ff).into(), - text_disabled: rgba(0x68766dff).into(), - text_accent: rgba(0x488b90ff).into(), - icon_muted: rgba(0x546259ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("text.literal".into(), rgba(0x9f713cff).into()), - ("string".into(), rgba(0x479962ff).into()), - ("punctuation.special".into(), rgba(0x857368ff).into()), - ("type".into(), rgba(0xa07d3aff).into()), - ("enum".into(), rgba(0x9f713cff).into()), - ("title".into(), rgba(0x171c19ff).into()), - ("comment".into(), rgba(0x77877cff).into()), - ("predictive".into(), rgba(0x75958bff).into()), - ("punctuation.list_marker".into(), rgba(0x232a25ff).into()), - ("string.special.symbol".into(), rgba(0x479962ff).into()), - ("constructor".into(), rgba(0x488b90ff).into()), - ("variable".into(), rgba(0x232a25ff).into()), - ("label".into(), rgba(0x488b90ff).into()), - ("attribute".into(), rgba(0x488b90ff).into()), - ("constant".into(), rgba(0x499963ff).into()), - ("function".into(), rgba(0x468b8fff).into()), - ("variable.special".into(), rgba(0x55859bff).into()), - ("keyword".into(), rgba(0x55859bff).into()), - ("number".into(), rgba(0x9f703bff).into()), - ("boolean".into(), rgba(0x499963ff).into()), - ("embedded".into(), rgba(0x171c19ff).into()), - ("string.special".into(), rgba(0x857368ff).into()), - ("emphasis.strong".into(), rgba(0x488b90ff).into()), - ("string.regex".into(), rgba(0x1b9aa0ff).into()), - ("hint".into(), rgba(0x66847cff).into()), - ("preproc".into(), rgba(0x171c19ff).into()), - ("link_uri".into(), rgba(0x499963ff).into()), - ("variant".into(), rgba(0xa07d3aff).into()), - ("function.method".into(), rgba(0x468b8fff).into()), - ("punctuation.bracket".into(), rgba(0x526057ff).into()), - ("punctuation.delimiter".into(), rgba(0x526057ff).into()), - ("punctuation".into(), rgba(0x232a25ff).into()), - ("primary".into(), rgba(0x232a25ff).into()), - ("string.escape".into(), rgba(0x526057ff).into()), - ("property".into(), rgba(0xb16038ff).into()), - ("operator".into(), rgba(0x526057ff).into()), - ("comment.doc".into(), rgba(0x526057ff).into()), - ( - "function.special.definition".into(), - rgba(0xa07d3aff).into(), - ), - ("link_text".into(), rgba(0x9f713cff).into()), - ("tag".into(), rgba(0x488b90ff).into()), - ("emphasis".into(), rgba(0x488b90ff).into()), - ], - }, - status_bar: rgba(0xbcc5bfff).into(), - title_bar: rgba(0xbcc5bfff).into(), - toolbar: rgba(0xecf4eeff).into(), - tab_bar: rgba(0xe3ebe6ff).into(), - editor: rgba(0xecf4eeff).into(), - editor_subheader: rgba(0xe3ebe6ff).into(), - editor_active_line: rgba(0xe3ebe6ff).into(), - terminal: rgba(0xecf4eeff).into(), - image_fallback_background: rgba(0xbcc5bfff).into(), - git_created: rgba(0x499963ff).into(), - git_modified: rgba(0x488b90ff).into(), - git_deleted: rgba(0xb16139ff).into(), - git_conflict: rgba(0xa07d3bff).into(), - git_ignored: rgba(0x68766dff).into(), - git_renamed: rgba(0xa07d3bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x488b90ff).into(), - selection: rgba(0x488b903d).into(), - }, - PlayerTheme { - cursor: rgba(0x499963ff).into(), - selection: rgba(0x4999633d).into(), - }, - PlayerTheme { - cursor: rgba(0x857368ff).into(), - selection: rgba(0x8573683d).into(), - }, - PlayerTheme { - cursor: rgba(0x9f713cff).into(), - selection: rgba(0x9f713c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x55859bff).into(), - selection: rgba(0x55859b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1e9aa0ff).into(), - selection: rgba(0x1e9aa03d).into(), - }, - PlayerTheme { - cursor: rgba(0xb16139ff).into(), - selection: rgba(0xb161393d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3bff).into(), - selection: rgba(0xa07d3b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_seaside_dark.rs b/crates/theme2/src/themes/atelier_seaside_dark.rs deleted file mode 100644 index 475115e0d1..0000000000 --- a/crates/theme2/src/themes/atelier_seaside_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_seaside_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Seaside Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5c6c5cff).into(), - border_variant: rgba(0x5c6c5cff).into(), - border_focused: rgba(0x102667ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3b453bff).into(), - surface: rgba(0x1f231fff).into(), - background: rgba(0x3b453bff).into(), - filled_element: rgba(0x3b453bff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x051949ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x051949ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf3faf3ff).into(), - text_muted: rgba(0x8ba48bff).into(), - text_placeholder: rgba(0xe61c3bff).into(), - text_disabled: rgba(0x778f77ff).into(), - text_accent: rgba(0x3e62f4ff).into(), - icon_muted: rgba(0x8ba48bff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("comment".into(), rgba(0x687d68ff).into()), - ("predictive".into(), rgba(0x00788bff).into()), - ("string.special".into(), rgba(0xe618c3ff).into()), - ("string.regex".into(), rgba(0x1899b3ff).into()), - ("boolean".into(), rgba(0x2aa329ff).into()), - ("string".into(), rgba(0x28a328ff).into()), - ("operator".into(), rgba(0x8ca68cff).into()), - ("primary".into(), rgba(0xcfe8cfff).into()), - ("number".into(), rgba(0x87711cff).into()), - ("punctuation.special".into(), rgba(0xe618c3ff).into()), - ("link_text".into(), rgba(0x87711dff).into()), - ("title".into(), rgba(0xf3faf3ff).into()), - ("comment.doc".into(), rgba(0x8ca68cff).into()), - ("label".into(), rgba(0x3e62f4ff).into()), - ("preproc".into(), rgba(0xf3faf3ff).into()), - ("punctuation.bracket".into(), rgba(0x8ca68cff).into()), - ("punctuation.delimiter".into(), rgba(0x8ca68cff).into()), - ("function.method".into(), rgba(0x3d62f5ff).into()), - ("tag".into(), rgba(0x3e62f4ff).into()), - ("embedded".into(), rgba(0xf3faf3ff).into()), - ("text.literal".into(), rgba(0x87711dff).into()), - ("punctuation".into(), rgba(0xcfe8cfff).into()), - ("string.special.symbol".into(), rgba(0x28a328ff).into()), - ("link_uri".into(), rgba(0x2aa329ff).into()), - ("keyword".into(), rgba(0xac2aeeff).into()), - ("function".into(), rgba(0x3d62f5ff).into()), - ("string.escape".into(), rgba(0x8ca68cff).into()), - ("variant".into(), rgba(0x98981bff).into()), - ( - "function.special.definition".into(), - rgba(0x98981bff).into(), - ), - ("constructor".into(), rgba(0x3e62f4ff).into()), - ("constant".into(), rgba(0x2aa329ff).into()), - ("hint".into(), rgba(0x008b9fff).into()), - ("type".into(), rgba(0x98981bff).into()), - ("emphasis".into(), rgba(0x3e62f4ff).into()), - ("variable".into(), rgba(0xcfe8cfff).into()), - ("emphasis.strong".into(), rgba(0x3e62f4ff).into()), - ("attribute".into(), rgba(0x3e62f4ff).into()), - ("enum".into(), rgba(0x87711dff).into()), - ("property".into(), rgba(0xe6183bff).into()), - ("punctuation.list_marker".into(), rgba(0xcfe8cfff).into()), - ("variable.special".into(), rgba(0xac2aeeff).into()), - ], - }, - status_bar: rgba(0x3b453bff).into(), - title_bar: rgba(0x3b453bff).into(), - toolbar: rgba(0x131513ff).into(), - tab_bar: rgba(0x1f231fff).into(), - editor: rgba(0x131513ff).into(), - editor_subheader: rgba(0x1f231fff).into(), - editor_active_line: rgba(0x1f231fff).into(), - terminal: rgba(0x131513ff).into(), - image_fallback_background: rgba(0x3b453bff).into(), - git_created: rgba(0x2aa329ff).into(), - git_modified: rgba(0x3e62f4ff).into(), - git_deleted: rgba(0xe61c3bff).into(), - git_conflict: rgba(0x98981bff).into(), - git_ignored: rgba(0x778f77ff).into(), - git_renamed: rgba(0x98981bff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e62f4ff).into(), - selection: rgba(0x3e62f43d).into(), - }, - PlayerTheme { - cursor: rgba(0x2aa329ff).into(), - selection: rgba(0x2aa3293d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61cc3ff).into(), - selection: rgba(0xe61cc33d).into(), - }, - PlayerTheme { - cursor: rgba(0x87711dff).into(), - selection: rgba(0x87711d3d).into(), - }, - PlayerTheme { - cursor: rgba(0xac2dedff).into(), - selection: rgba(0xac2ded3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1b99b3ff).into(), - selection: rgba(0x1b99b33d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61c3bff).into(), - selection: rgba(0xe61c3b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x98981bff).into(), - selection: rgba(0x98981b3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_seaside_light.rs b/crates/theme2/src/themes/atelier_seaside_light.rs deleted file mode 100644 index 557134b540..0000000000 --- a/crates/theme2/src/themes/atelier_seaside_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_seaside_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Seaside Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x8ea88eff).into(), - border_variant: rgba(0x8ea88eff).into(), - border_focused: rgba(0xc9c4fdff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xb4ceb4ff).into(), - surface: rgba(0xdaeedaff).into(), - background: rgba(0xb4ceb4ff).into(), - filled_element: rgba(0xb4ceb4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe1ddfeff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe1ddfeff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x131513ff).into(), - text_muted: rgba(0x5f705fff).into(), - text_placeholder: rgba(0xe61c3dff).into(), - text_disabled: rgba(0x718771ff).into(), - text_accent: rgba(0x3e61f4ff).into(), - icon_muted: rgba(0x5f705fff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.escape".into(), rgba(0x5e6e5eff).into()), - ("boolean".into(), rgba(0x2aa32aff).into()), - ("string.special".into(), rgba(0xe618c3ff).into()), - ("comment".into(), rgba(0x809980ff).into()), - ("number".into(), rgba(0x87711cff).into()), - ("comment.doc".into(), rgba(0x5e6e5eff).into()), - ("tag".into(), rgba(0x3e61f4ff).into()), - ("string.special.symbol".into(), rgba(0x28a328ff).into()), - ("primary".into(), rgba(0x242924ff).into()), - ("string".into(), rgba(0x28a328ff).into()), - ("enum".into(), rgba(0x87711fff).into()), - ("operator".into(), rgba(0x5e6e5eff).into()), - ("string.regex".into(), rgba(0x1899b3ff).into()), - ("keyword".into(), rgba(0xac2aeeff).into()), - ("emphasis".into(), rgba(0x3e61f4ff).into()), - ("link_uri".into(), rgba(0x2aa32aff).into()), - ("constant".into(), rgba(0x2aa32aff).into()), - ("constructor".into(), rgba(0x3e61f4ff).into()), - ("link_text".into(), rgba(0x87711fff).into()), - ("emphasis.strong".into(), rgba(0x3e61f4ff).into()), - ("punctuation.list_marker".into(), rgba(0x242924ff).into()), - ("punctuation.delimiter".into(), rgba(0x5e6e5eff).into()), - ("punctuation.special".into(), rgba(0xe618c3ff).into()), - ("variant".into(), rgba(0x98981bff).into()), - ("predictive".into(), rgba(0x00a2b5ff).into()), - ("attribute".into(), rgba(0x3e61f4ff).into()), - ("preproc".into(), rgba(0x131513ff).into()), - ("embedded".into(), rgba(0x131513ff).into()), - ("punctuation".into(), rgba(0x242924ff).into()), - ("label".into(), rgba(0x3e61f4ff).into()), - ("function.method".into(), rgba(0x3d62f5ff).into()), - ("property".into(), rgba(0xe6183bff).into()), - ("title".into(), rgba(0x131513ff).into()), - ("variable".into(), rgba(0x242924ff).into()), - ("function".into(), rgba(0x3d62f5ff).into()), - ("variable.special".into(), rgba(0xac2aeeff).into()), - ("type".into(), rgba(0x98981bff).into()), - ("text.literal".into(), rgba(0x87711fff).into()), - ("hint".into(), rgba(0x008fa1ff).into()), - ( - "function.special.definition".into(), - rgba(0x98981bff).into(), - ), - ("punctuation.bracket".into(), rgba(0x5e6e5eff).into()), - ], - }, - status_bar: rgba(0xb4ceb4ff).into(), - title_bar: rgba(0xb4ceb4ff).into(), - toolbar: rgba(0xf3faf3ff).into(), - tab_bar: rgba(0xdaeedaff).into(), - editor: rgba(0xf3faf3ff).into(), - editor_subheader: rgba(0xdaeedaff).into(), - editor_active_line: rgba(0xdaeedaff).into(), - terminal: rgba(0xf3faf3ff).into(), - image_fallback_background: rgba(0xb4ceb4ff).into(), - git_created: rgba(0x2aa32aff).into(), - git_modified: rgba(0x3e61f4ff).into(), - git_deleted: rgba(0xe61c3dff).into(), - git_conflict: rgba(0x98981cff).into(), - git_ignored: rgba(0x718771ff).into(), - git_renamed: rgba(0x98981cff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e61f4ff).into(), - selection: rgba(0x3e61f43d).into(), - }, - PlayerTheme { - cursor: rgba(0x2aa32aff).into(), - selection: rgba(0x2aa32a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61cc2ff).into(), - selection: rgba(0xe61cc23d).into(), - }, - PlayerTheme { - cursor: rgba(0x87711fff).into(), - selection: rgba(0x87711f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xac2dedff).into(), - selection: rgba(0xac2ded3d).into(), - }, - PlayerTheme { - cursor: rgba(0x1c99b3ff).into(), - selection: rgba(0x1c99b33d).into(), - }, - PlayerTheme { - cursor: rgba(0xe61c3dff).into(), - selection: rgba(0xe61c3d3d).into(), - }, - PlayerTheme { - cursor: rgba(0x98981cff).into(), - selection: rgba(0x98981c3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_sulphurpool_dark.rs b/crates/theme2/src/themes/atelier_sulphurpool_dark.rs deleted file mode 100644 index 8be8451740..0000000000 --- a/crates/theme2/src/themes/atelier_sulphurpool_dark.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_sulphurpool_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Sulphurpool Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b6385ff).into(), - border_variant: rgba(0x5b6385ff).into(), - border_focused: rgba(0x203348ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3e4769ff).into(), - surface: rgba(0x262f51ff).into(), - background: rgba(0x3e4769ff).into(), - filled_element: rgba(0x3e4769ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x161f2bff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x161f2bff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf5f7ffff).into(), - text_muted: rgba(0x959bb2ff).into(), - text_placeholder: rgba(0xc94922ff).into(), - text_disabled: rgba(0x7e849eff).into(), - text_accent: rgba(0x3e8ed0ff).into(), - icon_muted: rgba(0x959bb2ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("title".into(), rgba(0xf5f7ffff).into()), - ("constructor".into(), rgba(0x3e8ed0ff).into()), - ("type".into(), rgba(0xc08b2fff).into()), - ("punctuation.list_marker".into(), rgba(0xdfe2f1ff).into()), - ("property".into(), rgba(0xc94821ff).into()), - ("link_uri".into(), rgba(0xac9739ff).into()), - ("string.escape".into(), rgba(0x979db4ff).into()), - ("constant".into(), rgba(0xac9739ff).into()), - ("embedded".into(), rgba(0xf5f7ffff).into()), - ("punctuation.special".into(), rgba(0x9b6279ff).into()), - ("punctuation.bracket".into(), rgba(0x979db4ff).into()), - ("preproc".into(), rgba(0xf5f7ffff).into()), - ("emphasis.strong".into(), rgba(0x3e8ed0ff).into()), - ("emphasis".into(), rgba(0x3e8ed0ff).into()), - ("enum".into(), rgba(0xc76a29ff).into()), - ("boolean".into(), rgba(0xac9739ff).into()), - ("primary".into(), rgba(0xdfe2f1ff).into()), - ("function.method".into(), rgba(0x3d8fd1ff).into()), - ( - "function.special.definition".into(), - rgba(0xc08b2fff).into(), - ), - ("comment.doc".into(), rgba(0x979db4ff).into()), - ("string".into(), rgba(0xac9738ff).into()), - ("text.literal".into(), rgba(0xc76a29ff).into()), - ("operator".into(), rgba(0x979db4ff).into()), - ("number".into(), rgba(0xc76a28ff).into()), - ("string.special".into(), rgba(0x9b6279ff).into()), - ("punctuation.delimiter".into(), rgba(0x979db4ff).into()), - ("tag".into(), rgba(0x3e8ed0ff).into()), - ("string.special.symbol".into(), rgba(0xac9738ff).into()), - ("variable".into(), rgba(0xdfe2f1ff).into()), - ("attribute".into(), rgba(0x3e8ed0ff).into()), - ("punctuation".into(), rgba(0xdfe2f1ff).into()), - ("string.regex".into(), rgba(0x21a2c9ff).into()), - ("keyword".into(), rgba(0x6679ccff).into()), - ("label".into(), rgba(0x3e8ed0ff).into()), - ("hint".into(), rgba(0x6c81a5ff).into()), - ("function".into(), rgba(0x3d8fd1ff).into()), - ("link_text".into(), rgba(0xc76a29ff).into()), - ("variant".into(), rgba(0xc08b2fff).into()), - ("variable.special".into(), rgba(0x6679ccff).into()), - ("predictive".into(), rgba(0x58709aff).into()), - ("comment".into(), rgba(0x6a7293ff).into()), - ], - }, - status_bar: rgba(0x3e4769ff).into(), - title_bar: rgba(0x3e4769ff).into(), - toolbar: rgba(0x202646ff).into(), - tab_bar: rgba(0x262f51ff).into(), - editor: rgba(0x202646ff).into(), - editor_subheader: rgba(0x262f51ff).into(), - editor_active_line: rgba(0x262f51ff).into(), - terminal: rgba(0x202646ff).into(), - image_fallback_background: rgba(0x3e4769ff).into(), - git_created: rgba(0xac9739ff).into(), - git_modified: rgba(0x3e8ed0ff).into(), - git_deleted: rgba(0xc94922ff).into(), - git_conflict: rgba(0xc08b30ff).into(), - git_ignored: rgba(0x7e849eff).into(), - git_renamed: rgba(0xc08b30ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e8ed0ff).into(), - selection: rgba(0x3e8ed03d).into(), - }, - PlayerTheme { - cursor: rgba(0xac9739ff).into(), - selection: rgba(0xac97393d).into(), - }, - PlayerTheme { - cursor: rgba(0x9b6279ff).into(), - selection: rgba(0x9b62793d).into(), - }, - PlayerTheme { - cursor: rgba(0xc76a29ff).into(), - selection: rgba(0xc76a293d).into(), - }, - PlayerTheme { - cursor: rgba(0x6679ccff).into(), - selection: rgba(0x6679cc3d).into(), - }, - PlayerTheme { - cursor: rgba(0x24a1c9ff).into(), - selection: rgba(0x24a1c93d).into(), - }, - PlayerTheme { - cursor: rgba(0xc94922ff).into(), - selection: rgba(0xc949223d).into(), - }, - PlayerTheme { - cursor: rgba(0xc08b30ff).into(), - selection: rgba(0xc08b303d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/atelier_sulphurpool_light.rs b/crates/theme2/src/themes/atelier_sulphurpool_light.rs deleted file mode 100644 index dba723331a..0000000000 --- a/crates/theme2/src/themes/atelier_sulphurpool_light.rs +++ /dev/null @@ -1,136 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn atelier_sulphurpool_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Atelier Sulphurpool Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x9a9fb6ff).into(), - border_variant: rgba(0x9a9fb6ff).into(), - border_focused: rgba(0xc2d5efff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xc1c5d8ff).into(), - surface: rgba(0xe5e8f5ff).into(), - background: rgba(0xc1c5d8ff).into(), - filled_element: rgba(0xc1c5d8ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdde7f6ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdde7f6ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x202646ff).into(), - text_muted: rgba(0x5f6789ff).into(), - text_placeholder: rgba(0xc94922ff).into(), - text_disabled: rgba(0x767d9aff).into(), - text_accent: rgba(0x3e8fd0ff).into(), - icon_muted: rgba(0x5f6789ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special".into(), rgba(0x9b6279ff).into()), - ("string.regex".into(), rgba(0x21a2c9ff).into()), - ("embedded".into(), rgba(0x202646ff).into()), - ("string".into(), rgba(0xac9738ff).into()), - ( - "function.special.definition".into(), - rgba(0xc08b2fff).into(), - ), - ("hint".into(), rgba(0x7087b2ff).into()), - ("function.method".into(), rgba(0x3d8fd1ff).into()), - ("punctuation.list_marker".into(), rgba(0x293256ff).into()), - ("punctuation".into(), rgba(0x293256ff).into()), - ("constant".into(), rgba(0xac9739ff).into()), - ("label".into(), rgba(0x3e8fd0ff).into()), - ("comment.doc".into(), rgba(0x5d6587ff).into()), - ("property".into(), rgba(0xc94821ff).into()), - ("punctuation.bracket".into(), rgba(0x5d6587ff).into()), - ("constructor".into(), rgba(0x3e8fd0ff).into()), - ("variable.special".into(), rgba(0x6679ccff).into()), - ("emphasis".into(), rgba(0x3e8fd0ff).into()), - ("link_text".into(), rgba(0xc76a29ff).into()), - ("keyword".into(), rgba(0x6679ccff).into()), - ("primary".into(), rgba(0x293256ff).into()), - ("comment".into(), rgba(0x898ea4ff).into()), - ("title".into(), rgba(0x202646ff).into()), - ("link_uri".into(), rgba(0xac9739ff).into()), - ("text.literal".into(), rgba(0xc76a29ff).into()), - ("operator".into(), rgba(0x5d6587ff).into()), - ("number".into(), rgba(0xc76a28ff).into()), - ("preproc".into(), rgba(0x202646ff).into()), - ("attribute".into(), rgba(0x3e8fd0ff).into()), - ("emphasis.strong".into(), rgba(0x3e8fd0ff).into()), - ("string.escape".into(), rgba(0x5d6587ff).into()), - ("tag".into(), rgba(0x3e8fd0ff).into()), - ("variable".into(), rgba(0x293256ff).into()), - ("predictive".into(), rgba(0x8599beff).into()), - ("enum".into(), rgba(0xc76a29ff).into()), - ("string.special.symbol".into(), rgba(0xac9738ff).into()), - ("punctuation.delimiter".into(), rgba(0x5d6587ff).into()), - ("function".into(), rgba(0x3d8fd1ff).into()), - ("type".into(), rgba(0xc08b2fff).into()), - ("punctuation.special".into(), rgba(0x9b6279ff).into()), - ("variant".into(), rgba(0xc08b2fff).into()), - ("boolean".into(), rgba(0xac9739ff).into()), - ], - }, - status_bar: rgba(0xc1c5d8ff).into(), - title_bar: rgba(0xc1c5d8ff).into(), - toolbar: rgba(0xf5f7ffff).into(), - tab_bar: rgba(0xe5e8f5ff).into(), - editor: rgba(0xf5f7ffff).into(), - editor_subheader: rgba(0xe5e8f5ff).into(), - editor_active_line: rgba(0xe5e8f5ff).into(), - terminal: rgba(0xf5f7ffff).into(), - image_fallback_background: rgba(0xc1c5d8ff).into(), - git_created: rgba(0xac9739ff).into(), - git_modified: rgba(0x3e8fd0ff).into(), - git_deleted: rgba(0xc94922ff).into(), - git_conflict: rgba(0xc08b30ff).into(), - git_ignored: rgba(0x767d9aff).into(), - git_renamed: rgba(0xc08b30ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3e8fd0ff).into(), - selection: rgba(0x3e8fd03d).into(), - }, - PlayerTheme { - cursor: rgba(0xac9739ff).into(), - selection: rgba(0xac97393d).into(), - }, - PlayerTheme { - cursor: rgba(0x9b6279ff).into(), - selection: rgba(0x9b62793d).into(), - }, - PlayerTheme { - cursor: rgba(0xc76a29ff).into(), - selection: rgba(0xc76a293d).into(), - }, - PlayerTheme { - cursor: rgba(0x6679cbff).into(), - selection: rgba(0x6679cb3d).into(), - }, - PlayerTheme { - cursor: rgba(0x24a1c9ff).into(), - selection: rgba(0x24a1c93d).into(), - }, - PlayerTheme { - cursor: rgba(0xc94922ff).into(), - selection: rgba(0xc949223d).into(), - }, - PlayerTheme { - cursor: rgba(0xc08b30ff).into(), - selection: rgba(0xc08b303d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/ayu_dark.rs b/crates/theme2/src/themes/ayu_dark.rs deleted file mode 100644 index 35d3a43154..0000000000 --- a/crates/theme2/src/themes/ayu_dark.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn ayu_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Ayu Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x3f4043ff).into(), - border_variant: rgba(0x3f4043ff).into(), - border_focused: rgba(0x1b4a6eff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x313337ff).into(), - surface: rgba(0x1f2127ff).into(), - background: rgba(0x313337ff).into(), - filled_element: rgba(0x313337ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0d2f4eff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0d2f4eff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xbfbdb6ff).into(), - text_muted: rgba(0x8a8986ff).into(), - text_placeholder: rgba(0xef7177ff).into(), - text_disabled: rgba(0x696a6aff).into(), - text_accent: rgba(0x5ac1feff).into(), - icon_muted: rgba(0x8a8986ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("emphasis".into(), rgba(0x5ac1feff).into()), - ("punctuation.bracket".into(), rgba(0xa6a5a0ff).into()), - ("constructor".into(), rgba(0x5ac1feff).into()), - ("predictive".into(), rgba(0x5a728bff).into()), - ("emphasis.strong".into(), rgba(0x5ac1feff).into()), - ("string.regex".into(), rgba(0x95e6cbff).into()), - ("tag".into(), rgba(0x5ac1feff).into()), - ("punctuation".into(), rgba(0xa6a5a0ff).into()), - ("number".into(), rgba(0xd2a6ffff).into()), - ("punctuation.special".into(), rgba(0xd2a6ffff).into()), - ("primary".into(), rgba(0xbfbdb6ff).into()), - ("boolean".into(), rgba(0xd2a6ffff).into()), - ("variant".into(), rgba(0x5ac1feff).into()), - ("link_uri".into(), rgba(0xaad84cff).into()), - ("comment.doc".into(), rgba(0x8c8b88ff).into()), - ("title".into(), rgba(0xbfbdb6ff).into()), - ("text.literal".into(), rgba(0xfe8f40ff).into()), - ("link_text".into(), rgba(0xfe8f40ff).into()), - ("punctuation.delimiter".into(), rgba(0xa6a5a0ff).into()), - ("string.escape".into(), rgba(0x8c8b88ff).into()), - ("hint".into(), rgba(0x628b80ff).into()), - ("type".into(), rgba(0x59c2ffff).into()), - ("variable".into(), rgba(0xbfbdb6ff).into()), - ("label".into(), rgba(0x5ac1feff).into()), - ("enum".into(), rgba(0xfe8f40ff).into()), - ("operator".into(), rgba(0xf29668ff).into()), - ("function".into(), rgba(0xffb353ff).into()), - ("preproc".into(), rgba(0xbfbdb6ff).into()), - ("embedded".into(), rgba(0xbfbdb6ff).into()), - ("string".into(), rgba(0xa9d94bff).into()), - ("attribute".into(), rgba(0x5ac1feff).into()), - ("keyword".into(), rgba(0xff8f3fff).into()), - ("string.special.symbol".into(), rgba(0xfe8f40ff).into()), - ("comment".into(), rgba(0xabb5be8c).into()), - ("property".into(), rgba(0x5ac1feff).into()), - ("punctuation.list_marker".into(), rgba(0xa6a5a0ff).into()), - ("constant".into(), rgba(0xd2a6ffff).into()), - ("string.special".into(), rgba(0xe5b572ff).into()), - ], - }, - status_bar: rgba(0x313337ff).into(), - title_bar: rgba(0x313337ff).into(), - toolbar: rgba(0x0d1016ff).into(), - tab_bar: rgba(0x1f2127ff).into(), - editor: rgba(0x0d1016ff).into(), - editor_subheader: rgba(0x1f2127ff).into(), - editor_active_line: rgba(0x1f2127ff).into(), - terminal: rgba(0x0d1016ff).into(), - image_fallback_background: rgba(0x313337ff).into(), - git_created: rgba(0xaad84cff).into(), - git_modified: rgba(0x5ac1feff).into(), - git_deleted: rgba(0xef7177ff).into(), - git_conflict: rgba(0xfeb454ff).into(), - git_ignored: rgba(0x696a6aff).into(), - git_renamed: rgba(0xfeb454ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5ac1feff).into(), - selection: rgba(0x5ac1fe3d).into(), - }, - PlayerTheme { - cursor: rgba(0xaad84cff).into(), - selection: rgba(0xaad84c3d).into(), - }, - PlayerTheme { - cursor: rgba(0x39bae5ff).into(), - selection: rgba(0x39bae53d).into(), - }, - PlayerTheme { - cursor: rgba(0xfe8f40ff).into(), - selection: rgba(0xfe8f403d).into(), - }, - PlayerTheme { - cursor: rgba(0xd2a6feff).into(), - selection: rgba(0xd2a6fe3d).into(), - }, - PlayerTheme { - cursor: rgba(0x95e5cbff).into(), - selection: rgba(0x95e5cb3d).into(), - }, - PlayerTheme { - cursor: rgba(0xef7177ff).into(), - selection: rgba(0xef71773d).into(), - }, - PlayerTheme { - cursor: rgba(0xfeb454ff).into(), - selection: rgba(0xfeb4543d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/ayu_light.rs b/crates/theme2/src/themes/ayu_light.rs deleted file mode 100644 index 887282e564..0000000000 --- a/crates/theme2/src/themes/ayu_light.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn ayu_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Ayu Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xcfd1d2ff).into(), - border_variant: rgba(0xcfd1d2ff).into(), - border_focused: rgba(0xc4daf6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xdcdddeff).into(), - surface: rgba(0xececedff).into(), - background: rgba(0xdcdddeff).into(), - filled_element: rgba(0xdcdddeff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdeebfaff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdeebfaff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x5c6166ff).into(), - text_muted: rgba(0x8b8e92ff).into(), - text_placeholder: rgba(0xef7271ff).into(), - text_disabled: rgba(0xa9acaeff).into(), - text_accent: rgba(0x3b9ee5ff).into(), - icon_muted: rgba(0x8b8e92ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string".into(), rgba(0x86b300ff).into()), - ("enum".into(), rgba(0xf98d3fff).into()), - ("comment".into(), rgba(0x787b8099).into()), - ("comment.doc".into(), rgba(0x898d90ff).into()), - ("emphasis".into(), rgba(0x3b9ee5ff).into()), - ("keyword".into(), rgba(0xfa8d3eff).into()), - ("string.regex".into(), rgba(0x4bbf98ff).into()), - ("text.literal".into(), rgba(0xf98d3fff).into()), - ("string.escape".into(), rgba(0x898d90ff).into()), - ("link_text".into(), rgba(0xf98d3fff).into()), - ("punctuation".into(), rgba(0x73777bff).into()), - ("constructor".into(), rgba(0x3b9ee5ff).into()), - ("constant".into(), rgba(0xa37accff).into()), - ("variable".into(), rgba(0x5c6166ff).into()), - ("primary".into(), rgba(0x5c6166ff).into()), - ("emphasis.strong".into(), rgba(0x3b9ee5ff).into()), - ("string.special".into(), rgba(0xe6ba7eff).into()), - ("number".into(), rgba(0xa37accff).into()), - ("preproc".into(), rgba(0x5c6166ff).into()), - ("punctuation.delimiter".into(), rgba(0x73777bff).into()), - ("string.special.symbol".into(), rgba(0xf98d3fff).into()), - ("boolean".into(), rgba(0xa37accff).into()), - ("property".into(), rgba(0x3b9ee5ff).into()), - ("title".into(), rgba(0x5c6166ff).into()), - ("hint".into(), rgba(0x8ca7c2ff).into()), - ("predictive".into(), rgba(0x9eb9d3ff).into()), - ("operator".into(), rgba(0xed9365ff).into()), - ("type".into(), rgba(0x389ee6ff).into()), - ("function".into(), rgba(0xf2ad48ff).into()), - ("variant".into(), rgba(0x3b9ee5ff).into()), - ("label".into(), rgba(0x3b9ee5ff).into()), - ("punctuation.list_marker".into(), rgba(0x73777bff).into()), - ("punctuation.bracket".into(), rgba(0x73777bff).into()), - ("embedded".into(), rgba(0x5c6166ff).into()), - ("punctuation.special".into(), rgba(0xa37accff).into()), - ("attribute".into(), rgba(0x3b9ee5ff).into()), - ("tag".into(), rgba(0x3b9ee5ff).into()), - ("link_uri".into(), rgba(0x85b304ff).into()), - ], - }, - status_bar: rgba(0xdcdddeff).into(), - title_bar: rgba(0xdcdddeff).into(), - toolbar: rgba(0xfcfcfcff).into(), - tab_bar: rgba(0xececedff).into(), - editor: rgba(0xfcfcfcff).into(), - editor_subheader: rgba(0xececedff).into(), - editor_active_line: rgba(0xececedff).into(), - terminal: rgba(0xfcfcfcff).into(), - image_fallback_background: rgba(0xdcdddeff).into(), - git_created: rgba(0x85b304ff).into(), - git_modified: rgba(0x3b9ee5ff).into(), - git_deleted: rgba(0xef7271ff).into(), - git_conflict: rgba(0xf1ad49ff).into(), - git_ignored: rgba(0xa9acaeff).into(), - git_renamed: rgba(0xf1ad49ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x3b9ee5ff).into(), - selection: rgba(0x3b9ee53d).into(), - }, - PlayerTheme { - cursor: rgba(0x85b304ff).into(), - selection: rgba(0x85b3043d).into(), - }, - PlayerTheme { - cursor: rgba(0x55b4d3ff).into(), - selection: rgba(0x55b4d33d).into(), - }, - PlayerTheme { - cursor: rgba(0xf98d3fff).into(), - selection: rgba(0xf98d3f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa37accff).into(), - selection: rgba(0xa37acc3d).into(), - }, - PlayerTheme { - cursor: rgba(0x4dbf99ff).into(), - selection: rgba(0x4dbf993d).into(), - }, - PlayerTheme { - cursor: rgba(0xef7271ff).into(), - selection: rgba(0xef72713d).into(), - }, - PlayerTheme { - cursor: rgba(0xf1ad49ff).into(), - selection: rgba(0xf1ad493d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/ayu_mirage.rs b/crates/theme2/src/themes/ayu_mirage.rs deleted file mode 100644 index 2974881a18..0000000000 --- a/crates/theme2/src/themes/ayu_mirage.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn ayu_mirage() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Ayu Mirage".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x53565dff).into(), - border_variant: rgba(0x53565dff).into(), - border_focused: rgba(0x24556fff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x464a52ff).into(), - surface: rgba(0x353944ff).into(), - background: rgba(0x464a52ff).into(), - filled_element: rgba(0x464a52ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x123950ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x123950ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xcccac2ff).into(), - text_muted: rgba(0x9a9a98ff).into(), - text_placeholder: rgba(0xf18779ff).into(), - text_disabled: rgba(0x7b7d7fff).into(), - text_accent: rgba(0x72cffeff).into(), - icon_muted: rgba(0x9a9a98ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("text.literal".into(), rgba(0xfead66ff).into()), - ("link_text".into(), rgba(0xfead66ff).into()), - ("function".into(), rgba(0xffd173ff).into()), - ("punctuation.delimiter".into(), rgba(0xb4b3aeff).into()), - ("property".into(), rgba(0x72cffeff).into()), - ("title".into(), rgba(0xcccac2ff).into()), - ("boolean".into(), rgba(0xdfbfffff).into()), - ("link_uri".into(), rgba(0xd5fe80ff).into()), - ("label".into(), rgba(0x72cffeff).into()), - ("primary".into(), rgba(0xcccac2ff).into()), - ("number".into(), rgba(0xdfbfffff).into()), - ("variant".into(), rgba(0x72cffeff).into()), - ("enum".into(), rgba(0xfead66ff).into()), - ("string.special.symbol".into(), rgba(0xfead66ff).into()), - ("operator".into(), rgba(0xf29e74ff).into()), - ("punctuation.special".into(), rgba(0xdfbfffff).into()), - ("constructor".into(), rgba(0x72cffeff).into()), - ("type".into(), rgba(0x73cfffff).into()), - ("emphasis.strong".into(), rgba(0x72cffeff).into()), - ("embedded".into(), rgba(0xcccac2ff).into()), - ("comment".into(), rgba(0xb8cfe680).into()), - ("tag".into(), rgba(0x72cffeff).into()), - ("keyword".into(), rgba(0xffad65ff).into()), - ("punctuation".into(), rgba(0xb4b3aeff).into()), - ("preproc".into(), rgba(0xcccac2ff).into()), - ("hint".into(), rgba(0x7399a3ff).into()), - ("string.special".into(), rgba(0xffdfb3ff).into()), - ("attribute".into(), rgba(0x72cffeff).into()), - ("string.regex".into(), rgba(0x95e6cbff).into()), - ("predictive".into(), rgba(0x6d839bff).into()), - ("comment.doc".into(), rgba(0x9b9b99ff).into()), - ("emphasis".into(), rgba(0x72cffeff).into()), - ("string".into(), rgba(0xd4fe7fff).into()), - ("constant".into(), rgba(0xdfbfffff).into()), - ("string.escape".into(), rgba(0x9b9b99ff).into()), - ("variable".into(), rgba(0xcccac2ff).into()), - ("punctuation.bracket".into(), rgba(0xb4b3aeff).into()), - ("punctuation.list_marker".into(), rgba(0xb4b3aeff).into()), - ], - }, - status_bar: rgba(0x464a52ff).into(), - title_bar: rgba(0x464a52ff).into(), - toolbar: rgba(0x242835ff).into(), - tab_bar: rgba(0x353944ff).into(), - editor: rgba(0x242835ff).into(), - editor_subheader: rgba(0x353944ff).into(), - editor_active_line: rgba(0x353944ff).into(), - terminal: rgba(0x242835ff).into(), - image_fallback_background: rgba(0x464a52ff).into(), - git_created: rgba(0xd5fe80ff).into(), - git_modified: rgba(0x72cffeff).into(), - git_deleted: rgba(0xf18779ff).into(), - git_conflict: rgba(0xfecf72ff).into(), - git_ignored: rgba(0x7b7d7fff).into(), - git_renamed: rgba(0xfecf72ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x72cffeff).into(), - selection: rgba(0x72cffe3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd5fe80ff).into(), - selection: rgba(0xd5fe803d).into(), - }, - PlayerTheme { - cursor: rgba(0x5bcde5ff).into(), - selection: rgba(0x5bcde53d).into(), - }, - PlayerTheme { - cursor: rgba(0xfead66ff).into(), - selection: rgba(0xfead663d).into(), - }, - PlayerTheme { - cursor: rgba(0xdebffeff).into(), - selection: rgba(0xdebffe3d).into(), - }, - PlayerTheme { - cursor: rgba(0x95e5cbff).into(), - selection: rgba(0x95e5cb3d).into(), - }, - PlayerTheme { - cursor: rgba(0xf18779ff).into(), - selection: rgba(0xf187793d).into(), - }, - PlayerTheme { - cursor: rgba(0xfecf72ff).into(), - selection: rgba(0xfecf723d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_dark.rs b/crates/theme2/src/themes/gruvbox_dark.rs deleted file mode 100644 index 6e982808cf..0000000000 --- a/crates/theme2/src/themes/gruvbox_dark.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b534dff).into(), - border_variant: rgba(0x5b534dff).into(), - border_focused: rgba(0x303a36ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x4c4642ff).into(), - surface: rgba(0x3a3735ff).into(), - background: rgba(0x4c4642ff).into(), - filled_element: rgba(0x4c4642ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1e2321ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1e2321ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfbf1c7ff).into(), - text_muted: rgba(0xc5b597ff).into(), - text_placeholder: rgba(0xfb4a35ff).into(), - text_disabled: rgba(0x998b78ff).into(), - text_accent: rgba(0x83a598ff).into(), - icon_muted: rgba(0xc5b597ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("operator".into(), rgba(0x8ec07cff).into()), - ("string.special.symbol".into(), rgba(0x8ec07cff).into()), - ("emphasis.strong".into(), rgba(0x83a598ff).into()), - ("attribute".into(), rgba(0x83a598ff).into()), - ("property".into(), rgba(0xebdbb2ff).into()), - ("comment.doc".into(), rgba(0xc6b697ff).into()), - ("emphasis".into(), rgba(0x83a598ff).into()), - ("variant".into(), rgba(0x83a598ff).into()), - ("text.literal".into(), rgba(0x83a598ff).into()), - ("keyword".into(), rgba(0xfb4833ff).into()), - ("primary".into(), rgba(0xebdbb2ff).into()), - ("variable".into(), rgba(0x83a598ff).into()), - ("enum".into(), rgba(0xfe7f18ff).into()), - ("constructor".into(), rgba(0x83a598ff).into()), - ("punctuation".into(), rgba(0xd5c4a1ff).into()), - ("link_uri".into(), rgba(0xd3869bff).into()), - ("hint".into(), rgba(0x8c957dff).into()), - ("string.regex".into(), rgba(0xfe7f18ff).into()), - ("punctuation.delimiter".into(), rgba(0xe5d5adff).into()), - ("string".into(), rgba(0xb8bb25ff).into()), - ("punctuation.special".into(), rgba(0xe5d5adff).into()), - ("link_text".into(), rgba(0x8ec07cff).into()), - ("tag".into(), rgba(0x8ec07cff).into()), - ("string.escape".into(), rgba(0xc6b697ff).into()), - ("label".into(), rgba(0x83a598ff).into()), - ("constant".into(), rgba(0xfabd2eff).into()), - ("type".into(), rgba(0xfabd2eff).into()), - ("number".into(), rgba(0xd3869bff).into()), - ("string.special".into(), rgba(0xd3869bff).into()), - ("function.builtin".into(), rgba(0xfb4833ff).into()), - ("boolean".into(), rgba(0xd3869bff).into()), - ("embedded".into(), rgba(0x8ec07cff).into()), - ("title".into(), rgba(0xb8bb25ff).into()), - ("function".into(), rgba(0xb8bb25ff).into()), - ("punctuation.bracket".into(), rgba(0xa89984ff).into()), - ("comment".into(), rgba(0xa89984ff).into()), - ("preproc".into(), rgba(0xfbf1c7ff).into()), - ("predictive".into(), rgba(0x717363ff).into()), - ("punctuation.list_marker".into(), rgba(0xebdbb2ff).into()), - ], - }, - status_bar: rgba(0x4c4642ff).into(), - title_bar: rgba(0x4c4642ff).into(), - toolbar: rgba(0x282828ff).into(), - tab_bar: rgba(0x3a3735ff).into(), - editor: rgba(0x282828ff).into(), - editor_subheader: rgba(0x3a3735ff).into(), - editor_active_line: rgba(0x3a3735ff).into(), - terminal: rgba(0x282828ff).into(), - image_fallback_background: rgba(0x4c4642ff).into(), - git_created: rgba(0xb7bb26ff).into(), - git_modified: rgba(0x83a598ff).into(), - git_deleted: rgba(0xfb4a35ff).into(), - git_conflict: rgba(0xf9bd2fff).into(), - git_ignored: rgba(0x998b78ff).into(), - git_renamed: rgba(0xf9bd2fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb7bb26ff).into(), - selection: rgba(0xb7bb263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa89984ff).into(), - selection: rgba(0xa899843d).into(), - }, - PlayerTheme { - cursor: rgba(0xfd801bff).into(), - selection: rgba(0xfd801b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd3869bff).into(), - selection: rgba(0xd3869b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8ec07cff).into(), - selection: rgba(0x8ec07c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfb4a35ff).into(), - selection: rgba(0xfb4a353d).into(), - }, - PlayerTheme { - cursor: rgba(0xf9bd2fff).into(), - selection: rgba(0xf9bd2f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_dark_hard.rs b/crates/theme2/src/themes/gruvbox_dark_hard.rs deleted file mode 100644 index 159ab28325..0000000000 --- a/crates/theme2/src/themes/gruvbox_dark_hard.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_dark_hard() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Dark Hard".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b534dff).into(), - border_variant: rgba(0x5b534dff).into(), - border_focused: rgba(0x303a36ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x4c4642ff).into(), - surface: rgba(0x393634ff).into(), - background: rgba(0x4c4642ff).into(), - filled_element: rgba(0x4c4642ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1e2321ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1e2321ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfbf1c7ff).into(), - text_muted: rgba(0xc5b597ff).into(), - text_placeholder: rgba(0xfb4a35ff).into(), - text_disabled: rgba(0x998b78ff).into(), - text_accent: rgba(0x83a598ff).into(), - icon_muted: rgba(0xc5b597ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("primary".into(), rgba(0xebdbb2ff).into()), - ("label".into(), rgba(0x83a598ff).into()), - ("punctuation.delimiter".into(), rgba(0xe5d5adff).into()), - ("variant".into(), rgba(0x83a598ff).into()), - ("type".into(), rgba(0xfabd2eff).into()), - ("string.regex".into(), rgba(0xfe7f18ff).into()), - ("function.builtin".into(), rgba(0xfb4833ff).into()), - ("title".into(), rgba(0xb8bb25ff).into()), - ("string".into(), rgba(0xb8bb25ff).into()), - ("operator".into(), rgba(0x8ec07cff).into()), - ("embedded".into(), rgba(0x8ec07cff).into()), - ("punctuation.bracket".into(), rgba(0xa89984ff).into()), - ("string.special".into(), rgba(0xd3869bff).into()), - ("attribute".into(), rgba(0x83a598ff).into()), - ("comment".into(), rgba(0xa89984ff).into()), - ("link_text".into(), rgba(0x8ec07cff).into()), - ("punctuation.special".into(), rgba(0xe5d5adff).into()), - ("punctuation.list_marker".into(), rgba(0xebdbb2ff).into()), - ("comment.doc".into(), rgba(0xc6b697ff).into()), - ("preproc".into(), rgba(0xfbf1c7ff).into()), - ("text.literal".into(), rgba(0x83a598ff).into()), - ("function".into(), rgba(0xb8bb25ff).into()), - ("predictive".into(), rgba(0x717363ff).into()), - ("emphasis.strong".into(), rgba(0x83a598ff).into()), - ("punctuation".into(), rgba(0xd5c4a1ff).into()), - ("string.special.symbol".into(), rgba(0x8ec07cff).into()), - ("property".into(), rgba(0xebdbb2ff).into()), - ("keyword".into(), rgba(0xfb4833ff).into()), - ("constructor".into(), rgba(0x83a598ff).into()), - ("tag".into(), rgba(0x8ec07cff).into()), - ("variable".into(), rgba(0x83a598ff).into()), - ("enum".into(), rgba(0xfe7f18ff).into()), - ("hint".into(), rgba(0x8c957dff).into()), - ("number".into(), rgba(0xd3869bff).into()), - ("constant".into(), rgba(0xfabd2eff).into()), - ("boolean".into(), rgba(0xd3869bff).into()), - ("link_uri".into(), rgba(0xd3869bff).into()), - ("string.escape".into(), rgba(0xc6b697ff).into()), - ("emphasis".into(), rgba(0x83a598ff).into()), - ], - }, - status_bar: rgba(0x4c4642ff).into(), - title_bar: rgba(0x4c4642ff).into(), - toolbar: rgba(0x1d2021ff).into(), - tab_bar: rgba(0x393634ff).into(), - editor: rgba(0x1d2021ff).into(), - editor_subheader: rgba(0x393634ff).into(), - editor_active_line: rgba(0x393634ff).into(), - terminal: rgba(0x1d2021ff).into(), - image_fallback_background: rgba(0x4c4642ff).into(), - git_created: rgba(0xb7bb26ff).into(), - git_modified: rgba(0x83a598ff).into(), - git_deleted: rgba(0xfb4a35ff).into(), - git_conflict: rgba(0xf9bd2fff).into(), - git_ignored: rgba(0x998b78ff).into(), - git_renamed: rgba(0xf9bd2fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb7bb26ff).into(), - selection: rgba(0xb7bb263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa89984ff).into(), - selection: rgba(0xa899843d).into(), - }, - PlayerTheme { - cursor: rgba(0xfd801bff).into(), - selection: rgba(0xfd801b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd3869bff).into(), - selection: rgba(0xd3869b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8ec07cff).into(), - selection: rgba(0x8ec07c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfb4a35ff).into(), - selection: rgba(0xfb4a353d).into(), - }, - PlayerTheme { - cursor: rgba(0xf9bd2fff).into(), - selection: rgba(0xf9bd2f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_dark_soft.rs b/crates/theme2/src/themes/gruvbox_dark_soft.rs deleted file mode 100644 index 6a6423389e..0000000000 --- a/crates/theme2/src/themes/gruvbox_dark_soft.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_dark_soft() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Dark Soft".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x5b534dff).into(), - border_variant: rgba(0x5b534dff).into(), - border_focused: rgba(0x303a36ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x4c4642ff).into(), - surface: rgba(0x3b3735ff).into(), - background: rgba(0x4c4642ff).into(), - filled_element: rgba(0x4c4642ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x1e2321ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x1e2321ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfbf1c7ff).into(), - text_muted: rgba(0xc5b597ff).into(), - text_placeholder: rgba(0xfb4a35ff).into(), - text_disabled: rgba(0x998b78ff).into(), - text_accent: rgba(0x83a598ff).into(), - icon_muted: rgba(0xc5b597ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.special".into(), rgba(0xe5d5adff).into()), - ("attribute".into(), rgba(0x83a598ff).into()), - ("preproc".into(), rgba(0xfbf1c7ff).into()), - ("keyword".into(), rgba(0xfb4833ff).into()), - ("emphasis".into(), rgba(0x83a598ff).into()), - ("punctuation.delimiter".into(), rgba(0xe5d5adff).into()), - ("punctuation.bracket".into(), rgba(0xa89984ff).into()), - ("comment".into(), rgba(0xa89984ff).into()), - ("text.literal".into(), rgba(0x83a598ff).into()), - ("predictive".into(), rgba(0x717363ff).into()), - ("link_text".into(), rgba(0x8ec07cff).into()), - ("variant".into(), rgba(0x83a598ff).into()), - ("label".into(), rgba(0x83a598ff).into()), - ("function".into(), rgba(0xb8bb25ff).into()), - ("string.regex".into(), rgba(0xfe7f18ff).into()), - ("boolean".into(), rgba(0xd3869bff).into()), - ("number".into(), rgba(0xd3869bff).into()), - ("string.escape".into(), rgba(0xc6b697ff).into()), - ("constructor".into(), rgba(0x83a598ff).into()), - ("link_uri".into(), rgba(0xd3869bff).into()), - ("string.special.symbol".into(), rgba(0x8ec07cff).into()), - ("type".into(), rgba(0xfabd2eff).into()), - ("function.builtin".into(), rgba(0xfb4833ff).into()), - ("title".into(), rgba(0xb8bb25ff).into()), - ("primary".into(), rgba(0xebdbb2ff).into()), - ("tag".into(), rgba(0x8ec07cff).into()), - ("constant".into(), rgba(0xfabd2eff).into()), - ("emphasis.strong".into(), rgba(0x83a598ff).into()), - ("string.special".into(), rgba(0xd3869bff).into()), - ("hint".into(), rgba(0x8c957dff).into()), - ("comment.doc".into(), rgba(0xc6b697ff).into()), - ("property".into(), rgba(0xebdbb2ff).into()), - ("embedded".into(), rgba(0x8ec07cff).into()), - ("operator".into(), rgba(0x8ec07cff).into()), - ("punctuation".into(), rgba(0xd5c4a1ff).into()), - ("variable".into(), rgba(0x83a598ff).into()), - ("enum".into(), rgba(0xfe7f18ff).into()), - ("punctuation.list_marker".into(), rgba(0xebdbb2ff).into()), - ("string".into(), rgba(0xb8bb25ff).into()), - ], - }, - status_bar: rgba(0x4c4642ff).into(), - title_bar: rgba(0x4c4642ff).into(), - toolbar: rgba(0x32302fff).into(), - tab_bar: rgba(0x3b3735ff).into(), - editor: rgba(0x32302fff).into(), - editor_subheader: rgba(0x3b3735ff).into(), - editor_active_line: rgba(0x3b3735ff).into(), - terminal: rgba(0x32302fff).into(), - image_fallback_background: rgba(0x4c4642ff).into(), - git_created: rgba(0xb7bb26ff).into(), - git_modified: rgba(0x83a598ff).into(), - git_deleted: rgba(0xfb4a35ff).into(), - git_conflict: rgba(0xf9bd2fff).into(), - git_ignored: rgba(0x998b78ff).into(), - git_renamed: rgba(0xf9bd2fff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb7bb26ff).into(), - selection: rgba(0xb7bb263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa89984ff).into(), - selection: rgba(0xa899843d).into(), - }, - PlayerTheme { - cursor: rgba(0xfd801bff).into(), - selection: rgba(0xfd801b3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd3869bff).into(), - selection: rgba(0xd3869b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x8ec07cff).into(), - selection: rgba(0x8ec07c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfb4a35ff).into(), - selection: rgba(0xfb4a353d).into(), - }, - PlayerTheme { - cursor: rgba(0xf9bd2fff).into(), - selection: rgba(0xf9bd2f3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_light.rs b/crates/theme2/src/themes/gruvbox_light.rs deleted file mode 100644 index 7582f8bd8a..0000000000 --- a/crates/theme2/src/themes/gruvbox_light.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc8b899ff).into(), - border_variant: rgba(0xc8b899ff).into(), - border_focused: rgba(0xadc5ccff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xd9c8a4ff).into(), - surface: rgba(0xecddb4ff).into(), - background: rgba(0xd9c8a4ff).into(), - filled_element: rgba(0xd9c8a4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd2dee2ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd2dee2ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x282828ff).into(), - text_muted: rgba(0x5f5650ff).into(), - text_placeholder: rgba(0x9d0308ff).into(), - text_disabled: rgba(0x897b6eff).into(), - text_accent: rgba(0x0b6678ff).into(), - icon_muted: rgba(0x5f5650ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("number".into(), rgba(0x8f3e71ff).into()), - ("link_text".into(), rgba(0x427b58ff).into()), - ("string.special".into(), rgba(0x8f3e71ff).into()), - ("string.special.symbol".into(), rgba(0x427b58ff).into()), - ("function".into(), rgba(0x79740eff).into()), - ("title".into(), rgba(0x79740eff).into()), - ("emphasis".into(), rgba(0x0b6678ff).into()), - ("punctuation".into(), rgba(0x3c3836ff).into()), - ("string.escape".into(), rgba(0x5d544eff).into()), - ("type".into(), rgba(0xb57613ff).into()), - ("string".into(), rgba(0x79740eff).into()), - ("keyword".into(), rgba(0x9d0006ff).into()), - ("tag".into(), rgba(0x427b58ff).into()), - ("primary".into(), rgba(0x282828ff).into()), - ("link_uri".into(), rgba(0x8f3e71ff).into()), - ("comment.doc".into(), rgba(0x5d544eff).into()), - ("boolean".into(), rgba(0x8f3e71ff).into()), - ("embedded".into(), rgba(0x427b58ff).into()), - ("hint".into(), rgba(0x677562ff).into()), - ("emphasis.strong".into(), rgba(0x0b6678ff).into()), - ("operator".into(), rgba(0x427b58ff).into()), - ("label".into(), rgba(0x0b6678ff).into()), - ("comment".into(), rgba(0x7c6f64ff).into()), - ("function.builtin".into(), rgba(0x9d0006ff).into()), - ("punctuation.bracket".into(), rgba(0x665c54ff).into()), - ("text.literal".into(), rgba(0x066578ff).into()), - ("string.regex".into(), rgba(0xaf3a02ff).into()), - ("property".into(), rgba(0x282828ff).into()), - ("attribute".into(), rgba(0x0b6678ff).into()), - ("punctuation.delimiter".into(), rgba(0x413d3aff).into()), - ("constructor".into(), rgba(0x0b6678ff).into()), - ("variable".into(), rgba(0x066578ff).into()), - ("constant".into(), rgba(0xb57613ff).into()), - ("preproc".into(), rgba(0x282828ff).into()), - ("punctuation.special".into(), rgba(0x413d3aff).into()), - ("punctuation.list_marker".into(), rgba(0x282828ff).into()), - ("variant".into(), rgba(0x0b6678ff).into()), - ("predictive".into(), rgba(0x7c9780ff).into()), - ("enum".into(), rgba(0xaf3a02ff).into()), - ], - }, - status_bar: rgba(0xd9c8a4ff).into(), - title_bar: rgba(0xd9c8a4ff).into(), - toolbar: rgba(0xfbf1c7ff).into(), - tab_bar: rgba(0xecddb4ff).into(), - editor: rgba(0xfbf1c7ff).into(), - editor_subheader: rgba(0xecddb4ff).into(), - editor_active_line: rgba(0xecddb4ff).into(), - terminal: rgba(0xfbf1c7ff).into(), - image_fallback_background: rgba(0xd9c8a4ff).into(), - git_created: rgba(0x797410ff).into(), - git_modified: rgba(0x0b6678ff).into(), - git_deleted: rgba(0x9d0308ff).into(), - git_conflict: rgba(0xb57615ff).into(), - git_ignored: rgba(0x897b6eff).into(), - git_renamed: rgba(0xb57615ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x0b6678ff).into(), - selection: rgba(0x0b66783d).into(), - }, - PlayerTheme { - cursor: rgba(0x797410ff).into(), - selection: rgba(0x7974103d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c6f64ff).into(), - selection: rgba(0x7c6f643d).into(), - }, - PlayerTheme { - cursor: rgba(0xaf3a04ff).into(), - selection: rgba(0xaf3a043d).into(), - }, - PlayerTheme { - cursor: rgba(0x8f3f70ff).into(), - selection: rgba(0x8f3f703d).into(), - }, - PlayerTheme { - cursor: rgba(0x437b59ff).into(), - selection: rgba(0x437b593d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d0308ff).into(), - selection: rgba(0x9d03083d).into(), - }, - PlayerTheme { - cursor: rgba(0xb57615ff).into(), - selection: rgba(0xb576153d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_light_hard.rs b/crates/theme2/src/themes/gruvbox_light_hard.rs deleted file mode 100644 index e5e3fe54cf..0000000000 --- a/crates/theme2/src/themes/gruvbox_light_hard.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_light_hard() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Light Hard".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc8b899ff).into(), - border_variant: rgba(0xc8b899ff).into(), - border_focused: rgba(0xadc5ccff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xd9c8a4ff).into(), - surface: rgba(0xecddb5ff).into(), - background: rgba(0xd9c8a4ff).into(), - filled_element: rgba(0xd9c8a4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd2dee2ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd2dee2ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x282828ff).into(), - text_muted: rgba(0x5f5650ff).into(), - text_placeholder: rgba(0x9d0308ff).into(), - text_disabled: rgba(0x897b6eff).into(), - text_accent: rgba(0x0b6678ff).into(), - icon_muted: rgba(0x5f5650ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("label".into(), rgba(0x0b6678ff).into()), - ("hint".into(), rgba(0x677562ff).into()), - ("boolean".into(), rgba(0x8f3e71ff).into()), - ("function.builtin".into(), rgba(0x9d0006ff).into()), - ("constant".into(), rgba(0xb57613ff).into()), - ("preproc".into(), rgba(0x282828ff).into()), - ("predictive".into(), rgba(0x7c9780ff).into()), - ("string".into(), rgba(0x79740eff).into()), - ("comment.doc".into(), rgba(0x5d544eff).into()), - ("function".into(), rgba(0x79740eff).into()), - ("title".into(), rgba(0x79740eff).into()), - ("text.literal".into(), rgba(0x066578ff).into()), - ("punctuation.bracket".into(), rgba(0x665c54ff).into()), - ("string.escape".into(), rgba(0x5d544eff).into()), - ("punctuation.delimiter".into(), rgba(0x413d3aff).into()), - ("string.special.symbol".into(), rgba(0x427b58ff).into()), - ("type".into(), rgba(0xb57613ff).into()), - ("constructor".into(), rgba(0x0b6678ff).into()), - ("property".into(), rgba(0x282828ff).into()), - ("comment".into(), rgba(0x7c6f64ff).into()), - ("enum".into(), rgba(0xaf3a02ff).into()), - ("emphasis".into(), rgba(0x0b6678ff).into()), - ("embedded".into(), rgba(0x427b58ff).into()), - ("operator".into(), rgba(0x427b58ff).into()), - ("attribute".into(), rgba(0x0b6678ff).into()), - ("emphasis.strong".into(), rgba(0x0b6678ff).into()), - ("link_text".into(), rgba(0x427b58ff).into()), - ("punctuation.special".into(), rgba(0x413d3aff).into()), - ("punctuation.list_marker".into(), rgba(0x282828ff).into()), - ("variant".into(), rgba(0x0b6678ff).into()), - ("primary".into(), rgba(0x282828ff).into()), - ("number".into(), rgba(0x8f3e71ff).into()), - ("tag".into(), rgba(0x427b58ff).into()), - ("keyword".into(), rgba(0x9d0006ff).into()), - ("link_uri".into(), rgba(0x8f3e71ff).into()), - ("string.regex".into(), rgba(0xaf3a02ff).into()), - ("variable".into(), rgba(0x066578ff).into()), - ("string.special".into(), rgba(0x8f3e71ff).into()), - ("punctuation".into(), rgba(0x3c3836ff).into()), - ], - }, - status_bar: rgba(0xd9c8a4ff).into(), - title_bar: rgba(0xd9c8a4ff).into(), - toolbar: rgba(0xf9f5d7ff).into(), - tab_bar: rgba(0xecddb5ff).into(), - editor: rgba(0xf9f5d7ff).into(), - editor_subheader: rgba(0xecddb5ff).into(), - editor_active_line: rgba(0xecddb5ff).into(), - terminal: rgba(0xf9f5d7ff).into(), - image_fallback_background: rgba(0xd9c8a4ff).into(), - git_created: rgba(0x797410ff).into(), - git_modified: rgba(0x0b6678ff).into(), - git_deleted: rgba(0x9d0308ff).into(), - git_conflict: rgba(0xb57615ff).into(), - git_ignored: rgba(0x897b6eff).into(), - git_renamed: rgba(0xb57615ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x0b6678ff).into(), - selection: rgba(0x0b66783d).into(), - }, - PlayerTheme { - cursor: rgba(0x797410ff).into(), - selection: rgba(0x7974103d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c6f64ff).into(), - selection: rgba(0x7c6f643d).into(), - }, - PlayerTheme { - cursor: rgba(0xaf3a04ff).into(), - selection: rgba(0xaf3a043d).into(), - }, - PlayerTheme { - cursor: rgba(0x8f3f70ff).into(), - selection: rgba(0x8f3f703d).into(), - }, - PlayerTheme { - cursor: rgba(0x437b59ff).into(), - selection: rgba(0x437b593d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d0308ff).into(), - selection: rgba(0x9d03083d).into(), - }, - PlayerTheme { - cursor: rgba(0xb57615ff).into(), - selection: rgba(0xb576153d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/gruvbox_light_soft.rs b/crates/theme2/src/themes/gruvbox_light_soft.rs deleted file mode 100644 index 15574e2960..0000000000 --- a/crates/theme2/src/themes/gruvbox_light_soft.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn gruvbox_light_soft() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Gruvbox Light Soft".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc8b899ff).into(), - border_variant: rgba(0xc8b899ff).into(), - border_focused: rgba(0xadc5ccff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xd9c8a4ff).into(), - surface: rgba(0xecdcb3ff).into(), - background: rgba(0xd9c8a4ff).into(), - filled_element: rgba(0xd9c8a4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xd2dee2ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xd2dee2ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x282828ff).into(), - text_muted: rgba(0x5f5650ff).into(), - text_placeholder: rgba(0x9d0308ff).into(), - text_disabled: rgba(0x897b6eff).into(), - text_accent: rgba(0x0b6678ff).into(), - icon_muted: rgba(0x5f5650ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("preproc".into(), rgba(0x282828ff).into()), - ("punctuation.list_marker".into(), rgba(0x282828ff).into()), - ("string".into(), rgba(0x79740eff).into()), - ("constant".into(), rgba(0xb57613ff).into()), - ("keyword".into(), rgba(0x9d0006ff).into()), - ("string.special.symbol".into(), rgba(0x427b58ff).into()), - ("comment.doc".into(), rgba(0x5d544eff).into()), - ("hint".into(), rgba(0x677562ff).into()), - ("number".into(), rgba(0x8f3e71ff).into()), - ("enum".into(), rgba(0xaf3a02ff).into()), - ("emphasis".into(), rgba(0x0b6678ff).into()), - ("operator".into(), rgba(0x427b58ff).into()), - ("comment".into(), rgba(0x7c6f64ff).into()), - ("embedded".into(), rgba(0x427b58ff).into()), - ("type".into(), rgba(0xb57613ff).into()), - ("title".into(), rgba(0x79740eff).into()), - ("constructor".into(), rgba(0x0b6678ff).into()), - ("punctuation.delimiter".into(), rgba(0x413d3aff).into()), - ("function".into(), rgba(0x79740eff).into()), - ("link_uri".into(), rgba(0x8f3e71ff).into()), - ("emphasis.strong".into(), rgba(0x0b6678ff).into()), - ("boolean".into(), rgba(0x8f3e71ff).into()), - ("function.builtin".into(), rgba(0x9d0006ff).into()), - ("predictive".into(), rgba(0x7c9780ff).into()), - ("string.regex".into(), rgba(0xaf3a02ff).into()), - ("tag".into(), rgba(0x427b58ff).into()), - ("text.literal".into(), rgba(0x066578ff).into()), - ("punctuation".into(), rgba(0x3c3836ff).into()), - ("punctuation.bracket".into(), rgba(0x665c54ff).into()), - ("variable".into(), rgba(0x066578ff).into()), - ("attribute".into(), rgba(0x0b6678ff).into()), - ("string.special".into(), rgba(0x8f3e71ff).into()), - ("label".into(), rgba(0x0b6678ff).into()), - ("string.escape".into(), rgba(0x5d544eff).into()), - ("link_text".into(), rgba(0x427b58ff).into()), - ("punctuation.special".into(), rgba(0x413d3aff).into()), - ("property".into(), rgba(0x282828ff).into()), - ("variant".into(), rgba(0x0b6678ff).into()), - ("primary".into(), rgba(0x282828ff).into()), - ], - }, - status_bar: rgba(0xd9c8a4ff).into(), - title_bar: rgba(0xd9c8a4ff).into(), - toolbar: rgba(0xf2e5bcff).into(), - tab_bar: rgba(0xecdcb3ff).into(), - editor: rgba(0xf2e5bcff).into(), - editor_subheader: rgba(0xecdcb3ff).into(), - editor_active_line: rgba(0xecdcb3ff).into(), - terminal: rgba(0xf2e5bcff).into(), - image_fallback_background: rgba(0xd9c8a4ff).into(), - git_created: rgba(0x797410ff).into(), - git_modified: rgba(0x0b6678ff).into(), - git_deleted: rgba(0x9d0308ff).into(), - git_conflict: rgba(0xb57615ff).into(), - git_ignored: rgba(0x897b6eff).into(), - git_renamed: rgba(0xb57615ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x0b6678ff).into(), - selection: rgba(0x0b66783d).into(), - }, - PlayerTheme { - cursor: rgba(0x797410ff).into(), - selection: rgba(0x7974103d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c6f64ff).into(), - selection: rgba(0x7c6f643d).into(), - }, - PlayerTheme { - cursor: rgba(0xaf3a04ff).into(), - selection: rgba(0xaf3a043d).into(), - }, - PlayerTheme { - cursor: rgba(0x8f3f70ff).into(), - selection: rgba(0x8f3f703d).into(), - }, - PlayerTheme { - cursor: rgba(0x437b59ff).into(), - selection: rgba(0x437b593d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d0308ff).into(), - selection: rgba(0x9d03083d).into(), - }, - PlayerTheme { - cursor: rgba(0xb57615ff).into(), - selection: rgba(0xb576153d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/mod.rs b/crates/theme2/src/themes/mod.rs deleted file mode 100644 index 17cd5ac6e0..0000000000 --- a/crates/theme2/src/themes/mod.rs +++ /dev/null @@ -1,79 +0,0 @@ -mod andromeda; -mod atelier_cave_dark; -mod atelier_cave_light; -mod atelier_dune_dark; -mod atelier_dune_light; -mod atelier_estuary_dark; -mod atelier_estuary_light; -mod atelier_forest_dark; -mod atelier_forest_light; -mod atelier_heath_dark; -mod atelier_heath_light; -mod atelier_lakeside_dark; -mod atelier_lakeside_light; -mod atelier_plateau_dark; -mod atelier_plateau_light; -mod atelier_savanna_dark; -mod atelier_savanna_light; -mod atelier_seaside_dark; -mod atelier_seaside_light; -mod atelier_sulphurpool_dark; -mod atelier_sulphurpool_light; -mod ayu_dark; -mod ayu_light; -mod ayu_mirage; -mod gruvbox_dark; -mod gruvbox_dark_hard; -mod gruvbox_dark_soft; -mod gruvbox_light; -mod gruvbox_light_hard; -mod gruvbox_light_soft; -mod one_dark; -mod one_light; -mod rose_pine; -mod rose_pine_dawn; -mod rose_pine_moon; -mod sandcastle; -mod solarized_dark; -mod solarized_light; -mod summercamp; - -pub use andromeda::*; -pub use atelier_cave_dark::*; -pub use atelier_cave_light::*; -pub use atelier_dune_dark::*; -pub use atelier_dune_light::*; -pub use atelier_estuary_dark::*; -pub use atelier_estuary_light::*; -pub use atelier_forest_dark::*; -pub use atelier_forest_light::*; -pub use atelier_heath_dark::*; -pub use atelier_heath_light::*; -pub use atelier_lakeside_dark::*; -pub use atelier_lakeside_light::*; -pub use atelier_plateau_dark::*; -pub use atelier_plateau_light::*; -pub use atelier_savanna_dark::*; -pub use atelier_savanna_light::*; -pub use atelier_seaside_dark::*; -pub use atelier_seaside_light::*; -pub use atelier_sulphurpool_dark::*; -pub use atelier_sulphurpool_light::*; -pub use ayu_dark::*; -pub use ayu_light::*; -pub use ayu_mirage::*; -pub use gruvbox_dark::*; -pub use gruvbox_dark_hard::*; -pub use gruvbox_dark_soft::*; -pub use gruvbox_light::*; -pub use gruvbox_light_hard::*; -pub use gruvbox_light_soft::*; -pub use one_dark::*; -pub use one_light::*; -pub use rose_pine::*; -pub use rose_pine_dawn::*; -pub use rose_pine_moon::*; -pub use sandcastle::*; -pub use solarized_dark::*; -pub use solarized_light::*; -pub use summercamp::*; diff --git a/crates/theme2/src/themes/one_dark.rs b/crates/theme2/src/themes/one_dark.rs deleted file mode 100644 index c7408d1820..0000000000 --- a/crates/theme2/src/themes/one_dark.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn one_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "One Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x464b57ff).into(), - border_variant: rgba(0x464b57ff).into(), - border_focused: rgba(0x293b5bff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x3b414dff).into(), - surface: rgba(0x2f343eff).into(), - background: rgba(0x3b414dff).into(), - filled_element: rgba(0x3b414dff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x18243dff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x18243dff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xc8ccd4ff).into(), - text_muted: rgba(0x838994ff).into(), - text_placeholder: rgba(0xd07277ff).into(), - text_disabled: rgba(0x555a63ff).into(), - text_accent: rgba(0x74ade8ff).into(), - icon_muted: rgba(0x838994ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("keyword".into(), rgba(0xb477cfff).into()), - ("comment.doc".into(), rgba(0x878e98ff).into()), - ("variant".into(), rgba(0x73ade9ff).into()), - ("property".into(), rgba(0xd07277ff).into()), - ("function".into(), rgba(0x73ade9ff).into()), - ("type".into(), rgba(0x6eb4bfff).into()), - ("tag".into(), rgba(0x74ade8ff).into()), - ("string.escape".into(), rgba(0x878e98ff).into()), - ("punctuation.bracket".into(), rgba(0xb2b9c6ff).into()), - ("hint".into(), rgba(0x5a6f89ff).into()), - ("punctuation".into(), rgba(0xacb2beff).into()), - ("comment".into(), rgba(0x5d636fff).into()), - ("emphasis".into(), rgba(0x74ade8ff).into()), - ("punctuation.special".into(), rgba(0xb1574bff).into()), - ("link_uri".into(), rgba(0x6eb4bfff).into()), - ("string.regex".into(), rgba(0xbf956aff).into()), - ("constructor".into(), rgba(0x73ade9ff).into()), - ("operator".into(), rgba(0x6eb4bfff).into()), - ("constant".into(), rgba(0xdfc184ff).into()), - ("string.special".into(), rgba(0xbf956aff).into()), - ("emphasis.strong".into(), rgba(0xbf956aff).into()), - ("string.special.symbol".into(), rgba(0xbf956aff).into()), - ("primary".into(), rgba(0xacb2beff).into()), - ("preproc".into(), rgba(0xc8ccd4ff).into()), - ("string".into(), rgba(0xa1c181ff).into()), - ("punctuation.delimiter".into(), rgba(0xb2b9c6ff).into()), - ("embedded".into(), rgba(0xc8ccd4ff).into()), - ("enum".into(), rgba(0xd07277ff).into()), - ("variable.special".into(), rgba(0xbf956aff).into()), - ("text.literal".into(), rgba(0xa1c181ff).into()), - ("attribute".into(), rgba(0x74ade8ff).into()), - ("link_text".into(), rgba(0x73ade9ff).into()), - ("title".into(), rgba(0xd07277ff).into()), - ("predictive".into(), rgba(0x5a6a87ff).into()), - ("number".into(), rgba(0xbf956aff).into()), - ("label".into(), rgba(0x74ade8ff).into()), - ("variable".into(), rgba(0xc8ccd4ff).into()), - ("boolean".into(), rgba(0xbf956aff).into()), - ("punctuation.list_marker".into(), rgba(0xd07277ff).into()), - ], - }, - status_bar: rgba(0x3b414dff).into(), - title_bar: rgba(0x3b414dff).into(), - toolbar: rgba(0x282c33ff).into(), - tab_bar: rgba(0x2f343eff).into(), - editor: rgba(0x282c33ff).into(), - editor_subheader: rgba(0x2f343eff).into(), - editor_active_line: rgba(0x2f343eff).into(), - terminal: rgba(0x282c33ff).into(), - image_fallback_background: rgba(0x3b414dff).into(), - git_created: rgba(0xa1c181ff).into(), - git_modified: rgba(0x74ade8ff).into(), - git_deleted: rgba(0xd07277ff).into(), - git_conflict: rgba(0xdec184ff).into(), - git_ignored: rgba(0x555a63ff).into(), - git_renamed: rgba(0xdec184ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x74ade8ff).into(), - selection: rgba(0x74ade83d).into(), - }, - PlayerTheme { - cursor: rgba(0xa1c181ff).into(), - selection: rgba(0xa1c1813d).into(), - }, - PlayerTheme { - cursor: rgba(0xbe5046ff).into(), - selection: rgba(0xbe50463d).into(), - }, - PlayerTheme { - cursor: rgba(0xbf956aff).into(), - selection: rgba(0xbf956a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xb477cfff).into(), - selection: rgba(0xb477cf3d).into(), - }, - PlayerTheme { - cursor: rgba(0x6eb4bfff).into(), - selection: rgba(0x6eb4bf3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd07277ff).into(), - selection: rgba(0xd072773d).into(), - }, - PlayerTheme { - cursor: rgba(0xdec184ff).into(), - selection: rgba(0xdec1843d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/one_light.rs b/crates/theme2/src/themes/one_light.rs deleted file mode 100644 index ee802d57d3..0000000000 --- a/crates/theme2/src/themes/one_light.rs +++ /dev/null @@ -1,131 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn one_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "One Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xc9c9caff).into(), - border_variant: rgba(0xc9c9caff).into(), - border_focused: rgba(0xcbcdf6ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xdcdcddff).into(), - surface: rgba(0xebebecff).into(), - background: rgba(0xdcdcddff).into(), - filled_element: rgba(0xdcdcddff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xe2e2faff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xe2e2faff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x383a41ff).into(), - text_muted: rgba(0x7e8087ff).into(), - text_placeholder: rgba(0xd36151ff).into(), - text_disabled: rgba(0xa1a1a3ff).into(), - text_accent: rgba(0x5c78e2ff).into(), - icon_muted: rgba(0x7e8087ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.special.symbol".into(), rgba(0xad6e26ff).into()), - ("hint".into(), rgba(0x9294beff).into()), - ("link_uri".into(), rgba(0x3882b7ff).into()), - ("type".into(), rgba(0x3882b7ff).into()), - ("string.regex".into(), rgba(0xad6e26ff).into()), - ("constant".into(), rgba(0x669f59ff).into()), - ("function".into(), rgba(0x5b79e3ff).into()), - ("string.special".into(), rgba(0xad6e26ff).into()), - ("punctuation.bracket".into(), rgba(0x4d4f52ff).into()), - ("variable".into(), rgba(0x383a41ff).into()), - ("punctuation".into(), rgba(0x383a41ff).into()), - ("property".into(), rgba(0xd3604fff).into()), - ("string".into(), rgba(0x649f57ff).into()), - ("predictive".into(), rgba(0x9b9ec6ff).into()), - ("attribute".into(), rgba(0x5c78e2ff).into()), - ("number".into(), rgba(0xad6e25ff).into()), - ("constructor".into(), rgba(0x5c78e2ff).into()), - ("embedded".into(), rgba(0x383a41ff).into()), - ("title".into(), rgba(0xd3604fff).into()), - ("tag".into(), rgba(0x5c78e2ff).into()), - ("boolean".into(), rgba(0xad6e25ff).into()), - ("punctuation.list_marker".into(), rgba(0xd3604fff).into()), - ("variant".into(), rgba(0x5b79e3ff).into()), - ("emphasis".into(), rgba(0x5c78e2ff).into()), - ("link_text".into(), rgba(0x5b79e3ff).into()), - ("comment".into(), rgba(0xa2a3a7ff).into()), - ("punctuation.special".into(), rgba(0xb92b46ff).into()), - ("emphasis.strong".into(), rgba(0xad6e25ff).into()), - ("primary".into(), rgba(0x383a41ff).into()), - ("punctuation.delimiter".into(), rgba(0x4d4f52ff).into()), - ("label".into(), rgba(0x5c78e2ff).into()), - ("keyword".into(), rgba(0xa449abff).into()), - ("string.escape".into(), rgba(0x7c7e86ff).into()), - ("text.literal".into(), rgba(0x649f57ff).into()), - ("variable.special".into(), rgba(0xad6e25ff).into()), - ("comment.doc".into(), rgba(0x7c7e86ff).into()), - ("enum".into(), rgba(0xd3604fff).into()), - ("operator".into(), rgba(0x3882b7ff).into()), - ("preproc".into(), rgba(0x383a41ff).into()), - ], - }, - status_bar: rgba(0xdcdcddff).into(), - title_bar: rgba(0xdcdcddff).into(), - toolbar: rgba(0xfafafaff).into(), - tab_bar: rgba(0xebebecff).into(), - editor: rgba(0xfafafaff).into(), - editor_subheader: rgba(0xebebecff).into(), - editor_active_line: rgba(0xebebecff).into(), - terminal: rgba(0xfafafaff).into(), - image_fallback_background: rgba(0xdcdcddff).into(), - git_created: rgba(0x669f59ff).into(), - git_modified: rgba(0x5c78e2ff).into(), - git_deleted: rgba(0xd36151ff).into(), - git_conflict: rgba(0xdec184ff).into(), - git_ignored: rgba(0xa1a1a3ff).into(), - git_renamed: rgba(0xdec184ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x5c78e2ff).into(), - selection: rgba(0x5c78e23d).into(), - }, - PlayerTheme { - cursor: rgba(0x669f59ff).into(), - selection: rgba(0x669f593d).into(), - }, - PlayerTheme { - cursor: rgba(0x984ea5ff).into(), - selection: rgba(0x984ea53d).into(), - }, - PlayerTheme { - cursor: rgba(0xad6e26ff).into(), - selection: rgba(0xad6e263d).into(), - }, - PlayerTheme { - cursor: rgba(0xa349abff).into(), - selection: rgba(0xa349ab3d).into(), - }, - PlayerTheme { - cursor: rgba(0x3a82b7ff).into(), - selection: rgba(0x3a82b73d).into(), - }, - PlayerTheme { - cursor: rgba(0xd36151ff).into(), - selection: rgba(0xd361513d).into(), - }, - PlayerTheme { - cursor: rgba(0xdec184ff).into(), - selection: rgba(0xdec1843d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/rose_pine.rs b/crates/theme2/src/themes/rose_pine.rs deleted file mode 100644 index f3bd454cdc..0000000000 --- a/crates/theme2/src/themes/rose_pine.rs +++ /dev/null @@ -1,132 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn rose_pine() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Rosé Pine".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x423f55ff).into(), - border_variant: rgba(0x423f55ff).into(), - border_focused: rgba(0x435255ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x292738ff).into(), - surface: rgba(0x1c1b2aff).into(), - background: rgba(0x292738ff).into(), - filled_element: rgba(0x292738ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x2f3639ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x2f3639ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xe0def4ff).into(), - text_muted: rgba(0x74708dff).into(), - text_placeholder: rgba(0xea6e92ff).into(), - text_disabled: rgba(0x2f2b43ff).into(), - text_accent: rgba(0x9bced6ff).into(), - icon_muted: rgba(0x74708dff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.delimiter".into(), rgba(0x9d99b6ff).into()), - ("number".into(), rgba(0x5cc1a3ff).into()), - ("punctuation.special".into(), rgba(0x9d99b6ff).into()), - ("string.escape".into(), rgba(0x76728fff).into()), - ("title".into(), rgba(0xf5c177ff).into()), - ("constant".into(), rgba(0x5cc1a3ff).into()), - ("string.regex".into(), rgba(0xc4a7e6ff).into()), - ("type.builtin".into(), rgba(0x9ccfd8ff).into()), - ("comment.doc".into(), rgba(0x76728fff).into()), - ("primary".into(), rgba(0xe0def4ff).into()), - ("string.special".into(), rgba(0xc4a7e6ff).into()), - ("punctuation".into(), rgba(0x908caaff).into()), - ("string.special.symbol".into(), rgba(0xc4a7e6ff).into()), - ("variant".into(), rgba(0x9bced6ff).into()), - ("function.method".into(), rgba(0xebbcbaff).into()), - ("comment".into(), rgba(0x6e6a86ff).into()), - ("boolean".into(), rgba(0xebbcbaff).into()), - ("preproc".into(), rgba(0xe0def4ff).into()), - ("link_uri".into(), rgba(0xebbcbaff).into()), - ("hint".into(), rgba(0x5e768cff).into()), - ("attribute".into(), rgba(0x9bced6ff).into()), - ("text.literal".into(), rgba(0xc4a7e6ff).into()), - ("punctuation.list_marker".into(), rgba(0x9d99b6ff).into()), - ("operator".into(), rgba(0x30738fff).into()), - ("emphasis.strong".into(), rgba(0x9bced6ff).into()), - ("keyword".into(), rgba(0x30738fff).into()), - ("enum".into(), rgba(0xc4a7e6ff).into()), - ("tag".into(), rgba(0x9ccfd8ff).into()), - ("constructor".into(), rgba(0x9bced6ff).into()), - ("function".into(), rgba(0xebbcbaff).into()), - ("string".into(), rgba(0xf5c177ff).into()), - ("type".into(), rgba(0x9ccfd8ff).into()), - ("emphasis".into(), rgba(0x9bced6ff).into()), - ("link_text".into(), rgba(0x9ccfd8ff).into()), - ("property".into(), rgba(0x9bced6ff).into()), - ("predictive".into(), rgba(0x556b81ff).into()), - ("punctuation.bracket".into(), rgba(0x9d99b6ff).into()), - ("embedded".into(), rgba(0xe0def4ff).into()), - ("variable".into(), rgba(0xe0def4ff).into()), - ("label".into(), rgba(0x9bced6ff).into()), - ], - }, - status_bar: rgba(0x292738ff).into(), - title_bar: rgba(0x292738ff).into(), - toolbar: rgba(0x191724ff).into(), - tab_bar: rgba(0x1c1b2aff).into(), - editor: rgba(0x191724ff).into(), - editor_subheader: rgba(0x1c1b2aff).into(), - editor_active_line: rgba(0x1c1b2aff).into(), - terminal: rgba(0x191724ff).into(), - image_fallback_background: rgba(0x292738ff).into(), - git_created: rgba(0x5cc1a3ff).into(), - git_modified: rgba(0x9bced6ff).into(), - git_deleted: rgba(0xea6e92ff).into(), - git_conflict: rgba(0xf5c177ff).into(), - git_ignored: rgba(0x2f2b43ff).into(), - git_renamed: rgba(0xf5c177ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x9bced6ff).into(), - selection: rgba(0x9bced63d).into(), - }, - PlayerTheme { - cursor: rgba(0x5cc1a3ff).into(), - selection: rgba(0x5cc1a33d).into(), - }, - PlayerTheme { - cursor: rgba(0x9d7591ff).into(), - selection: rgba(0x9d75913d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0x31738fff).into(), - selection: rgba(0x31738f3d).into(), - }, - PlayerTheme { - cursor: rgba(0xea6e92ff).into(), - selection: rgba(0xea6e923d).into(), - }, - PlayerTheme { - cursor: rgba(0xf5c177ff).into(), - selection: rgba(0xf5c1773d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/rose_pine_dawn.rs b/crates/theme2/src/themes/rose_pine_dawn.rs deleted file mode 100644 index ba64bf9d99..0000000000 --- a/crates/theme2/src/themes/rose_pine_dawn.rs +++ /dev/null @@ -1,132 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn rose_pine_dawn() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Rosé Pine Dawn".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0xdcd6d5ff).into(), - border_variant: rgba(0xdcd6d5ff).into(), - border_focused: rgba(0xc3d7dbff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xdcd8d8ff).into(), - surface: rgba(0xfef9f2ff).into(), - background: rgba(0xdcd8d8ff).into(), - filled_element: rgba(0xdcd8d8ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdde9ebff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdde9ebff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x575279ff).into(), - text_muted: rgba(0x706c8cff).into(), - text_placeholder: rgba(0xb4647aff).into(), - text_disabled: rgba(0x938fa3ff).into(), - text_accent: rgba(0x57949fff).into(), - icon_muted: rgba(0x706c8cff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("primary".into(), rgba(0x575279ff).into()), - ("attribute".into(), rgba(0x57949fff).into()), - ("operator".into(), rgba(0x276983ff).into()), - ("boolean".into(), rgba(0xd7827dff).into()), - ("tag".into(), rgba(0x55949fff).into()), - ("enum".into(), rgba(0x9079a9ff).into()), - ("embedded".into(), rgba(0x575279ff).into()), - ("label".into(), rgba(0x57949fff).into()), - ("function.method".into(), rgba(0xd7827dff).into()), - ("punctuation.list_marker".into(), rgba(0x635e82ff).into()), - ("punctuation.delimiter".into(), rgba(0x635e82ff).into()), - ("string".into(), rgba(0xea9d34ff).into()), - ("type".into(), rgba(0x55949fff).into()), - ("string.regex".into(), rgba(0x9079a9ff).into()), - ("variable".into(), rgba(0x575279ff).into()), - ("constructor".into(), rgba(0x57949fff).into()), - ("punctuation.bracket".into(), rgba(0x635e82ff).into()), - ("emphasis".into(), rgba(0x57949fff).into()), - ("comment.doc".into(), rgba(0x6e6a8bff).into()), - ("comment".into(), rgba(0x9893a5ff).into()), - ("keyword".into(), rgba(0x276983ff).into()), - ("preproc".into(), rgba(0x575279ff).into()), - ("string.special".into(), rgba(0x9079a9ff).into()), - ("string.escape".into(), rgba(0x6e6a8bff).into()), - ("constant".into(), rgba(0x3daa8eff).into()), - ("property".into(), rgba(0x57949fff).into()), - ("punctuation.special".into(), rgba(0x635e82ff).into()), - ("text.literal".into(), rgba(0x9079a9ff).into()), - ("type.builtin".into(), rgba(0x55949fff).into()), - ("string.special.symbol".into(), rgba(0x9079a9ff).into()), - ("link_uri".into(), rgba(0xd7827dff).into()), - ("number".into(), rgba(0x3daa8eff).into()), - ("emphasis.strong".into(), rgba(0x57949fff).into()), - ("function".into(), rgba(0xd7827dff).into()), - ("title".into(), rgba(0xea9d34ff).into()), - ("punctuation".into(), rgba(0x797593ff).into()), - ("link_text".into(), rgba(0x55949fff).into()), - ("variant".into(), rgba(0x57949fff).into()), - ("predictive".into(), rgba(0xa2acbeff).into()), - ("hint".into(), rgba(0x7a92aaff).into()), - ], - }, - status_bar: rgba(0xdcd8d8ff).into(), - title_bar: rgba(0xdcd8d8ff).into(), - toolbar: rgba(0xfaf4edff).into(), - tab_bar: rgba(0xfef9f2ff).into(), - editor: rgba(0xfaf4edff).into(), - editor_subheader: rgba(0xfef9f2ff).into(), - editor_active_line: rgba(0xfef9f2ff).into(), - terminal: rgba(0xfaf4edff).into(), - image_fallback_background: rgba(0xdcd8d8ff).into(), - git_created: rgba(0x3daa8eff).into(), - git_modified: rgba(0x57949fff).into(), - git_deleted: rgba(0xb4647aff).into(), - git_conflict: rgba(0xe99d35ff).into(), - git_ignored: rgba(0x938fa3ff).into(), - git_renamed: rgba(0xe99d35ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x57949fff).into(), - selection: rgba(0x57949f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x3daa8eff).into(), - selection: rgba(0x3daa8e3d).into(), - }, - PlayerTheme { - cursor: rgba(0x7c697fff).into(), - selection: rgba(0x7c697f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x9079a9ff).into(), - selection: rgba(0x9079a93d).into(), - }, - PlayerTheme { - cursor: rgba(0x9079a9ff).into(), - selection: rgba(0x9079a93d).into(), - }, - PlayerTheme { - cursor: rgba(0x296983ff).into(), - selection: rgba(0x2969833d).into(), - }, - PlayerTheme { - cursor: rgba(0xb4647aff).into(), - selection: rgba(0xb4647a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xe99d35ff).into(), - selection: rgba(0xe99d353d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/rose_pine_moon.rs b/crates/theme2/src/themes/rose_pine_moon.rs deleted file mode 100644 index 167b78afb5..0000000000 --- a/crates/theme2/src/themes/rose_pine_moon.rs +++ /dev/null @@ -1,132 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn rose_pine_moon() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Rosé Pine Moon".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x504c68ff).into(), - border_variant: rgba(0x504c68ff).into(), - border_focused: rgba(0x435255ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x38354eff).into(), - surface: rgba(0x28253cff).into(), - background: rgba(0x38354eff).into(), - filled_element: rgba(0x38354eff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x2f3639ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x2f3639ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xe0def4ff).into(), - text_muted: rgba(0x85819eff).into(), - text_placeholder: rgba(0xea6e92ff).into(), - text_disabled: rgba(0x605d7aff).into(), - text_accent: rgba(0x9bced6ff).into(), - icon_muted: rgba(0x85819eff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("type.builtin".into(), rgba(0x9ccfd8ff).into()), - ("variable".into(), rgba(0xe0def4ff).into()), - ("punctuation".into(), rgba(0x908caaff).into()), - ("number".into(), rgba(0x5cc1a3ff).into()), - ("comment".into(), rgba(0x6e6a86ff).into()), - ("string.special".into(), rgba(0xc4a7e6ff).into()), - ("string.escape".into(), rgba(0x8682a0ff).into()), - ("function.method".into(), rgba(0xea9a97ff).into()), - ("predictive".into(), rgba(0x516b83ff).into()), - ("punctuation.delimiter".into(), rgba(0xaeabc6ff).into()), - ("primary".into(), rgba(0xe0def4ff).into()), - ("link_text".into(), rgba(0x9ccfd8ff).into()), - ("string.regex".into(), rgba(0xc4a7e6ff).into()), - ("constructor".into(), rgba(0x9bced6ff).into()), - ("constant".into(), rgba(0x5cc1a3ff).into()), - ("emphasis.strong".into(), rgba(0x9bced6ff).into()), - ("function".into(), rgba(0xea9a97ff).into()), - ("hint".into(), rgba(0x728aa2ff).into()), - ("preproc".into(), rgba(0xe0def4ff).into()), - ("property".into(), rgba(0x9bced6ff).into()), - ("punctuation.list_marker".into(), rgba(0xaeabc6ff).into()), - ("emphasis".into(), rgba(0x9bced6ff).into()), - ("attribute".into(), rgba(0x9bced6ff).into()), - ("title".into(), rgba(0xf5c177ff).into()), - ("keyword".into(), rgba(0x3d8fb0ff).into()), - ("string".into(), rgba(0xf5c177ff).into()), - ("text.literal".into(), rgba(0xc4a7e6ff).into()), - ("embedded".into(), rgba(0xe0def4ff).into()), - ("comment.doc".into(), rgba(0x8682a0ff).into()), - ("variant".into(), rgba(0x9bced6ff).into()), - ("label".into(), rgba(0x9bced6ff).into()), - ("punctuation.special".into(), rgba(0xaeabc6ff).into()), - ("string.special.symbol".into(), rgba(0xc4a7e6ff).into()), - ("tag".into(), rgba(0x9ccfd8ff).into()), - ("enum".into(), rgba(0xc4a7e6ff).into()), - ("boolean".into(), rgba(0xea9a97ff).into()), - ("punctuation.bracket".into(), rgba(0xaeabc6ff).into()), - ("operator".into(), rgba(0x3d8fb0ff).into()), - ("type".into(), rgba(0x9ccfd8ff).into()), - ("link_uri".into(), rgba(0xea9a97ff).into()), - ], - }, - status_bar: rgba(0x38354eff).into(), - title_bar: rgba(0x38354eff).into(), - toolbar: rgba(0x232136ff).into(), - tab_bar: rgba(0x28253cff).into(), - editor: rgba(0x232136ff).into(), - editor_subheader: rgba(0x28253cff).into(), - editor_active_line: rgba(0x28253cff).into(), - terminal: rgba(0x232136ff).into(), - image_fallback_background: rgba(0x38354eff).into(), - git_created: rgba(0x5cc1a3ff).into(), - git_modified: rgba(0x9bced6ff).into(), - git_deleted: rgba(0xea6e92ff).into(), - git_conflict: rgba(0xf5c177ff).into(), - git_ignored: rgba(0x605d7aff).into(), - git_renamed: rgba(0xf5c177ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x9bced6ff).into(), - selection: rgba(0x9bced63d).into(), - }, - PlayerTheme { - cursor: rgba(0x5cc1a3ff).into(), - selection: rgba(0x5cc1a33d).into(), - }, - PlayerTheme { - cursor: rgba(0xa683a0ff).into(), - selection: rgba(0xa683a03d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0xc4a7e6ff).into(), - selection: rgba(0xc4a7e63d).into(), - }, - PlayerTheme { - cursor: rgba(0x3e8fb0ff).into(), - selection: rgba(0x3e8fb03d).into(), - }, - PlayerTheme { - cursor: rgba(0xea6e92ff).into(), - selection: rgba(0xea6e923d).into(), - }, - PlayerTheme { - cursor: rgba(0xf5c177ff).into(), - selection: rgba(0xf5c1773d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/sandcastle.rs b/crates/theme2/src/themes/sandcastle.rs deleted file mode 100644 index 7fa0a27fb3..0000000000 --- a/crates/theme2/src/themes/sandcastle.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn sandcastle() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Sandcastle".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x3d4350ff).into(), - border_variant: rgba(0x3d4350ff).into(), - border_focused: rgba(0x223131ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x333944ff).into(), - surface: rgba(0x2b3038ff).into(), - background: rgba(0x333944ff).into(), - filled_element: rgba(0x333944ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x171e1eff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x171e1eff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfdf4c1ff).into(), - text_muted: rgba(0xa69782ff).into(), - text_placeholder: rgba(0xb3627aff).into(), - text_disabled: rgba(0x827568ff).into(), - text_accent: rgba(0x518b8bff).into(), - icon_muted: rgba(0xa69782ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("comment".into(), rgba(0xa89984ff).into()), - ("type".into(), rgba(0x83a598ff).into()), - ("preproc".into(), rgba(0xfdf4c1ff).into()), - ("punctuation.bracket".into(), rgba(0xd5c5a1ff).into()), - ("hint".into(), rgba(0x727d68ff).into()), - ("link_uri".into(), rgba(0x83a598ff).into()), - ("text.literal".into(), rgba(0xa07d3aff).into()), - ("enum".into(), rgba(0xa07d3aff).into()), - ("string.special".into(), rgba(0xa07d3aff).into()), - ("string".into(), rgba(0xa07d3aff).into()), - ("punctuation.special".into(), rgba(0xd5c5a1ff).into()), - ("keyword".into(), rgba(0x518b8bff).into()), - ("constructor".into(), rgba(0x518b8bff).into()), - ("predictive".into(), rgba(0x5c6152ff).into()), - ("title".into(), rgba(0xfdf4c1ff).into()), - ("variable".into(), rgba(0xfdf4c1ff).into()), - ("emphasis.strong".into(), rgba(0x518b8bff).into()), - ("primary".into(), rgba(0xfdf4c1ff).into()), - ("emphasis".into(), rgba(0x518b8bff).into()), - ("punctuation".into(), rgba(0xd5c5a1ff).into()), - ("constant".into(), rgba(0x83a598ff).into()), - ("link_text".into(), rgba(0xa07d3aff).into()), - ("punctuation.delimiter".into(), rgba(0xd5c5a1ff).into()), - ("embedded".into(), rgba(0xfdf4c1ff).into()), - ("string.special.symbol".into(), rgba(0xa07d3aff).into()), - ("tag".into(), rgba(0x518b8bff).into()), - ("punctuation.list_marker".into(), rgba(0xd5c5a1ff).into()), - ("operator".into(), rgba(0xa07d3aff).into()), - ("boolean".into(), rgba(0x83a598ff).into()), - ("function".into(), rgba(0xa07d3aff).into()), - ("attribute".into(), rgba(0x518b8bff).into()), - ("number".into(), rgba(0x83a598ff).into()), - ("string.escape".into(), rgba(0xa89984ff).into()), - ("comment.doc".into(), rgba(0xa89984ff).into()), - ("label".into(), rgba(0x518b8bff).into()), - ("string.regex".into(), rgba(0xa07d3aff).into()), - ("property".into(), rgba(0x518b8bff).into()), - ("variant".into(), rgba(0x518b8bff).into()), - ], - }, - status_bar: rgba(0x333944ff).into(), - title_bar: rgba(0x333944ff).into(), - toolbar: rgba(0x282c33ff).into(), - tab_bar: rgba(0x2b3038ff).into(), - editor: rgba(0x282c33ff).into(), - editor_subheader: rgba(0x2b3038ff).into(), - editor_active_line: rgba(0x2b3038ff).into(), - terminal: rgba(0x282c33ff).into(), - image_fallback_background: rgba(0x333944ff).into(), - git_created: rgba(0x83a598ff).into(), - git_modified: rgba(0x518b8bff).into(), - git_deleted: rgba(0xb3627aff).into(), - git_conflict: rgba(0xa07d3aff).into(), - git_ignored: rgba(0x827568ff).into(), - git_renamed: rgba(0xa07d3aff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x518b8bff).into(), - selection: rgba(0x518b8b3d).into(), - }, - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xa87222ff).into(), - selection: rgba(0xa872223d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3aff).into(), - selection: rgba(0xa07d3a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xd75f5fff).into(), - selection: rgba(0xd75f5f3d).into(), - }, - PlayerTheme { - cursor: rgba(0x83a598ff).into(), - selection: rgba(0x83a5983d).into(), - }, - PlayerTheme { - cursor: rgba(0xb3627aff).into(), - selection: rgba(0xb3627a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xa07d3aff).into(), - selection: rgba(0xa07d3a3d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/solarized_dark.rs b/crates/theme2/src/themes/solarized_dark.rs deleted file mode 100644 index 2e381a6e95..0000000000 --- a/crates/theme2/src/themes/solarized_dark.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn solarized_dark() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Solarized Dark".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x2b4e58ff).into(), - border_variant: rgba(0x2b4e58ff).into(), - border_focused: rgba(0x1b3149ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x073743ff).into(), - surface: rgba(0x04313bff).into(), - background: rgba(0x073743ff).into(), - filled_element: rgba(0x073743ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x141f2cff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x141f2cff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xfdf6e3ff).into(), - text_muted: rgba(0x93a1a1ff).into(), - text_placeholder: rgba(0xdc3330ff).into(), - text_disabled: rgba(0x6f8389ff).into(), - text_accent: rgba(0x278ad1ff).into(), - icon_muted: rgba(0x93a1a1ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("punctuation.special".into(), rgba(0xefe9d6ff).into()), - ("string".into(), rgba(0xcb4b16ff).into()), - ("variant".into(), rgba(0x278ad1ff).into()), - ("variable".into(), rgba(0xfdf6e3ff).into()), - ("string.special.symbol".into(), rgba(0xcb4b16ff).into()), - ("primary".into(), rgba(0xfdf6e3ff).into()), - ("type".into(), rgba(0x2ba198ff).into()), - ("boolean".into(), rgba(0x849903ff).into()), - ("string.special".into(), rgba(0xcb4b16ff).into()), - ("label".into(), rgba(0x278ad1ff).into()), - ("link_uri".into(), rgba(0x849903ff).into()), - ("constructor".into(), rgba(0x278ad1ff).into()), - ("hint".into(), rgba(0x4f8297ff).into()), - ("preproc".into(), rgba(0xfdf6e3ff).into()), - ("text.literal".into(), rgba(0xcb4b16ff).into()), - ("string.escape".into(), rgba(0x99a5a4ff).into()), - ("link_text".into(), rgba(0xcb4b16ff).into()), - ("comment".into(), rgba(0x99a5a4ff).into()), - ("enum".into(), rgba(0xcb4b16ff).into()), - ("constant".into(), rgba(0x849903ff).into()), - ("comment.doc".into(), rgba(0x99a5a4ff).into()), - ("emphasis".into(), rgba(0x278ad1ff).into()), - ("predictive".into(), rgba(0x3f718bff).into()), - ("attribute".into(), rgba(0x278ad1ff).into()), - ("punctuation.delimiter".into(), rgba(0xefe9d6ff).into()), - ("function".into(), rgba(0xb58902ff).into()), - ("emphasis.strong".into(), rgba(0x278ad1ff).into()), - ("tag".into(), rgba(0x278ad1ff).into()), - ("string.regex".into(), rgba(0xcb4b16ff).into()), - ("property".into(), rgba(0x278ad1ff).into()), - ("keyword".into(), rgba(0x278ad1ff).into()), - ("number".into(), rgba(0x849903ff).into()), - ("embedded".into(), rgba(0xfdf6e3ff).into()), - ("operator".into(), rgba(0xcb4b16ff).into()), - ("punctuation".into(), rgba(0xefe9d6ff).into()), - ("punctuation.bracket".into(), rgba(0xefe9d6ff).into()), - ("title".into(), rgba(0xfdf6e3ff).into()), - ("punctuation.list_marker".into(), rgba(0xefe9d6ff).into()), - ], - }, - status_bar: rgba(0x073743ff).into(), - title_bar: rgba(0x073743ff).into(), - toolbar: rgba(0x002a35ff).into(), - tab_bar: rgba(0x04313bff).into(), - editor: rgba(0x002a35ff).into(), - editor_subheader: rgba(0x04313bff).into(), - editor_active_line: rgba(0x04313bff).into(), - terminal: rgba(0x002a35ff).into(), - image_fallback_background: rgba(0x073743ff).into(), - git_created: rgba(0x849903ff).into(), - git_modified: rgba(0x278ad1ff).into(), - git_deleted: rgba(0xdc3330ff).into(), - git_conflict: rgba(0xb58902ff).into(), - git_ignored: rgba(0x6f8389ff).into(), - git_renamed: rgba(0xb58902ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x278ad1ff).into(), - selection: rgba(0x278ad13d).into(), - }, - PlayerTheme { - cursor: rgba(0x849903ff).into(), - selection: rgba(0x8499033d).into(), - }, - PlayerTheme { - cursor: rgba(0xd33781ff).into(), - selection: rgba(0xd337813d).into(), - }, - PlayerTheme { - cursor: rgba(0xcb4b16ff).into(), - selection: rgba(0xcb4b163d).into(), - }, - PlayerTheme { - cursor: rgba(0x6c71c4ff).into(), - selection: rgba(0x6c71c43d).into(), - }, - PlayerTheme { - cursor: rgba(0x2ba198ff).into(), - selection: rgba(0x2ba1983d).into(), - }, - PlayerTheme { - cursor: rgba(0xdc3330ff).into(), - selection: rgba(0xdc33303d).into(), - }, - PlayerTheme { - cursor: rgba(0xb58902ff).into(), - selection: rgba(0xb589023d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/solarized_light.rs b/crates/theme2/src/themes/solarized_light.rs deleted file mode 100644 index a959a0a9d1..0000000000 --- a/crates/theme2/src/themes/solarized_light.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn solarized_light() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Solarized Light".into(), - is_light: true, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x9faaa8ff).into(), - border_variant: rgba(0x9faaa8ff).into(), - border_focused: rgba(0xbfd3efff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0xcfd0c4ff).into(), - surface: rgba(0xf3eddaff).into(), - background: rgba(0xcfd0c4ff).into(), - filled_element: rgba(0xcfd0c4ff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0xdbe6f6ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0xdbe6f6ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0x002a35ff).into(), - text_muted: rgba(0x34555eff).into(), - text_placeholder: rgba(0xdc3330ff).into(), - text_disabled: rgba(0x6a7f86ff).into(), - text_accent: rgba(0x288bd1ff).into(), - icon_muted: rgba(0x34555eff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("string.escape".into(), rgba(0x30525bff).into()), - ("boolean".into(), rgba(0x849903ff).into()), - ("comment.doc".into(), rgba(0x30525bff).into()), - ("string.special".into(), rgba(0xcb4b17ff).into()), - ("punctuation".into(), rgba(0x04333eff).into()), - ("emphasis".into(), rgba(0x288bd1ff).into()), - ("type".into(), rgba(0x2ba198ff).into()), - ("preproc".into(), rgba(0x002a35ff).into()), - ("emphasis.strong".into(), rgba(0x288bd1ff).into()), - ("constant".into(), rgba(0x849903ff).into()), - ("title".into(), rgba(0x002a35ff).into()), - ("operator".into(), rgba(0xcb4b17ff).into()), - ("punctuation.bracket".into(), rgba(0x04333eff).into()), - ("link_uri".into(), rgba(0x849903ff).into()), - ("label".into(), rgba(0x288bd1ff).into()), - ("enum".into(), rgba(0xcb4b17ff).into()), - ("property".into(), rgba(0x288bd1ff).into()), - ("predictive".into(), rgba(0x679aafff).into()), - ("punctuation.special".into(), rgba(0x04333eff).into()), - ("text.literal".into(), rgba(0xcb4b17ff).into()), - ("string".into(), rgba(0xcb4b17ff).into()), - ("string.regex".into(), rgba(0xcb4b17ff).into()), - ("variable".into(), rgba(0x002a35ff).into()), - ("tag".into(), rgba(0x288bd1ff).into()), - ("string.special.symbol".into(), rgba(0xcb4b17ff).into()), - ("link_text".into(), rgba(0xcb4b17ff).into()), - ("punctuation.list_marker".into(), rgba(0x04333eff).into()), - ("keyword".into(), rgba(0x288bd1ff).into()), - ("constructor".into(), rgba(0x288bd1ff).into()), - ("attribute".into(), rgba(0x288bd1ff).into()), - ("variant".into(), rgba(0x288bd1ff).into()), - ("function".into(), rgba(0xb58903ff).into()), - ("primary".into(), rgba(0x002a35ff).into()), - ("hint".into(), rgba(0x5789a3ff).into()), - ("comment".into(), rgba(0x30525bff).into()), - ("number".into(), rgba(0x849903ff).into()), - ("punctuation.delimiter".into(), rgba(0x04333eff).into()), - ("embedded".into(), rgba(0x002a35ff).into()), - ], - }, - status_bar: rgba(0xcfd0c4ff).into(), - title_bar: rgba(0xcfd0c4ff).into(), - toolbar: rgba(0xfdf6e3ff).into(), - tab_bar: rgba(0xf3eddaff).into(), - editor: rgba(0xfdf6e3ff).into(), - editor_subheader: rgba(0xf3eddaff).into(), - editor_active_line: rgba(0xf3eddaff).into(), - terminal: rgba(0xfdf6e3ff).into(), - image_fallback_background: rgba(0xcfd0c4ff).into(), - git_created: rgba(0x849903ff).into(), - git_modified: rgba(0x288bd1ff).into(), - git_deleted: rgba(0xdc3330ff).into(), - git_conflict: rgba(0xb58903ff).into(), - git_ignored: rgba(0x6a7f86ff).into(), - git_renamed: rgba(0xb58903ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x288bd1ff).into(), - selection: rgba(0x288bd13d).into(), - }, - PlayerTheme { - cursor: rgba(0x849903ff).into(), - selection: rgba(0x8499033d).into(), - }, - PlayerTheme { - cursor: rgba(0xd33781ff).into(), - selection: rgba(0xd337813d).into(), - }, - PlayerTheme { - cursor: rgba(0xcb4b17ff).into(), - selection: rgba(0xcb4b173d).into(), - }, - PlayerTheme { - cursor: rgba(0x6c71c3ff).into(), - selection: rgba(0x6c71c33d).into(), - }, - PlayerTheme { - cursor: rgba(0x2ba198ff).into(), - selection: rgba(0x2ba1983d).into(), - }, - PlayerTheme { - cursor: rgba(0xdc3330ff).into(), - selection: rgba(0xdc33303d).into(), - }, - PlayerTheme { - cursor: rgba(0xb58903ff).into(), - selection: rgba(0xb589033d).into(), - }, - ], - } -} diff --git a/crates/theme2/src/themes/summercamp.rs b/crates/theme2/src/themes/summercamp.rs deleted file mode 100644 index c1e66aedd1..0000000000 --- a/crates/theme2/src/themes/summercamp.rs +++ /dev/null @@ -1,130 +0,0 @@ -use gpui2::rgba; - -use crate::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub fn summercamp() -> Theme { - Theme { - metadata: ThemeMetadata { - name: "Summercamp".into(), - is_light: false, - }, - transparent: rgba(0x00000000).into(), - mac_os_traffic_light_red: rgba(0xec695eff).into(), - mac_os_traffic_light_yellow: rgba(0xf4bf4eff).into(), - mac_os_traffic_light_green: rgba(0x61c553ff).into(), - border: rgba(0x302c21ff).into(), - border_variant: rgba(0x302c21ff).into(), - border_focused: rgba(0x193760ff).into(), - border_transparent: rgba(0x00000000).into(), - elevated_surface: rgba(0x2a261cff).into(), - surface: rgba(0x231f16ff).into(), - background: rgba(0x2a261cff).into(), - filled_element: rgba(0x2a261cff).into(), - filled_element_hover: rgba(0xffffff1e).into(), - filled_element_active: rgba(0xffffff28).into(), - filled_element_selected: rgba(0x0e2242ff).into(), - filled_element_disabled: rgba(0x00000000).into(), - ghost_element: rgba(0x00000000).into(), - ghost_element_hover: rgba(0xffffff14).into(), - ghost_element_active: rgba(0xffffff1e).into(), - ghost_element_selected: rgba(0x0e2242ff).into(), - ghost_element_disabled: rgba(0x00000000).into(), - text: rgba(0xf8f5deff).into(), - text_muted: rgba(0x736e55ff).into(), - text_placeholder: rgba(0xe35041ff).into(), - text_disabled: rgba(0x4c4735ff).into(), - text_accent: rgba(0x499befff).into(), - icon_muted: rgba(0x736e55ff).into(), - syntax: SyntaxTheme { - highlights: vec![ - ("predictive".into(), rgba(0x78434aff).into()), - ("title".into(), rgba(0xf8f5deff).into()), - ("primary".into(), rgba(0xf8f5deff).into()), - ("punctuation.special".into(), rgba(0xbfbb9bff).into()), - ("constant".into(), rgba(0x5dea5aff).into()), - ("string.regex".into(), rgba(0xfaa11cff).into()), - ("tag".into(), rgba(0x499befff).into()), - ("preproc".into(), rgba(0xf8f5deff).into()), - ("comment".into(), rgba(0x777159ff).into()), - ("punctuation.bracket".into(), rgba(0xbfbb9bff).into()), - ("constructor".into(), rgba(0x499befff).into()), - ("type".into(), rgba(0x5aeabbff).into()), - ("variable".into(), rgba(0xf8f5deff).into()), - ("operator".into(), rgba(0xfaa11cff).into()), - ("boolean".into(), rgba(0x5dea5aff).into()), - ("attribute".into(), rgba(0x499befff).into()), - ("link_text".into(), rgba(0xfaa11cff).into()), - ("string.escape".into(), rgba(0x777159ff).into()), - ("string.special".into(), rgba(0xfaa11cff).into()), - ("string.special.symbol".into(), rgba(0xfaa11cff).into()), - ("hint".into(), rgba(0x246e61ff).into()), - ("link_uri".into(), rgba(0x5dea5aff).into()), - ("comment.doc".into(), rgba(0x777159ff).into()), - ("emphasis".into(), rgba(0x499befff).into()), - ("punctuation".into(), rgba(0xbfbb9bff).into()), - ("text.literal".into(), rgba(0xfaa11cff).into()), - ("number".into(), rgba(0x5dea5aff).into()), - ("punctuation.delimiter".into(), rgba(0xbfbb9bff).into()), - ("label".into(), rgba(0x499befff).into()), - ("function".into(), rgba(0xf1fe28ff).into()), - ("property".into(), rgba(0x499befff).into()), - ("keyword".into(), rgba(0x499befff).into()), - ("embedded".into(), rgba(0xf8f5deff).into()), - ("string".into(), rgba(0xfaa11cff).into()), - ("punctuation.list_marker".into(), rgba(0xbfbb9bff).into()), - ("enum".into(), rgba(0xfaa11cff).into()), - ("emphasis.strong".into(), rgba(0x499befff).into()), - ("variant".into(), rgba(0x499befff).into()), - ], - }, - status_bar: rgba(0x2a261cff).into(), - title_bar: rgba(0x2a261cff).into(), - toolbar: rgba(0x1b1810ff).into(), - tab_bar: rgba(0x231f16ff).into(), - editor: rgba(0x1b1810ff).into(), - editor_subheader: rgba(0x231f16ff).into(), - editor_active_line: rgba(0x231f16ff).into(), - terminal: rgba(0x1b1810ff).into(), - image_fallback_background: rgba(0x2a261cff).into(), - git_created: rgba(0x5dea5aff).into(), - git_modified: rgba(0x499befff).into(), - git_deleted: rgba(0xe35041ff).into(), - git_conflict: rgba(0xf1fe28ff).into(), - git_ignored: rgba(0x4c4735ff).into(), - git_renamed: rgba(0xf1fe28ff).into(), - players: [ - PlayerTheme { - cursor: rgba(0x499befff).into(), - selection: rgba(0x499bef3d).into(), - }, - PlayerTheme { - cursor: rgba(0x5dea5aff).into(), - selection: rgba(0x5dea5a3d).into(), - }, - PlayerTheme { - cursor: rgba(0xf59be6ff).into(), - selection: rgba(0xf59be63d).into(), - }, - PlayerTheme { - cursor: rgba(0xfaa11cff).into(), - selection: rgba(0xfaa11c3d).into(), - }, - PlayerTheme { - cursor: rgba(0xfe8080ff).into(), - selection: rgba(0xfe80803d).into(), - }, - PlayerTheme { - cursor: rgba(0x5aeabbff).into(), - selection: rgba(0x5aeabb3d).into(), - }, - PlayerTheme { - cursor: rgba(0xe35041ff).into(), - selection: rgba(0xe350413d).into(), - }, - PlayerTheme { - cursor: rgba(0xf1fe28ff).into(), - selection: rgba(0xf1fe283d).into(), - }, - ], - } -} diff --git a/crates/theme_converter/Cargo.toml b/crates/theme_converter/Cargo.toml deleted file mode 100644 index 0ec692b7cc..0000000000 --- a/crates/theme_converter/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "theme_converter" -version = "0.1.0" -edition = "2021" -publish = false - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow.workspace = true -clap = { version = "4.4", features = ["derive", "string"] } -convert_case = "0.6.0" -gpui2 = { path = "../gpui2" } -log.workspace = true -rust-embed.workspace = true -serde.workspace = true -simplelog = "0.9" -theme2 = { path = "../theme2" } diff --git a/crates/theme_converter/src/main.rs b/crates/theme_converter/src/main.rs deleted file mode 100644 index cc0cdf9c99..0000000000 --- a/crates/theme_converter/src/main.rs +++ /dev/null @@ -1,390 +0,0 @@ -mod theme_printer; - -use std::borrow::Cow; -use std::collections::HashMap; -use std::fmt::{self, Debug}; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; -use std::str::FromStr; - -use anyhow::{anyhow, Context, Result}; -use clap::Parser; -use convert_case::{Case, Casing}; -use gpui2::{hsla, rgb, serde_json, AssetSource, Hsla, SharedString}; -use log::LevelFilter; -use rust_embed::RustEmbed; -use serde::de::Visitor; -use serde::{Deserialize, Deserializer}; -use simplelog::SimpleLogger; -use theme2::{PlayerTheme, SyntaxTheme}; - -use crate::theme_printer::ThemePrinter; - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Args { - /// The name of the theme to convert. - theme: String, -} - -fn main() -> Result<()> { - SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - - // let args = Args::parse(); - - let themes_path = PathBuf::from_str("crates/theme2/src/themes")?; - - let mut theme_modules = Vec::new(); - - for theme_path in Assets.list("themes/")? { - let (_, theme_name) = theme_path.split_once("themes/").unwrap(); - - if theme_name == ".gitkeep" { - continue; - } - - let (json_theme, legacy_theme) = load_theme(&theme_path)?; - - let theme = convert_theme(json_theme, legacy_theme)?; - - let theme_slug = theme - .metadata - .name - .as_ref() - .replace("é", "e") - .to_case(Case::Snake); - - let mut output_file = File::create(themes_path.join(format!("{theme_slug}.rs")))?; - - let theme_module = format!( - r#" - use gpui2::rgba; - - use crate::{{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}}; - - pub fn {theme_slug}() -> Theme {{ - {theme_definition} - }} - "#, - theme_definition = format!("{:#?}", ThemePrinter::new(theme)) - ); - - output_file.write_all(theme_module.as_bytes())?; - - theme_modules.push(theme_slug); - } - - let mut mod_rs_file = File::create(themes_path.join(format!("mod.rs")))?; - - let mod_rs_contents = format!( - r#" - {mod_statements} - - {use_statements} - "#, - mod_statements = theme_modules - .iter() - .map(|module| format!("mod {module};")) - .collect::>() - .join("\n"), - use_statements = theme_modules - .iter() - .map(|module| format!("pub use {module}::*;")) - .collect::>() - .join("\n") - ); - - mod_rs_file.write_all(mod_rs_contents.as_bytes())?; - - Ok(()) -} - -#[derive(RustEmbed)] -#[folder = "../../assets"] -#[include = "fonts/**/*"] -#[include = "icons/**/*"] -#[include = "themes/**/*"] -#[include = "sounds/**/*"] -#[include = "*.md"] -#[exclude = "*.DS_Store"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result> { - Self::get(path) - .map(|f| f.data) - .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) - } - - fn list(&self, path: &str) -> Result> { - Ok(Self::iter() - .filter(|p| p.starts_with(path)) - .map(SharedString::from) - .collect()) - } -} - -#[derive(Clone, Copy)] -pub struct PlayerThemeColors { - pub cursor: Hsla, - pub selection: Hsla, -} - -impl PlayerThemeColors { - pub fn new(theme: &LegacyTheme, ix: usize) -> Self { - if ix < theme.players.len() { - Self { - cursor: theme.players[ix].cursor, - selection: theme.players[ix].selection, - } - } else { - Self { - cursor: rgb::(0xff00ff), - selection: rgb::(0xff00ff), - } - } - } -} - -impl From for PlayerTheme { - fn from(value: PlayerThemeColors) -> Self { - Self { - cursor: value.cursor, - selection: value.selection, - } - } -} - -fn convert_theme(json_theme: JsonTheme, legacy_theme: LegacyTheme) -> Result { - let transparent = hsla(0.0, 0.0, 0.0, 0.0); - - let players: [PlayerTheme; 8] = [ - PlayerThemeColors::new(&legacy_theme, 0).into(), - PlayerThemeColors::new(&legacy_theme, 1).into(), - PlayerThemeColors::new(&legacy_theme, 2).into(), - PlayerThemeColors::new(&legacy_theme, 3).into(), - PlayerThemeColors::new(&legacy_theme, 4).into(), - PlayerThemeColors::new(&legacy_theme, 5).into(), - PlayerThemeColors::new(&legacy_theme, 6).into(), - PlayerThemeColors::new(&legacy_theme, 7).into(), - ]; - - let theme = theme2::Theme { - metadata: theme2::ThemeMetadata { - name: legacy_theme.name.clone().into(), - is_light: legacy_theme.is_light, - }, - transparent, - mac_os_traffic_light_red: rgb::(0xEC695E), - mac_os_traffic_light_yellow: rgb::(0xF4BF4F), - mac_os_traffic_light_green: rgb::(0x62C554), - border: legacy_theme.lowest.base.default.border, - border_variant: legacy_theme.lowest.variant.default.border, - border_focused: legacy_theme.lowest.accent.default.border, - border_transparent: transparent, - elevated_surface: legacy_theme.lowest.base.default.background, - surface: legacy_theme.middle.base.default.background, - background: legacy_theme.lowest.base.default.background, - filled_element: legacy_theme.lowest.base.default.background, - filled_element_hover: hsla(0.0, 0.0, 100.0, 0.12), - filled_element_active: hsla(0.0, 0.0, 100.0, 0.16), - filled_element_selected: legacy_theme.lowest.accent.default.background, - filled_element_disabled: transparent, - ghost_element: transparent, - ghost_element_hover: hsla(0.0, 0.0, 100.0, 0.08), - ghost_element_active: hsla(0.0, 0.0, 100.0, 0.12), - ghost_element_selected: legacy_theme.lowest.accent.default.background, - ghost_element_disabled: transparent, - text: legacy_theme.lowest.base.default.foreground, - text_muted: legacy_theme.lowest.variant.default.foreground, - /// TODO: map this to a real value - text_placeholder: legacy_theme.lowest.negative.default.foreground, - text_disabled: legacy_theme.lowest.base.disabled.foreground, - text_accent: legacy_theme.lowest.accent.default.foreground, - icon_muted: legacy_theme.lowest.variant.default.foreground, - syntax: SyntaxTheme { - highlights: json_theme - .editor - .syntax - .iter() - .map(|(token, style)| (token.clone(), style.color.clone().into())) - .collect(), - }, - status_bar: legacy_theme.lowest.base.default.background, - title_bar: legacy_theme.lowest.base.default.background, - toolbar: legacy_theme.highest.base.default.background, - tab_bar: legacy_theme.middle.base.default.background, - editor: legacy_theme.highest.base.default.background, - editor_subheader: legacy_theme.middle.base.default.background, - terminal: legacy_theme.highest.base.default.background, - editor_active_line: legacy_theme.highest.on.default.background, - image_fallback_background: legacy_theme.lowest.base.default.background, - - git_created: legacy_theme.lowest.positive.default.foreground, - git_modified: legacy_theme.lowest.accent.default.foreground, - git_deleted: legacy_theme.lowest.negative.default.foreground, - git_conflict: legacy_theme.lowest.warning.default.foreground, - git_ignored: legacy_theme.lowest.base.disabled.foreground, - git_renamed: legacy_theme.lowest.warning.default.foreground, - - players, - }; - - Ok(theme) -} - -#[derive(Deserialize)] -struct JsonTheme { - pub editor: JsonEditorTheme, - pub base_theme: serde_json::Value, -} - -#[derive(Deserialize)] -struct JsonEditorTheme { - pub syntax: HashMap, -} - -#[derive(Deserialize)] -struct JsonSyntaxStyle { - pub color: Hsla, -} - -/// Loads the [`Theme`] with the given name. -fn load_theme(theme_path: &str) -> Result<(JsonTheme, LegacyTheme)> { - let theme_contents = - Assets::get(theme_path).with_context(|| format!("theme file not found: '{theme_path}'"))?; - - let json_theme: JsonTheme = serde_json::from_str(std::str::from_utf8(&theme_contents.data)?) - .context("failed to parse legacy theme")?; - - let legacy_theme: LegacyTheme = serde_json::from_value(json_theme.base_theme.clone()) - .context("failed to parse `base_theme`")?; - - Ok((json_theme, legacy_theme)) -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct LegacyTheme { - pub name: String, - pub is_light: bool, - pub lowest: Layer, - pub middle: Layer, - pub highest: Layer, - pub popover_shadow: Shadow, - pub modal_shadow: Shadow, - #[serde(deserialize_with = "deserialize_player_colors")] - pub players: Vec, - #[serde(deserialize_with = "deserialize_syntax_colors")] - pub syntax: HashMap, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Layer { - pub base: StyleSet, - pub variant: StyleSet, - pub on: StyleSet, - pub accent: StyleSet, - pub positive: StyleSet, - pub warning: StyleSet, - pub negative: StyleSet, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct StyleSet { - #[serde(rename = "default")] - pub default: ContainerColors, - pub hovered: ContainerColors, - pub pressed: ContainerColors, - pub active: ContainerColors, - pub disabled: ContainerColors, - pub inverted: ContainerColors, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct ContainerColors { - pub background: Hsla, - pub foreground: Hsla, - pub border: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct PlayerColors { - pub selection: Hsla, - pub cursor: Hsla, -} - -#[derive(Deserialize, Clone, Default, Debug)] -pub struct Shadow { - pub blur: u8, - pub color: Hsla, - pub offset: Vec, -} - -fn deserialize_player_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct PlayerArrayVisitor; - - impl<'de> Visitor<'de> for PlayerArrayVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("an object with integer keys") - } - - fn visit_map>( - self, - mut map: A, - ) -> Result { - let mut players = Vec::with_capacity(8); - while let Some((key, value)) = map.next_entry::()? { - if key < 8 { - players.push(value); - } else { - return Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(key as u64), - &"a key in range 0..7", - )); - } - } - Ok(players) - } - } - - deserializer.deserialize_map(PlayerArrayVisitor) -} - -fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - #[derive(Deserialize)] - struct ColorWrapper { - color: Hsla, - } - - struct SyntaxVisitor; - - impl<'de> Visitor<'de> for SyntaxVisitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map with keys and objects with a single color field as values") - } - - fn visit_map(self, mut map: M) -> Result, M::Error> - where - M: serde::de::MapAccess<'de>, - { - let mut result = HashMap::new(); - while let Some(key) = map.next_key()? { - let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla - result.insert(key, wrapper.color); - } - Ok(result) - } - } - deserializer.deserialize_map(SyntaxVisitor) -} diff --git a/crates/theme_converter/src/theme_printer.rs b/crates/theme_converter/src/theme_printer.rs deleted file mode 100644 index 3a9bdb159b..0000000000 --- a/crates/theme_converter/src/theme_printer.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::fmt::{self, Debug}; - -use gpui2::{Hsla, Rgba}; -use theme2::{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}; - -pub struct ThemePrinter(Theme); - -impl ThemePrinter { - pub fn new(theme: Theme) -> Self { - Self(theme) - } -} - -struct HslaPrinter(Hsla); - -impl Debug for HslaPrinter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", IntoPrinter(&Rgba::from(self.0))) - } -} - -struct IntoPrinter<'a, D: Debug>(&'a D); - -impl<'a, D: Debug> Debug for IntoPrinter<'a, D> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}.into()", self.0) - } -} - -impl Debug for ThemePrinter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Theme") - .field("metadata", &ThemeMetadataPrinter(self.0.metadata.clone())) - .field("transparent", &HslaPrinter(self.0.transparent)) - .field( - "mac_os_traffic_light_red", - &HslaPrinter(self.0.mac_os_traffic_light_red), - ) - .field( - "mac_os_traffic_light_yellow", - &HslaPrinter(self.0.mac_os_traffic_light_yellow), - ) - .field( - "mac_os_traffic_light_green", - &HslaPrinter(self.0.mac_os_traffic_light_green), - ) - .field("border", &HslaPrinter(self.0.border)) - .field("border_variant", &HslaPrinter(self.0.border_variant)) - .field("border_focused", &HslaPrinter(self.0.border_focused)) - .field( - "border_transparent", - &HslaPrinter(self.0.border_transparent), - ) - .field("elevated_surface", &HslaPrinter(self.0.elevated_surface)) - .field("surface", &HslaPrinter(self.0.surface)) - .field("background", &HslaPrinter(self.0.background)) - .field("filled_element", &HslaPrinter(self.0.filled_element)) - .field( - "filled_element_hover", - &HslaPrinter(self.0.filled_element_hover), - ) - .field( - "filled_element_active", - &HslaPrinter(self.0.filled_element_active), - ) - .field( - "filled_element_selected", - &HslaPrinter(self.0.filled_element_selected), - ) - .field( - "filled_element_disabled", - &HslaPrinter(self.0.filled_element_disabled), - ) - .field("ghost_element", &HslaPrinter(self.0.ghost_element)) - .field( - "ghost_element_hover", - &HslaPrinter(self.0.ghost_element_hover), - ) - .field( - "ghost_element_active", - &HslaPrinter(self.0.ghost_element_active), - ) - .field( - "ghost_element_selected", - &HslaPrinter(self.0.ghost_element_selected), - ) - .field( - "ghost_element_disabled", - &HslaPrinter(self.0.ghost_element_disabled), - ) - .field("text", &HslaPrinter(self.0.text)) - .field("text_muted", &HslaPrinter(self.0.text_muted)) - .field("text_placeholder", &HslaPrinter(self.0.text_placeholder)) - .field("text_disabled", &HslaPrinter(self.0.text_disabled)) - .field("text_accent", &HslaPrinter(self.0.text_accent)) - .field("icon_muted", &HslaPrinter(self.0.icon_muted)) - .field("syntax", &SyntaxThemePrinter(self.0.syntax.clone())) - .field("status_bar", &HslaPrinter(self.0.status_bar)) - .field("title_bar", &HslaPrinter(self.0.title_bar)) - .field("toolbar", &HslaPrinter(self.0.toolbar)) - .field("tab_bar", &HslaPrinter(self.0.tab_bar)) - .field("editor", &HslaPrinter(self.0.editor)) - .field("editor_subheader", &HslaPrinter(self.0.editor_subheader)) - .field( - "editor_active_line", - &HslaPrinter(self.0.editor_active_line), - ) - .field("terminal", &HslaPrinter(self.0.terminal)) - .field( - "image_fallback_background", - &HslaPrinter(self.0.image_fallback_background), - ) - .field("git_created", &HslaPrinter(self.0.git_created)) - .field("git_modified", &HslaPrinter(self.0.git_modified)) - .field("git_deleted", &HslaPrinter(self.0.git_deleted)) - .field("git_conflict", &HslaPrinter(self.0.git_conflict)) - .field("git_ignored", &HslaPrinter(self.0.git_ignored)) - .field("git_renamed", &HslaPrinter(self.0.git_renamed)) - .field("players", &self.0.players.map(PlayerThemePrinter)) - .finish() - } -} - -pub struct ThemeMetadataPrinter(ThemeMetadata); - -impl Debug for ThemeMetadataPrinter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ThemeMetadata") - .field("name", &IntoPrinter(&self.0.name)) - .field("is_light", &self.0.is_light) - .finish() - } -} - -pub struct SyntaxThemePrinter(SyntaxTheme); - -impl Debug for SyntaxThemePrinter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SyntaxTheme") - .field( - "highlights", - &VecPrinter( - &self - .0 - .highlights - .iter() - .map(|(token, highlight)| { - (IntoPrinter(token), HslaPrinter(highlight.color.unwrap())) - }) - .collect(), - ), - ) - .finish() - } -} - -pub struct VecPrinter<'a, T>(&'a Vec); - -impl<'a, T: Debug> Debug for VecPrinter<'a, T> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "vec!{:?}", &self.0) - } -} - -pub struct PlayerThemePrinter(PlayerTheme); - -impl Debug for PlayerThemePrinter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("PlayerTheme") - .field("cursor", &HslaPrinter(self.0.cursor)) - .field("selection", &HslaPrinter(self.0.selection)) - .finish() - } -} From 272f85646050d997c755ca3885080ae6ff1a4195 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 04:33:51 +0100 Subject: [PATCH 06/15] Use `Refineable` for `ThemeStyles` (#3196) This PR updates the `ThemeStyles` struct to use the `Refineable` trait instead of a custom declarative macro for generating refinements. Release Notes: - N/A --- crates/theme2/src/colors.rs | 23 ++++++++-------- crates/theme2/src/default_theme.rs | 6 ++--- crates/theme2/src/syntax.rs | 2 +- crates/theme2/src/theme2.rs | 3 +-- crates/theme2/src/utils.rs | 43 ------------------------------ 5 files changed, 17 insertions(+), 60 deletions(-) delete mode 100644 crates/theme2/src/utils.rs diff --git a/crates/theme2/src/colors.rs b/crates/theme2/src/colors.rs index 2a59fa41bd..02c93a2e98 100644 --- a/crates/theme2/src/colors.rs +++ b/crates/theme2/src/colors.rs @@ -1,8 +1,9 @@ use gpui2::Hsla; use refineable::Refineable; -use crate::{generate_struct_with_overrides, SyntaxTheme}; +use crate::SyntaxTheme; +#[derive(Clone)] pub struct SystemColors { pub transparent: Hsla, pub mac_os_traffic_light_red: Hsla, @@ -17,6 +18,7 @@ pub struct PlayerColor { pub selection: Hsla, } +#[derive(Clone)] pub struct PlayerColors(pub Vec); #[derive(Refineable, Clone, Debug)] @@ -46,7 +48,7 @@ pub struct GitStatusColors { pub renamed: Hsla, } -#[derive(Refineable, Clone, Debug)] +#[derive(Refineable, Clone, Debug, Default)] #[refineable(debug)] pub struct ThemeColors { pub border: Hsla, @@ -86,15 +88,14 @@ pub struct ThemeColors { pub editor_active_line: Hsla, } -generate_struct_with_overrides! { - ThemeStyle, - ThemeStyleOverrides, - system: SystemColors, - colors: ThemeColors, - status: StatusColors, - git: GitStatusColors, - player: PlayerColors, - syntax: SyntaxTheme +#[derive(Refineable, Clone)] +pub struct ThemeStyles { + pub system: SystemColors, + pub colors: ThemeColors, + pub status: StatusColors, + pub git: GitStatusColors, + pub player: PlayerColors, + pub syntax: SyntaxTheme, } #[cfg(test)] diff --git a/crates/theme2/src/default_theme.rs b/crates/theme2/src/default_theme.rs index 26a55b5e0d..d7360b6f71 100644 --- a/crates/theme2/src/default_theme.rs +++ b/crates/theme2/src/default_theme.rs @@ -1,5 +1,5 @@ use crate::{ - colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyle}, + colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles}, default_color_scales, Appearance, SyntaxTheme, ThemeFamily, ThemeVariant, }; @@ -8,7 +8,7 @@ fn zed_pro_daylight() -> ThemeVariant { id: "zed_pro_daylight".to_string(), name: "Zed Pro Daylight".into(), appearance: Appearance::Light, - styles: ThemeStyle { + styles: ThemeStyles { system: SystemColors::default(), colors: ThemeColors::default_light(), status: StatusColors::default(), @@ -24,7 +24,7 @@ pub(crate) fn zed_pro_moonlight() -> ThemeVariant { id: "zed_pro_moonlight".to_string(), name: "Zed Pro Moonlight".into(), appearance: Appearance::Dark, - styles: ThemeStyle { + styles: ThemeStyles { system: SystemColors::default(), colors: ThemeColors::default_dark(), status: StatusColors::default(), diff --git a/crates/theme2/src/syntax.rs b/crates/theme2/src/syntax.rs index 1cf8564bca..a8127f0c44 100644 --- a/crates/theme2/src/syntax.rs +++ b/crates/theme2/src/syntax.rs @@ -1,6 +1,6 @@ use gpui2::{HighlightStyle, Hsla}; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct SyntaxTheme { pub highlights: Vec<(String, HighlightStyle)>, } diff --git a/crates/theme2/src/theme2.rs b/crates/theme2/src/theme2.rs index 372e976bd3..34727eaf89 100644 --- a/crates/theme2/src/theme2.rs +++ b/crates/theme2/src/theme2.rs @@ -5,7 +5,6 @@ mod registry; mod scale; mod settings; mod syntax; -mod utils; pub use colors::*; pub use default_colors::*; @@ -55,7 +54,7 @@ pub struct ThemeVariant { pub(crate) id: String, pub name: SharedString, pub appearance: Appearance, - pub styles: ThemeStyle, + pub styles: ThemeStyles, } impl ThemeVariant { diff --git a/crates/theme2/src/utils.rs b/crates/theme2/src/utils.rs deleted file mode 100644 index ccdcde4274..0000000000 --- a/crates/theme2/src/utils.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// This macro generates a struct and a corresponding struct with optional fields. -/// -/// It takes as input the name of the struct to be generated, the name of the struct with optional fields, -/// and a list of field names along with their types. -/// -/// # Example -/// ``` -/// generate_struct_with_overrides!( -/// MyStruct, -/// MyStructOverride, -/// field1: i32, -/// field2: String -/// ); -/// ``` -/// This will generate the following structs: -/// ``` -/// pub struct MyStruct { -/// pub field1: i32, -/// pub field2: String, -/// } -/// -/// pub struct MyStructOverride { -/// pub field1: Option, -/// pub field2: Option, -/// } -/// ``` -#[macro_export] -macro_rules! generate_struct_with_overrides { - ($struct_name:ident, $struct_override_name:ident, $($field:ident: $type:ty),*) => { - pub struct $struct_name { - $( - pub $field: $type, - )* - } - - #[allow(dead_code)] - pub struct $struct_override_name { - $( - pub $field: Option<$type>, - )* - } - }; -} From bbe53895efb25233a4ef42ea701054d1efdc0f59 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 15:45:42 +0100 Subject: [PATCH 07/15] Return `ColorScaleSet`s from individual color scale functions (#3197) This PR adjusts the individual color scale functions to return `ColorScaleSet`s instead of `DefaultColorScaleSet`s. We only use the `DefaultColorScaleSet`s to simplify the construction of the scales, so it isn't necessary to surface them outside of the function. Release Notes: - N/A --- crates/theme2/src/default_colors.rs | 165 +++++++++++++++++----------- 1 file changed, 99 insertions(+), 66 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 5ef93d036f..8fb38e9661 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -299,43 +299,43 @@ impl From for ColorScaleSet { pub fn default_color_scales() -> ColorScales { ColorScales { - gray: gray().into(), - mauve: mauve().into(), - slate: slate().into(), - sage: sage().into(), - olive: olive().into(), - sand: sand().into(), - gold: gold().into(), - bronze: bronze().into(), - brown: brown().into(), - yellow: yellow().into(), - amber: amber().into(), - orange: orange().into(), - tomato: tomato().into(), - red: red().into(), - ruby: ruby().into(), - crimson: crimson().into(), - pink: pink().into(), - plum: plum().into(), - purple: purple().into(), - violet: violet().into(), - iris: iris().into(), - indigo: indigo().into(), - blue: blue().into(), - cyan: cyan().into(), - teal: teal().into(), - jade: jade().into(), - green: green().into(), - grass: grass().into(), - lime: lime().into(), - mint: mint().into(), - sky: sky().into(), - black: black().into(), - white: white().into(), + gray: gray(), + mauve: mauve(), + slate: slate(), + sage: sage(), + olive: olive(), + sand: sand(), + gold: gold(), + bronze: bronze(), + brown: brown(), + yellow: yellow(), + amber: amber(), + orange: orange(), + tomato: tomato(), + red: red(), + ruby: ruby(), + crimson: crimson(), + pink: pink(), + plum: plum(), + purple: purple(), + violet: violet(), + iris: iris(), + indigo: indigo(), + blue: blue(), + cyan: cyan(), + teal: teal(), + jade: jade(), + green: green(), + grass: grass(), + lime: lime(), + mint: mint(), + sky: sky(), + black: black(), + white: white(), } } -fn gray() -> DefaultColorScaleSet { +fn gray() -> ColorScaleSet { DefaultColorScaleSet { scale: "Gray", light: [ @@ -395,9 +395,10 @@ fn gray() -> DefaultColorScaleSet { "#ffffffed", ], } + .into() } -fn mauve() -> DefaultColorScaleSet { +fn mauve() -> ColorScaleSet { DefaultColorScaleSet { scale: "Mauve", light: [ @@ -457,9 +458,10 @@ fn mauve() -> DefaultColorScaleSet { "#fdfdffef", ], } + .into() } -fn slate() -> DefaultColorScaleSet { +fn slate() -> ColorScaleSet { DefaultColorScaleSet { scale: "Slate", light: [ @@ -519,9 +521,10 @@ fn slate() -> DefaultColorScaleSet { "#fcfdffef", ], } + .into() } -fn sage() -> DefaultColorScaleSet { +fn sage() -> ColorScaleSet { DefaultColorScaleSet { scale: "Sage", light: [ @@ -581,9 +584,10 @@ fn sage() -> DefaultColorScaleSet { "#fdfffeed", ], } + .into() } -fn olive() -> DefaultColorScaleSet { +fn olive() -> ColorScaleSet { DefaultColorScaleSet { scale: "Olive", light: [ @@ -643,9 +647,10 @@ fn olive() -> DefaultColorScaleSet { "#fdfffded", ], } + .into() } -fn sand() -> DefaultColorScaleSet { +fn sand() -> ColorScaleSet { DefaultColorScaleSet { scale: "Sand", light: [ @@ -705,9 +710,10 @@ fn sand() -> DefaultColorScaleSet { "#fffffded", ], } + .into() } -fn gold() -> DefaultColorScaleSet { +fn gold() -> ColorScaleSet { DefaultColorScaleSet { scale: "Gold", light: [ @@ -767,9 +773,10 @@ fn gold() -> DefaultColorScaleSet { "#fef7ede7", ], } + .into() } -fn bronze() -> DefaultColorScaleSet { +fn bronze() -> ColorScaleSet { DefaultColorScaleSet { scale: "Bronze", light: [ @@ -829,9 +836,10 @@ fn bronze() -> DefaultColorScaleSet { "#fff1e9ec", ], } + .into() } -fn brown() -> DefaultColorScaleSet { +fn brown() -> ColorScaleSet { DefaultColorScaleSet { scale: "Brown", light: [ @@ -891,9 +899,10 @@ fn brown() -> DefaultColorScaleSet { "#feecd4f2", ], } + .into() } -fn yellow() -> DefaultColorScaleSet { +fn yellow() -> ColorScaleSet { DefaultColorScaleSet { scale: "Yellow", light: [ @@ -953,9 +962,10 @@ fn yellow() -> DefaultColorScaleSet { "#fef6baf6", ], } + .into() } -fn amber() -> DefaultColorScaleSet { +fn amber() -> ColorScaleSet { DefaultColorScaleSet { scale: "Amber", light: [ @@ -1015,9 +1025,10 @@ fn amber() -> DefaultColorScaleSet { "#ffe7b3ff", ], } + .into() } -fn orange() -> DefaultColorScaleSet { +fn orange() -> ColorScaleSet { DefaultColorScaleSet { scale: "Orange", light: [ @@ -1077,9 +1088,10 @@ fn orange() -> DefaultColorScaleSet { "#ffe0c2ff", ], } + .into() } -fn tomato() -> DefaultColorScaleSet { +fn tomato() -> ColorScaleSet { DefaultColorScaleSet { scale: "Tomato", light: [ @@ -1139,9 +1151,10 @@ fn tomato() -> DefaultColorScaleSet { "#ffd6cefb", ], } + .into() } -fn red() -> DefaultColorScaleSet { +fn red() -> ColorScaleSet { DefaultColorScaleSet { scale: "Red", light: [ @@ -1201,9 +1214,10 @@ fn red() -> DefaultColorScaleSet { "#ffd1d9ff", ], } + .into() } -fn ruby() -> DefaultColorScaleSet { +fn ruby() -> ColorScaleSet { DefaultColorScaleSet { scale: "Ruby", light: [ @@ -1263,9 +1277,10 @@ fn ruby() -> DefaultColorScaleSet { "#ffd3e2fe", ], } + .into() } -fn crimson() -> DefaultColorScaleSet { +fn crimson() -> ColorScaleSet { DefaultColorScaleSet { scale: "Crimson", light: [ @@ -1325,9 +1340,10 @@ fn crimson() -> DefaultColorScaleSet { "#ffd5eafd", ], } + .into() } -fn pink() -> DefaultColorScaleSet { +fn pink() -> ColorScaleSet { DefaultColorScaleSet { scale: "Pink", light: [ @@ -1387,9 +1403,10 @@ fn pink() -> DefaultColorScaleSet { "#ffd3ecfd", ], } + .into() } -fn plum() -> DefaultColorScaleSet { +fn plum() -> ColorScaleSet { DefaultColorScaleSet { scale: "Plum", light: [ @@ -1449,9 +1466,10 @@ fn plum() -> DefaultColorScaleSet { "#feddfef4", ], } + .into() } -fn purple() -> DefaultColorScaleSet { +fn purple() -> ColorScaleSet { DefaultColorScaleSet { scale: "Purple", light: [ @@ -1511,9 +1529,10 @@ fn purple() -> DefaultColorScaleSet { "#f1ddfffa", ], } + .into() } -fn violet() -> DefaultColorScaleSet { +fn violet() -> ColorScaleSet { DefaultColorScaleSet { scale: "Violet", light: [ @@ -1573,9 +1592,10 @@ fn violet() -> DefaultColorScaleSet { "#e3defffe", ], } + .into() } -fn iris() -> DefaultColorScaleSet { +fn iris() -> ColorScaleSet { DefaultColorScaleSet { scale: "Iris", light: [ @@ -1635,9 +1655,10 @@ fn iris() -> DefaultColorScaleSet { "#e1e0fffe", ], } + .into() } -fn indigo() -> DefaultColorScaleSet { +fn indigo() -> ColorScaleSet { DefaultColorScaleSet { scale: "Indigo", light: [ @@ -1697,9 +1718,10 @@ fn indigo() -> DefaultColorScaleSet { "#d6e1ffff", ], } + .into() } -fn blue() -> DefaultColorScaleSet { +fn blue() -> ColorScaleSet { DefaultColorScaleSet { scale: "Blue", light: [ @@ -1759,9 +1781,10 @@ fn blue() -> DefaultColorScaleSet { "#c2e6ffff", ], } + .into() } -fn cyan() -> DefaultColorScaleSet { +fn cyan() -> ColorScaleSet { DefaultColorScaleSet { scale: "Cyan", light: [ @@ -1821,9 +1844,10 @@ fn cyan() -> DefaultColorScaleSet { "#bbf3fef7", ], } + .into() } -fn teal() -> DefaultColorScaleSet { +fn teal() -> ColorScaleSet { DefaultColorScaleSet { scale: "Teal", light: [ @@ -1883,9 +1907,10 @@ fn teal() -> DefaultColorScaleSet { "#b8ffebef", ], } + .into() } -fn jade() -> DefaultColorScaleSet { +fn jade() -> ColorScaleSet { DefaultColorScaleSet { scale: "Jade", light: [ @@ -1945,9 +1970,10 @@ fn jade() -> DefaultColorScaleSet { "#b8ffe1ef", ], } + .into() } -fn green() -> DefaultColorScaleSet { +fn green() -> ColorScaleSet { DefaultColorScaleSet { scale: "Green", light: [ @@ -2007,9 +2033,10 @@ fn green() -> DefaultColorScaleSet { "#bbffd7f0", ], } + .into() } -fn grass() -> DefaultColorScaleSet { +fn grass() -> ColorScaleSet { DefaultColorScaleSet { scale: "Grass", light: [ @@ -2069,9 +2096,10 @@ fn grass() -> DefaultColorScaleSet { "#ceffceef", ], } + .into() } -fn lime() -> DefaultColorScaleSet { +fn lime() -> ColorScaleSet { DefaultColorScaleSet { scale: "Lime", light: [ @@ -2131,9 +2159,10 @@ fn lime() -> DefaultColorScaleSet { "#e9febff7", ], } + .into() } -fn mint() -> DefaultColorScaleSet { +fn mint() -> ColorScaleSet { DefaultColorScaleSet { scale: "Mint", light: [ @@ -2193,9 +2222,10 @@ fn mint() -> DefaultColorScaleSet { "#cbfee9f5", ], } + .into() } -fn sky() -> DefaultColorScaleSet { +fn sky() -> ColorScaleSet { DefaultColorScaleSet { scale: "Sky", light: [ @@ -2255,9 +2285,10 @@ fn sky() -> DefaultColorScaleSet { "#c2f3ffff", ], } + .into() } -fn black() -> DefaultColorScaleSet { +fn black() -> ColorScaleSet { DefaultColorScaleSet { scale: "Black", light: [ @@ -2317,9 +2348,10 @@ fn black() -> DefaultColorScaleSet { "#000000f2", ], } + .into() } -fn white() -> DefaultColorScaleSet { +fn white() -> ColorScaleSet { DefaultColorScaleSet { scale: "White", light: [ @@ -2379,4 +2411,5 @@ fn white() -> DefaultColorScaleSet { "#fffffff2", ], } + .into() } From 51fa80ef066d28816f47d095e76e9c5ef0aede19 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 09:19:32 -0700 Subject: [PATCH 08/15] ported example app, live_kit_client2 is done --- crates/live_kit_client2/examples/test_app.rs | 299 +++++++++--------- .../live_kit_client2/src/live_kit_client2.rs | 12 +- crates/live_kit_client2/src/test.rs | 4 +- 3 files changed, 159 insertions(+), 156 deletions(-) diff --git a/crates/live_kit_client2/examples/test_app.rs b/crates/live_kit_client2/examples/test_app.rs index 2147f6ab8c..ad10a4c95d 100644 --- a/crates/live_kit_client2/examples/test_app.rs +++ b/crates/live_kit_client2/examples/test_app.rs @@ -1,175 +1,178 @@ -// use std::time::Duration; -// todo!() +use std::{sync::Arc, time::Duration}; -// use futures::StreamExt; -// use gpui2::{actions, keymap_matcher::Binding, Menu, MenuItem}; -// use live_kit_client2::{ -// LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, -// }; -// use live_kit_server::token::{self, VideoGrant}; -// use log::LevelFilter; -// use simplelog::SimpleLogger; +use futures::StreamExt; +use gpui2::KeyBinding; +use live_kit_client2::{ + LocalAudioTrack, LocalVideoTrack, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate, Room, +}; +use live_kit_server::token::{self, VideoGrant}; +use log::LevelFilter; +use serde_derive::Deserialize; +use simplelog::SimpleLogger; -// actions!(capture, [Quit]); +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)] +struct Quit; fn main() { - // SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); + SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); - // gpui2::App::new(()).unwrap().run(|cx| { - // #[cfg(any(test, feature = "test-support"))] - // println!("USING TEST LIVEKIT"); + gpui2::App::production(Arc::new(())).run(|cx| { + #[cfg(any(test, feature = "test-support"))] + println!("USING TEST LIVEKIT"); - // #[cfg(not(any(test, feature = "test-support")))] - // println!("USING REAL LIVEKIT"); + #[cfg(not(any(test, feature = "test-support")))] + println!("USING REAL LIVEKIT"); - // cx.platform().activate(true); - // cx.add_global_action(quit); + cx.activate(true); - // cx.add_bindings([Binding::new("cmd-q", Quit, None)]); - // cx.set_menus(vec![Menu { - // name: "Zed", - // items: vec![MenuItem::Action { - // name: "Quit", - // action: Box::new(Quit), - // os_action: None, - // }], - // }]); + cx.on_action(quit); + cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]); - // let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); - // let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); - // let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); + // todo!() + // cx.set_menus(vec![Menu { + // name: "Zed", + // items: vec![MenuItem::Action { + // name: "Quit", + // action: Box::new(Quit), + // os_action: None, + // }], + // }]); - // cx.spawn(|cx| async move { - // let user_a_token = token::create( - // &live_kit_key, - // &live_kit_secret, - // Some("test-participant-1"), - // VideoGrant::to_join("test-room"), - // ) - // .unwrap(); - // let room_a = Room::new(); - // room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); + let live_kit_url = std::env::var("LIVE_KIT_URL").unwrap_or("http://localhost:7880".into()); + let live_kit_key = std::env::var("LIVE_KIT_KEY").unwrap_or("devkey".into()); + let live_kit_secret = std::env::var("LIVE_KIT_SECRET").unwrap_or("secret".into()); - // let user2_token = token::create( - // &live_kit_key, - // &live_kit_secret, - // Some("test-participant-2"), - // VideoGrant::to_join("test-room"), - // ) - // .unwrap(); - // let room_b = Room::new(); - // room_b.connect(&live_kit_url, &user2_token).await.unwrap(); + cx.spawn_on_main(|cx| async move { + let user_a_token = token::create( + &live_kit_key, + &live_kit_secret, + Some("test-participant-1"), + VideoGrant::to_join("test-room"), + ) + .unwrap(); + let room_a = Room::new(); + room_a.connect(&live_kit_url, &user_a_token).await.unwrap(); - // let mut audio_track_updates = room_b.remote_audio_track_updates(); - // let audio_track = LocalAudioTrack::create(); - // let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); + let user2_token = token::create( + &live_kit_key, + &live_kit_secret, + Some("test-participant-2"), + VideoGrant::to_join("test-room"), + ) + .unwrap(); + let room_b = Room::new(); + room_b.connect(&live_kit_url, &user2_token).await.unwrap(); - // if let RemoteAudioTrackUpdate::Subscribed(track, _) = - // audio_track_updates.next().await.unwrap() - // { - // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - // assert_eq!(remote_tracks.len(), 1); - // assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); - // assert_eq!(track.publisher_id(), "test-participant-1"); - // } else { - // panic!("unexpected message"); - // } + let mut audio_track_updates = room_b.remote_audio_track_updates(); + let audio_track = LocalAudioTrack::create(); + let audio_track_publication = room_a.publish_audio_track(audio_track).await.unwrap(); - // audio_track_publication.set_mute(true).await.unwrap(); + if let RemoteAudioTrackUpdate::Subscribed(track, _) = + audio_track_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks.len(), 1); + assert_eq!(remote_tracks[0].publisher_id(), "test-participant-1"); + assert_eq!(track.publisher_id(), "test-participant-1"); + } else { + panic!("unexpected message"); + } - // println!("waiting for mute changed!"); - // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - // audio_track_updates.next().await.unwrap() - // { - // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - // assert_eq!(remote_tracks[0].sid(), track_id); - // assert_eq!(muted, true); - // } else { - // panic!("unexpected message"); - // } + audio_track_publication.set_mute(true).await.unwrap(); - // audio_track_publication.set_mute(false).await.unwrap(); + println!("waiting for mute changed!"); + if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + audio_track_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks[0].sid(), track_id); + assert_eq!(muted, true); + } else { + panic!("unexpected message"); + } - // if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = - // audio_track_updates.next().await.unwrap() - // { - // let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); - // assert_eq!(remote_tracks[0].sid(), track_id); - // assert_eq!(muted, false); - // } else { - // panic!("unexpected message"); - // } + audio_track_publication.set_mute(false).await.unwrap(); - // println!("Pausing for 5 seconds to test audio, make some noise!"); - // let timer = cx.background().timer(Duration::from_secs(5)); - // timer.await; - // let remote_audio_track = room_b - // .remote_audio_tracks("test-participant-1") - // .pop() - // .unwrap(); - // room_a.unpublish_track(audio_track_publication); + if let RemoteAudioTrackUpdate::MuteChanged { track_id, muted } = + audio_track_updates.next().await.unwrap() + { + let remote_tracks = room_b.remote_audio_tracks("test-participant-1"); + assert_eq!(remote_tracks[0].sid(), track_id); + assert_eq!(muted, false); + } else { + panic!("unexpected message"); + } - // // Clear out any active speakers changed messages - // let mut next = audio_track_updates.next().await.unwrap(); - // while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { - // println!("Speakers changed: {:?}", speakers); - // next = audio_track_updates.next().await.unwrap(); - // } + println!("Pausing for 5 seconds to test audio, make some noise!"); + let timer = cx.executor().timer(Duration::from_secs(5)); + timer.await; + let remote_audio_track = room_b + .remote_audio_tracks("test-participant-1") + .pop() + .unwrap(); + room_a.unpublish_track(audio_track_publication); - // if let RemoteAudioTrackUpdate::Unsubscribed { - // publisher_id, - // track_id, - // } = next - // { - // assert_eq!(publisher_id, "test-participant-1"); - // assert_eq!(remote_audio_track.sid(), track_id); - // assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); - // } else { - // panic!("unexpected message"); - // } + // Clear out any active speakers changed messages + let mut next = audio_track_updates.next().await.unwrap(); + while let RemoteAudioTrackUpdate::ActiveSpeakersChanged { speakers } = next { + println!("Speakers changed: {:?}", speakers); + next = audio_track_updates.next().await.unwrap(); + } - // let mut video_track_updates = room_b.remote_video_track_updates(); - // let displays = room_a.display_sources().await.unwrap(); - // let display = displays.into_iter().next().unwrap(); + if let RemoteAudioTrackUpdate::Unsubscribed { + publisher_id, + track_id, + } = next + { + assert_eq!(publisher_id, "test-participant-1"); + assert_eq!(remote_audio_track.sid(), track_id); + assert_eq!(room_b.remote_audio_tracks("test-participant-1").len(), 0); + } else { + panic!("unexpected message"); + } - // let local_video_track = LocalVideoTrack::screen_share_for_display(&display); - // let local_video_track_publication = - // room_a.publish_video_track(local_video_track).await.unwrap(); + let mut video_track_updates = room_b.remote_video_track_updates(); + let displays = room_a.display_sources().await.unwrap(); + let display = displays.into_iter().next().unwrap(); - // if let RemoteVideoTrackUpdate::Subscribed(track) = - // video_track_updates.next().await.unwrap() - // { - // let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); - // assert_eq!(remote_video_tracks.len(), 1); - // assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); - // assert_eq!(track.publisher_id(), "test-participant-1"); - // } else { - // panic!("unexpected message"); - // } + let local_video_track = LocalVideoTrack::screen_share_for_display(&display); + let local_video_track_publication = + room_a.publish_video_track(local_video_track).await.unwrap(); - // let remote_video_track = room_b - // .remote_video_tracks("test-participant-1") - // .pop() - // .unwrap(); - // room_a.unpublish_track(local_video_track_publication); - // if let RemoteVideoTrackUpdate::Unsubscribed { - // publisher_id, - // track_id, - // } = video_track_updates.next().await.unwrap() - // { - // assert_eq!(publisher_id, "test-participant-1"); - // assert_eq!(remote_video_track.sid(), track_id); - // assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); - // } else { - // panic!("unexpected message"); - // } + if let RemoteVideoTrackUpdate::Subscribed(track) = + video_track_updates.next().await.unwrap() + { + let remote_video_tracks = room_b.remote_video_tracks("test-participant-1"); + assert_eq!(remote_video_tracks.len(), 1); + assert_eq!(remote_video_tracks[0].publisher_id(), "test-participant-1"); + assert_eq!(track.publisher_id(), "test-participant-1"); + } else { + panic!("unexpected message"); + } - // cx.platform().quit(); - // }) - // .detach(); - // }); + let remote_video_track = room_b + .remote_video_tracks("test-participant-1") + .pop() + .unwrap(); + room_a.unpublish_track(local_video_track_publication); + if let RemoteVideoTrackUpdate::Unsubscribed { + publisher_id, + track_id, + } = video_track_updates.next().await.unwrap() + { + assert_eq!(publisher_id, "test-participant-1"); + assert_eq!(remote_video_track.sid(), track_id); + assert_eq!(room_b.remote_video_tracks("test-participant-1").len(), 0); + } else { + panic!("unexpected message"); + } + + cx.update(|cx| cx.quit()).ok(); + }) + .detach(); + }); } -// fn quit(_: &Quit, cx: &mut gpui2::AppContext) { -// cx.platform().quit(); -// } +fn quit(_: &Quit, cx: &mut gpui2::AppContext) { + cx.quit(); +} diff --git a/crates/live_kit_client2/src/live_kit_client2.rs b/crates/live_kit_client2/src/live_kit_client2.rs index 35682382e9..47cc3873ff 100644 --- a/crates/live_kit_client2/src/live_kit_client2.rs +++ b/crates/live_kit_client2/src/live_kit_client2.rs @@ -1,11 +1,11 @@ -// #[cfg(not(any(test, feature = "test-support")))] +#[cfg(not(any(test, feature = "test-support")))] pub mod prod; -// #[cfg(not(any(test, feature = "test-support")))] +#[cfg(not(any(test, feature = "test-support")))] pub use prod::*; -// #[cfg(any(test, feature = "test-support"))] -// pub mod test; +#[cfg(any(test, feature = "test-support"))] +pub mod test; -// #[cfg(any(test, feature = "test-support"))] -// pub use test::*; +#[cfg(any(test, feature = "test-support"))] +pub use test::*; diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs index 7185c11fa8..535ab20afb 100644 --- a/crates/live_kit_client2/src/test.rs +++ b/crates/live_kit_client2/src/test.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Result, Context}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; @@ -364,7 +364,7 @@ impl Room { let token = token.to_string(); async move { let server = TestServer::get(&url)?; - server.join_room(token.clone(), this.clone()).await?; + server.join_room(token.clone(), this.clone()).await.context("room join")?; *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; Ok(()) } From 1568ecbe1ee6c64a54597f3d02a31dc591f05624 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 09:29:54 -0700 Subject: [PATCH 09/15] Add back room code to call2 --- crates/call2/src/participant.rs | 29 +- crates/call2/src/room.rs | 692 ++++++++++++++-------------- crates/live_kit_client2/src/prod.rs | 4 + crates/live_kit_client2/src/test.rs | 8 +- 4 files changed, 350 insertions(+), 383 deletions(-) diff --git a/crates/call2/src/participant.rs b/crates/call2/src/participant.rs index a1837e3ad0..9fe212e776 100644 --- a/crates/call2/src/participant.rs +++ b/crates/call2/src/participant.rs @@ -1,10 +1,12 @@ use anyhow::{anyhow, Result}; use client2::ParticipantIndex; use client2::{proto, User}; +use collections::HashMap; use gpui2::WeakModel; pub use live_kit_client2::Frame; +use live_kit_client2::{RemoteAudioTrack, RemoteVideoTrack}; use project2::Project; -use std::{fmt, sync::Arc}; +use std::sync::Arc; #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum ParticipantLocation { @@ -45,27 +47,6 @@ pub struct RemoteParticipant { pub participant_index: ParticipantIndex, pub muted: bool, pub speaking: bool, - // pub video_tracks: HashMap>, - // pub audio_tracks: HashMap>, -} - -#[derive(Clone)] -pub struct RemoteVideoTrack { - pub(crate) live_kit_track: Arc, -} - -unsafe impl Send for RemoteVideoTrack {} -// todo!("remove this sync because it's not legit") -unsafe impl Sync for RemoteVideoTrack {} - -impl fmt::Debug for RemoteVideoTrack { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("RemoteVideoTrack").finish() - } -} - -impl RemoteVideoTrack { - pub fn frames(&self) -> async_broadcast::Receiver { - self.live_kit_track.frames() - } + pub video_tracks: HashMap>, + pub audio_tracks: HashMap>, } diff --git a/crates/call2/src/room.rs b/crates/call2/src/room.rs index f0e0b8de17..cf98db015b 100644 --- a/crates/call2/src/room.rs +++ b/crates/call2/src/room.rs @@ -1,9 +1,6 @@ -#![allow(dead_code, unused)] -// todo!() - use crate::{ call_settings::CallSettings, - participant::{LocalParticipant, ParticipantLocation, RemoteParticipant, RemoteVideoTrack}, + participant::{LocalParticipant, ParticipantLocation, RemoteParticipant}, IncomingCall, }; use anyhow::{anyhow, Result}; @@ -19,12 +16,15 @@ use gpui2::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel, }; use language2::LanguageRegistry; -use live_kit_client2::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate}; +use live_kit_client2::{ + LocalAudioTrack, LocalTrackPublication, LocalVideoTrack, RemoteAudioTrackUpdate, + RemoteVideoTrackUpdate, +}; use postage::{sink::Sink, stream::Stream, watch}; use project2::Project; use settings2::Settings; -use std::{future::Future, sync::Arc, time::Duration}; -use util::{ResultExt, TryFutureExt}; +use std::{future::Future, mem, sync::Arc, time::Duration}; +use util::{post_inc, ResultExt, TryFutureExt}; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -95,15 +95,14 @@ impl Room { #[cfg(any(test, feature = "test-support"))] pub fn is_connected(&self) -> bool { - false - // if let Some(live_kit) = self.live_kit.as_ref() { - // matches!( - // *live_kit.room.status().borrow(), - // live_kit_client::ConnectionState::Connected { .. } - // ) - // } else { - // false - // } + if let Some(live_kit) = self.live_kit.as_ref() { + matches!( + *live_kit.room.status().borrow(), + live_kit_client2::ConnectionState::Connected { .. } + ) + } else { + false + } } fn new( @@ -423,7 +422,7 @@ impl Room { self.pending_participants.clear(); self.participant_user_ids.clear(); self.client_subscriptions.clear(); - // self.live_kit.take(); + self.live_kit.take(); self.pending_room_update.take(); self.maintain_connection.take(); } @@ -799,43 +798,43 @@ impl Room { location, muted: true, speaking: false, - // video_tracks: Default::default(), - // audio_tracks: Default::default(), + video_tracks: Default::default(), + audio_tracks: Default::default(), }, ); Audio::play_sound(Sound::Joined, cx); - // if let Some(live_kit) = this.live_kit.as_ref() { - // let video_tracks = - // live_kit.room.remote_video_tracks(&user.id.to_string()); - // let audio_tracks = - // live_kit.room.remote_audio_tracks(&user.id.to_string()); - // let publications = live_kit - // .room - // .remote_audio_track_publications(&user.id.to_string()); + if let Some(live_kit) = this.live_kit.as_ref() { + let video_tracks = + live_kit.room.remote_video_tracks(&user.id.to_string()); + let audio_tracks = + live_kit.room.remote_audio_tracks(&user.id.to_string()); + let publications = live_kit + .room + .remote_audio_track_publications(&user.id.to_string()); - // for track in video_tracks { - // this.remote_video_track_updated( - // RemoteVideoTrackUpdate::Subscribed(track), - // cx, - // ) - // .log_err(); - // } + for track in video_tracks { + this.remote_video_track_updated( + RemoteVideoTrackUpdate::Subscribed(track), + cx, + ) + .log_err(); + } - // for (track, publication) in - // audio_tracks.iter().zip(publications.iter()) - // { - // this.remote_audio_track_updated( - // RemoteAudioTrackUpdate::Subscribed( - // track.clone(), - // publication.clone(), - // ), - // cx, - // ) - // .log_err(); - // } - // } + for (track, publication) in + audio_tracks.iter().zip(publications.iter()) + { + this.remote_audio_track_updated( + RemoteAudioTrackUpdate::Subscribed( + track.clone(), + publication.clone(), + ), + cx, + ) + .log_err(); + } + } } } @@ -923,7 +922,6 @@ impl Room { change: RemoteVideoTrackUpdate, cx: &mut ModelContext, ) -> Result<()> { - todo!(); match change { RemoteVideoTrackUpdate::Subscribed(track) => { let user_id = track.publisher_id().parse()?; @@ -932,12 +930,7 @@ impl Room { .remote_participants .get_mut(&user_id) .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; - // participant.video_tracks.insert( - // track_id.clone(), - // Arc::new(RemoteVideoTrack { - // live_kit_track: track, - // }), - // ); + participant.video_tracks.insert(track_id.clone(), track); cx.emit(Event::RemoteVideoTracksChanged { participant_id: participant.peer_id, }); @@ -951,7 +944,7 @@ impl Room { .remote_participants .get_mut(&user_id) .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; - // participant.video_tracks.remove(&track_id); + participant.video_tracks.remove(&track_id); cx.emit(Event::RemoteVideoTracksChanged { participant_id: participant.peer_id, }); @@ -981,65 +974,61 @@ impl Room { participant.speaking = false; } } - // todo!() - // if let Some(id) = self.client.user_id() { - // if let Some(room) = &mut self.live_kit { - // if let Ok(_) = speaker_ids.binary_search(&id) { - // room.speaking = true; - // } else { - // room.speaking = false; - // } - // } - // } + if let Some(id) = self.client.user_id() { + if let Some(room) = &mut self.live_kit { + if let Ok(_) = speaker_ids.binary_search(&id) { + room.speaking = true; + } else { + room.speaking = false; + } + } + } cx.notify(); } RemoteAudioTrackUpdate::MuteChanged { track_id, muted } => { - // todo!() - // let mut found = false; - // for participant in &mut self.remote_participants.values_mut() { - // for track in participant.audio_tracks.values() { - // if track.sid() == track_id { - // found = true; - // break; - // } - // } - // if found { - // participant.muted = muted; - // break; - // } - // } + let mut found = false; + for participant in &mut self.remote_participants.values_mut() { + for track in participant.audio_tracks.values() { + if track.sid() == track_id { + found = true; + break; + } + } + if found { + participant.muted = muted; + break; + } + } cx.notify(); } RemoteAudioTrackUpdate::Subscribed(track, publication) => { - // todo!() - // let user_id = track.publisher_id().parse()?; - // let track_id = track.sid().to_string(); - // let participant = self - // .remote_participants - // .get_mut(&user_id) - // .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; - // // participant.audio_tracks.insert(track_id.clone(), track); - // participant.muted = publication.is_muted(); + let user_id = track.publisher_id().parse()?; + let track_id = track.sid().to_string(); + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("subscribed to track by unknown participant"))?; + participant.audio_tracks.insert(track_id.clone(), track); + participant.muted = publication.is_muted(); - // cx.emit(Event::RemoteAudioTracksChanged { - // participant_id: participant.peer_id, - // }); + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); } RemoteAudioTrackUpdate::Unsubscribed { publisher_id, track_id, } => { - // todo!() - // let user_id = publisher_id.parse()?; - // let participant = self - // .remote_participants - // .get_mut(&user_id) - // .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; - // participant.audio_tracks.remove(&track_id); - // cx.emit(Event::RemoteAudioTracksChanged { - // participant_id: participant.peer_id, - // }); + let user_id = publisher_id.parse()?; + let participant = self + .remote_participants + .get_mut(&user_id) + .ok_or_else(|| anyhow!("unsubscribed from track by unknown participant"))?; + participant.audio_tracks.remove(&track_id); + cx.emit(Event::RemoteAudioTracksChanged { + participant_id: participant.peer_id, + }); } } @@ -1220,278 +1209,269 @@ impl Room { } pub fn is_screen_sharing(&self) -> bool { - todo!() - // self.live_kit.as_ref().map_or(false, |live_kit| { - // !matches!(live_kit.screen_track, LocalTrack::None) - // }) + self.live_kit.as_ref().map_or(false, |live_kit| { + !matches!(live_kit.screen_track, LocalTrack::None) + }) } pub fn is_sharing_mic(&self) -> bool { - todo!() - // self.live_kit.as_ref().map_or(false, |live_kit| { - // !matches!(live_kit.microphone_track, LocalTrack::None) - // }) + self.live_kit.as_ref().map_or(false, |live_kit| { + !matches!(live_kit.microphone_track, LocalTrack::None) + }) } pub fn is_muted(&self, cx: &AppContext) -> bool { - todo!() - // self.live_kit - // .as_ref() - // .and_then(|live_kit| match &live_kit.microphone_track { - // LocalTrack::None => Some(Self::mute_on_join(cx)), - // LocalTrack::Pending { muted, .. } => Some(*muted), - // LocalTrack::Published { muted, .. } => Some(*muted), - // }) - // .unwrap_or(false) + self.live_kit + .as_ref() + .and_then(|live_kit| match &live_kit.microphone_track { + LocalTrack::None => Some(Self::mute_on_join(cx)), + LocalTrack::Pending { muted, .. } => Some(*muted), + LocalTrack::Published { muted, .. } => Some(*muted), + }) + .unwrap_or(false) } pub fn is_speaking(&self) -> bool { - todo!() - // self.live_kit - // .as_ref() - // .map_or(false, |live_kit| live_kit.speaking) + self.live_kit + .as_ref() + .map_or(false, |live_kit| live_kit.speaking) } pub fn is_deafened(&self) -> Option { - // self.live_kit.as_ref().map(|live_kit| live_kit.deafened) - todo!() + self.live_kit.as_ref().map(|live_kit| live_kit.deafened) } #[track_caller] pub fn share_microphone(&mut self, cx: &mut ModelContext) -> Task> { - todo!() - // if self.status.is_offline() { - // return Task::ready(Err(anyhow!("room is offline"))); - // } else if self.is_sharing_mic() { - // return Task::ready(Err(anyhow!("microphone was already shared"))); - // } + if self.status.is_offline() { + return Task::ready(Err(anyhow!("room is offline"))); + } else if self.is_sharing_mic() { + return Task::ready(Err(anyhow!("microphone was already shared"))); + } - // let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { - // let publish_id = post_inc(&mut live_kit.next_publish_id); - // live_kit.microphone_track = LocalTrack::Pending { - // publish_id, - // muted: false, - // }; - // cx.notify(); - // publish_id - // } else { - // return Task::ready(Err(anyhow!("live-kit was not initialized"))); - // }; + let publish_id = if let Some(live_kit) = self.live_kit.as_mut() { + let publish_id = post_inc(&mut live_kit.next_publish_id); + live_kit.microphone_track = LocalTrack::Pending { + publish_id, + muted: false, + }; + cx.notify(); + publish_id + } else { + return Task::ready(Err(anyhow!("live-kit was not initialized"))); + }; - // cx.spawn(move |this, mut cx| async move { - // let publish_track = async { - // let track = LocalAudioTrack::create(); - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, _| { - // this.live_kit - // .as_ref() - // .map(|live_kit| live_kit.room.publish_audio_track(track)) - // })? - // .ok_or_else(|| anyhow!("live-kit was not initialized"))? - // .await - // }; + cx.spawn(move |this, mut cx| async move { + let publish_track = async { + let track = LocalAudioTrack::create(); + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, _| { + this.live_kit + .as_ref() + .map(|live_kit| live_kit.room.publish_audio_track(track)) + })? + .ok_or_else(|| anyhow!("live-kit was not initialized"))? + .await + }; - // let publication = publish_track.await; - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, cx| { - // let live_kit = this - // .live_kit - // .as_mut() - // .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + let publication = publish_track.await; + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - // let (canceled, muted) = if let LocalTrack::Pending { - // publish_id: cur_publish_id, - // muted, - // } = &live_kit.microphone_track - // { - // (*cur_publish_id != publish_id, *muted) - // } else { - // (true, false) - // }; + let (canceled, muted) = if let LocalTrack::Pending { + publish_id: cur_publish_id, + muted, + } = &live_kit.microphone_track + { + (*cur_publish_id != publish_id, *muted) + } else { + (true, false) + }; - // match publication { - // Ok(publication) => { - // if canceled { - // live_kit.room.unpublish_track(publication); - // } else { - // if muted { - // cx.executor().spawn(publication.set_mute(muted)).detach(); - // } - // live_kit.microphone_track = LocalTrack::Published { - // track_publication: publication, - // muted, - // }; - // cx.notify(); - // } - // Ok(()) - // } - // Err(error) => { - // if canceled { - // Ok(()) - // } else { - // live_kit.microphone_track = LocalTrack::None; - // cx.notify(); - // Err(error) - // } - // } - // } - // })? - // }) + match publication { + Ok(publication) => { + if canceled { + live_kit.room.unpublish_track(publication); + } else { + if muted { + cx.executor().spawn(publication.set_mute(muted)).detach(); + } + live_kit.microphone_track = LocalTrack::Published { + track_publication: publication, + muted, + }; + cx.notify(); + } + Ok(()) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.microphone_track = LocalTrack::None; + cx.notify(); + Err(error) + } + } + } + })? + }) } pub fn share_screen(&mut self, cx: &mut ModelContext) -> Task> { - todo!() - // if self.status.is_offline() { - // return Task::ready(Err(anyhow!("room is offline"))); - // } else if self.is_screen_sharing() { - // return Task::ready(Err(anyhow!("screen was already shared"))); - // } + if self.status.is_offline() { + return Task::ready(Err(anyhow!("room is offline"))); + } else if self.is_screen_sharing() { + return Task::ready(Err(anyhow!("screen was already shared"))); + } - // let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { - // let publish_id = post_inc(&mut live_kit.next_publish_id); - // live_kit.screen_track = LocalTrack::Pending { - // publish_id, - // muted: false, - // }; - // cx.notify(); - // (live_kit.room.display_sources(), publish_id) - // } else { - // return Task::ready(Err(anyhow!("live-kit was not initialized"))); - // }; + let (displays, publish_id) = if let Some(live_kit) = self.live_kit.as_mut() { + let publish_id = post_inc(&mut live_kit.next_publish_id); + live_kit.screen_track = LocalTrack::Pending { + publish_id, + muted: false, + }; + cx.notify(); + (live_kit.room.display_sources(), publish_id) + } else { + return Task::ready(Err(anyhow!("live-kit was not initialized"))); + }; - // cx.spawn(move |this, mut cx| async move { - // let publish_track = async { - // let displays = displays.await?; - // let display = displays - // .first() - // .ok_or_else(|| anyhow!("no display found"))?; - // let track = LocalVideoTrack::screen_share_for_display(&display); - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, _| { - // this.live_kit - // .as_ref() - // .map(|live_kit| live_kit.room.publish_video_track(track)) - // })? - // .ok_or_else(|| anyhow!("live-kit was not initialized"))? - // .await - // }; + cx.spawn_on_main(move |this, mut cx| async move { + let publish_track = async { + let displays = displays.await?; + let display = displays + .first() + .ok_or_else(|| anyhow!("no display found"))?; + let track = LocalVideoTrack::screen_share_for_display(&display); + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, _| { + this.live_kit + .as_ref() + .map(|live_kit| live_kit.room.publish_video_track(track)) + })? + .ok_or_else(|| anyhow!("live-kit was not initialized"))? + .await + }; - // let publication = publish_track.await; - // this.upgrade() - // .ok_or_else(|| anyhow!("room was dropped"))? - // .update(&mut cx, |this, cx| { - // let live_kit = this - // .live_kit - // .as_mut() - // .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + let publication = publish_track.await; + this.upgrade() + .ok_or_else(|| anyhow!("room was dropped"))? + .update(&mut cx, |this, cx| { + let live_kit = this + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - // let (canceled, muted) = if let LocalTrack::Pending { - // publish_id: cur_publish_id, - // muted, - // } = &live_kit.screen_track - // { - // (*cur_publish_id != publish_id, *muted) - // } else { - // (true, false) - // }; + let (canceled, muted) = if let LocalTrack::Pending { + publish_id: cur_publish_id, + muted, + } = &live_kit.screen_track + { + (*cur_publish_id != publish_id, *muted) + } else { + (true, false) + }; - // match publication { - // Ok(publication) => { - // if canceled { - // live_kit.room.unpublish_track(publication); - // } else { - // if muted { - // cx.executor().spawn(publication.set_mute(muted)).detach(); - // } - // live_kit.screen_track = LocalTrack::Published { - // track_publication: publication, - // muted, - // }; - // cx.notify(); - // } + match publication { + Ok(publication) => { + if canceled { + live_kit.room.unpublish_track(publication); + } else { + if muted { + cx.executor().spawn(publication.set_mute(muted)).detach(); + } + live_kit.screen_track = LocalTrack::Published { + track_publication: publication, + muted, + }; + cx.notify(); + } - // Audio::play_sound(Sound::StartScreenshare, cx); + Audio::play_sound(Sound::StartScreenshare, cx); - // Ok(()) - // } - // Err(error) => { - // if canceled { - // Ok(()) - // } else { - // live_kit.screen_track = LocalTrack::None; - // cx.notify(); - // Err(error) - // } - // } - // } - // })? - // }) + Ok(()) + } + Err(error) => { + if canceled { + Ok(()) + } else { + live_kit.screen_track = LocalTrack::None; + cx.notify(); + Err(error) + } + } + } + })? + }) } pub fn toggle_mute(&mut self, cx: &mut ModelContext) -> Result>> { - todo!() - // let should_mute = !self.is_muted(cx); - // if let Some(live_kit) = self.live_kit.as_mut() { - // if matches!(live_kit.microphone_track, LocalTrack::None) { - // return Ok(self.share_microphone(cx)); - // } + let should_mute = !self.is_muted(cx); + if let Some(live_kit) = self.live_kit.as_mut() { + if matches!(live_kit.microphone_track, LocalTrack::None) { + return Ok(self.share_microphone(cx)); + } - // let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; - // live_kit.muted_by_user = should_mute; + let (ret_task, old_muted) = live_kit.set_mute(should_mute, cx)?; + live_kit.muted_by_user = should_mute; - // if old_muted == true && live_kit.deafened == true { - // if let Some(task) = self.toggle_deafen(cx).ok() { - // task.detach(); - // } - // } + if old_muted == true && live_kit.deafened == true { + if let Some(task) = self.toggle_deafen(cx).ok() { + task.detach(); + } + } - // Ok(ret_task) - // } else { - // Err(anyhow!("LiveKit not started")) - // } + Ok(ret_task) + } else { + Err(anyhow!("LiveKit not started")) + } } pub fn toggle_deafen(&mut self, cx: &mut ModelContext) -> Result>> { - todo!() - // if let Some(live_kit) = self.live_kit.as_mut() { - // (*live_kit).deafened = !live_kit.deafened; + if let Some(live_kit) = self.live_kit.as_mut() { + (*live_kit).deafened = !live_kit.deafened; - // let mut tasks = Vec::with_capacity(self.remote_participants.len()); - // // Context notification is sent within set_mute itself. - // let mut mute_task = None; - // // When deafening, mute user's mic as well. - // // When undeafening, unmute user's mic unless it was manually muted prior to deafening. - // if live_kit.deafened || !live_kit.muted_by_user { - // mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0); - // }; - // for participant in self.remote_participants.values() { - // for track in live_kit - // .room - // .remote_audio_track_publications(&participant.user.id.to_string()) - // { - // let deafened = live_kit.deafened; - // tasks.push( - // cx.executor() - // .spawn_on_main(move || track.set_enabled(!deafened)), - // ); - // } - // } + let mut tasks = Vec::with_capacity(self.remote_participants.len()); + // Context notification is sent within set_mute itself. + let mut mute_task = None; + // When deafening, mute user's mic as well. + // When undeafening, unmute user's mic unless it was manually muted prior to deafening. + if live_kit.deafened || !live_kit.muted_by_user { + mute_task = Some(live_kit.set_mute(live_kit.deafened, cx)?.0); + }; + for participant in self.remote_participants.values() { + for track in live_kit + .room + .remote_audio_track_publications(&participant.user.id.to_string()) + { + let deafened = live_kit.deafened; + tasks.push( + cx.executor() + .spawn_on_main(move || track.set_enabled(!deafened)), + ); + } + } - // Ok(cx.executor().spawn_on_main(|| async { - // if let Some(mute_task) = mute_task { - // mute_task.await?; - // } - // for task in tasks { - // task.await?; - // } - // Ok(()) - // })) - // } else { - // Err(anyhow!("LiveKit not started")) - // } + Ok(cx.executor().spawn_on_main(|| async { + if let Some(mute_task) = mute_task { + mute_task.await?; + } + for task in tasks { + task.await?; + } + Ok(()) + })) + } else { + Err(anyhow!("LiveKit not started")) + } } pub fn unshare_screen(&mut self, cx: &mut ModelContext) -> Result<()> { @@ -1499,37 +1479,35 @@ impl Room { return Err(anyhow!("room is offline")); } - todo!() - // let live_kit = self - // .live_kit - // .as_mut() - // .ok_or_else(|| anyhow!("live-kit was not initialized"))?; - // match mem::take(&mut live_kit.screen_track) { - // LocalTrack::None => Err(anyhow!("screen was not shared")), - // LocalTrack::Pending { .. } => { - // cx.notify(); - // Ok(()) - // } - // LocalTrack::Published { - // track_publication, .. - // } => { - // live_kit.room.unpublish_track(track_publication); - // cx.notify(); + let live_kit = self + .live_kit + .as_mut() + .ok_or_else(|| anyhow!("live-kit was not initialized"))?; + match mem::take(&mut live_kit.screen_track) { + LocalTrack::None => Err(anyhow!("screen was not shared")), + LocalTrack::Pending { .. } => { + cx.notify(); + Ok(()) + } + LocalTrack::Published { + track_publication, .. + } => { + live_kit.room.unpublish_track(track_publication); + cx.notify(); - // Audio::play_sound(Sound::StopScreenshare, cx); - // Ok(()) - // } - // } + Audio::play_sound(Sound::StopScreenshare, cx); + Ok(()) + } + } } #[cfg(any(test, feature = "test-support"))] pub fn set_display_sources(&self, sources: Vec) { - todo!() - // self.live_kit - // .as_ref() - // .unwrap() - // .room - // .set_display_sources(sources); + self.live_kit + .as_ref() + .unwrap() + .room + .set_display_sources(sources); } } diff --git a/crates/live_kit_client2/src/prod.rs b/crates/live_kit_client2/src/prod.rs index 65ed8b754f..b2b83e95fc 100644 --- a/crates/live_kit_client2/src/prod.rs +++ b/crates/live_kit_client2/src/prod.rs @@ -499,6 +499,10 @@ impl Room { rx, ) } + + pub fn set_display_sources(&self, _: Vec) { + unreachable!("This is a test-only function") + } } impl Drop for Room { diff --git a/crates/live_kit_client2/src/test.rs b/crates/live_kit_client2/src/test.rs index 535ab20afb..f1c3d39b8e 100644 --- a/crates/live_kit_client2/src/test.rs +++ b/crates/live_kit_client2/src/test.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, Result, Context}; +use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{BTreeMap, HashMap}; use futures::Stream; @@ -364,7 +364,10 @@ impl Room { let token = token.to_string(); async move { let server = TestServer::get(&url)?; - server.join_room(token.clone(), this.clone()).await.context("room join")?; + server + .join_room(token.clone(), this.clone()) + .await + .context("room join")?; *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token }; Ok(()) } @@ -547,6 +550,7 @@ impl LocalAudioTrack { } } +#[derive(Debug)] pub struct RemoteVideoTrack { sid: Sid, publisher_id: Sid, From 7d300d3f5b416532c9583f38d3160ec90e4a17e4 Mon Sep 17 00:00:00 2001 From: "Joseph T. Lyons" Date: Wed, 1 Nov 2023 12:34:26 -0400 Subject: [PATCH 10/15] v0.112.x dev --- Cargo.lock | 2 +- crates/zed/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca8767846f..3c6cc30645 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10821,7 +10821,7 @@ dependencies = [ [[package]] name = "zed" -version = "0.111.0" +version = "0.112.0" dependencies = [ "activity_indicator", "ai", diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 250a1814aa..c9012a3a14 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -3,7 +3,7 @@ authors = ["Nathan Sobo "] description = "The fast, collaborative code editor." edition = "2021" name = "zed" -version = "0.111.0" +version = "0.112.0" publish = false [lib] From b910bbf0021002b9dee2a65e630194df73eae490 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Wed, 1 Nov 2023 18:11:12 +0100 Subject: [PATCH 11/15] Add `ui_font_size` setting (#3199) This PR adds a new `ui_font_size` setting that can be used to control the scale of the entire UI. We use the value in this setting to set the base rem size of the window. Release Notes: - N/A --- Cargo.lock | 1 + crates/gpui2/src/window.rs | 6 ++++ crates/storybook2/src/storybook2.rs | 7 +++- crates/theme2/src/settings.rs | 12 +++++-- crates/ui2/Cargo.toml | 1 + crates/ui2/src/components/icon_button.rs | 6 ++-- crates/ui2/src/components/workspace.rs | 42 ++++++++++++------------ crates/ui2/src/elements/button.rs | 10 +++--- crates/ui2/src/elements/icon.rs | 6 ++-- crates/ui2/src/elements/label.rs | 4 +-- crates/ui2/src/prelude.rs | 11 +------ crates/ui2/src/settings.rs | 2 -- 12 files changed, 58 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea10948b5a..755f4440d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9666,6 +9666,7 @@ dependencies = [ "itertools 0.11.0", "rand 0.8.5", "serde", + "settings2", "smallvec", "strum", "theme2", diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 3997a3197f..7223826ab0 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -541,6 +541,12 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.rem_size } + /// Sets the size of an em for the base font of the application. Adjusting this value allows the + /// UI to scale, just like zooming a web page. + pub fn set_rem_size(&mut self, rem_size: impl Into) { + self.window.rem_size = rem_size.into(); + } + /// The line height associated with the current text style. pub fn line_height(&self) -> Pixels { let rem_size = self.rem_size(); diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 411fe18071..3b5722732b 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/crates/storybook2/src/storybook2.rs @@ -81,7 +81,12 @@ fn main() { }), ..Default::default() }, - move |cx| cx.build_view(|cx| StoryWrapper::new(selector.story(cx))), + move |cx| { + let theme_settings = ThemeSettings::get_global(cx); + cx.set_rem_size(theme_settings.ui_font_size); + + cx.build_view(|cx| StoryWrapper::new(selector.story(cx))) + }, ); cx.activate(true); diff --git a/crates/theme2/src/settings.rs b/crates/theme2/src/settings.rs index bad00ee196..c8d2b52273 100644 --- a/crates/theme2/src/settings.rs +++ b/crates/theme2/src/settings.rs @@ -17,6 +17,7 @@ const MIN_LINE_HEIGHT: f32 = 1.0; #[derive(Clone)] pub struct ThemeSettings { + pub ui_font_size: Pixels, pub buffer_font: Font, pub buffer_font_size: Pixels, pub buffer_line_height: BufferLineHeight, @@ -28,6 +29,8 @@ pub struct AdjustedBufferFontSize(Option); #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct ThemeSettingsContent { + #[serde(default)] + pub ui_font_size: Option, #[serde(default)] pub buffer_font_family: Option, #[serde(default)] @@ -115,6 +118,7 @@ impl settings2::Settings for ThemeSettings { let themes = cx.default_global::>(); let mut this = Self { + ui_font_size: defaults.ui_font_size.unwrap_or(16.).into(), buffer_font: Font { family: defaults.buffer_font_family.clone().unwrap().into(), features: defaults.buffer_font_features.clone().unwrap(), @@ -123,9 +127,10 @@ impl settings2::Settings for ThemeSettings { }, buffer_font_size: defaults.buffer_font_size.unwrap().into(), buffer_line_height: defaults.buffer_line_height.unwrap(), - active_theme: themes.get("Zed Pro Moonlight").unwrap(), - // todo!(Read the theme name from the settings) - // active_theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), + active_theme: themes + .get(defaults.theme.as_ref().unwrap()) + .or(themes.get("Zed Pro Moonlight")) + .unwrap(), }; for value in user_values.into_iter().copied().cloned() { @@ -142,6 +147,7 @@ impl settings2::Settings for ThemeSettings { } } + merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into)); merge( &mut this.buffer_font_size, value.buffer_font_size.map(Into::into), diff --git a/crates/ui2/Cargo.toml b/crates/ui2/Cargo.toml index 58013e34cd..f11fd652b6 100644 --- a/crates/ui2/Cargo.toml +++ b/crates/ui2/Cargo.toml @@ -10,6 +10,7 @@ chrono = "0.4" gpui2 = { path = "../gpui2" } itertools = { version = "0.11.0", optional = true } serde.workspace = true +settings2 = { path = "../settings2" } smallvec.workspace = true strum = { version = "0.25.0", features = ["derive"] } theme2 = { path = "../theme2" } diff --git a/crates/ui2/src/components/icon_button.rs b/crates/ui2/src/components/icon_button.rs index 06e242b1ef..101c845a76 100644 --- a/crates/ui2/src/components/icon_button.rs +++ b/crates/ui2/src/components/icon_button.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use gpui2::MouseButton; +use gpui2::{rems, MouseButton}; use crate::{h_stack, prelude::*}; use crate::{ClickHandler, Icon, IconColor, IconElement}; @@ -88,8 +88,8 @@ impl IconButton { .id(self.id.clone()) .justify_center() .rounded_md() - .py(ui_size(cx, 0.25)) - .px(ui_size(cx, 6. / 14.)) + .py(rems(0.21875)) + .px(rems(0.375)) .bg(bg_color) .hover(|style| style.bg(bg_hover_color)) .active(|style| style.bg(bg_active_color)) diff --git a/crates/ui2/src/components/workspace.rs b/crates/ui2/src/components/workspace.rs index 0e31c6b9ad..97570a33e3 100644 --- a/crates/ui2/src/components/workspace.rs +++ b/crates/ui2/src/components/workspace.rs @@ -1,14 +1,16 @@ use std::sync::Arc; use chrono::DateTime; -use gpui2::{px, relative, rems, Div, Render, Size, View, VisualContext}; +use gpui2::{px, relative, Div, Render, Size, View, VisualContext}; +use settings2::Settings; +use theme2::ThemeSettings; -use crate::{prelude::*, NotificationsPanel}; +use crate::prelude::*; use crate::{ - static_livestream, user_settings_mut, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, - CollabPanel, EditorPane, FakeSettings, Label, LanguageSelector, Pane, PaneGroup, Panel, - PanelAllowedSides, PanelSide, ProjectPanel, SettingValue, SplitDirection, StatusBar, Terminal, - TitleBar, Toast, ToastOrigin, + static_livestream, v_stack, AssistantPanel, Button, ChatMessage, ChatPanel, CollabPanel, + EditorPane, Label, LanguageSelector, NotificationsPanel, Pane, PaneGroup, Panel, + PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal, TitleBar, + Toast, ToastOrigin, }; #[derive(Clone)] @@ -150,6 +152,18 @@ impl Workspace { pub fn debug_toggle_user_settings(&mut self, cx: &mut ViewContext) { self.debug.enable_user_settings = !self.debug.enable_user_settings; + let mut theme_settings = ThemeSettings::get_global(cx).clone(); + + if self.debug.enable_user_settings { + theme_settings.ui_font_size = 18.0.into(); + } else { + theme_settings.ui_font_size = 16.0.into(); + } + + ThemeSettings::override_global(theme_settings.clone(), cx); + + cx.set_rem_size(theme_settings.ui_font_size); + cx.notify(); } @@ -179,20 +193,6 @@ impl Render for Workspace { type Element = Div; fn render(&mut self, cx: &mut ViewContext) -> Div { - // HACK: This should happen inside of `debug_toggle_user_settings`, but - // we don't have `cx.global::()` in event handlers at the moment. - // Need to talk with Nathan/Antonio about this. - { - let settings = user_settings_mut(cx); - - if self.debug.enable_user_settings { - settings.list_indent_depth = SettingValue::UserDefined(rems(0.5).into()); - settings.ui_scale = SettingValue::UserDefined(1.25); - } else { - *settings = FakeSettings::default(); - } - } - let root_group = PaneGroup::new_panes( vec![Pane::new( "pane-0", @@ -321,7 +321,7 @@ impl Render for Workspace { v_stack() .z_index(9) .absolute() - .bottom_10() + .top_20() .left_1_4() .w_40() .gap_2() diff --git a/crates/ui2/src/elements/button.rs b/crates/ui2/src/elements/button.rs index e63269197c..073bcdbb45 100644 --- a/crates/ui2/src/elements/button.rs +++ b/crates/ui2/src/elements/button.rs @@ -1,9 +1,9 @@ use std::sync::Arc; -use gpui2::{div, DefiniteLength, Hsla, MouseButton, WindowContext}; +use gpui2::{div, rems, DefiniteLength, Hsla, MouseButton, WindowContext}; -use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor}; -use crate::{prelude::*, LineHeightStyle}; +use crate::prelude::*; +use crate::{h_stack, Icon, IconColor, IconElement, Label, LabelColor, LineHeightStyle}; #[derive(Default, PartialEq, Clone, Copy)] pub enum IconPosition { @@ -151,7 +151,7 @@ impl Button { .relative() .id(SharedString::from(format!("{}", self.label))) .p_1() - .text_size(ui_size(cx, 1.)) + .text_size(rems(1.)) .rounded_md() .bg(self.variant.bg_color(cx)) .hover(|style| style.bg(self.variant.bg_color_hover(cx))) @@ -198,7 +198,7 @@ impl ButtonGroup { } fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { - let mut el = h_stack().text_size(ui_size(cx, 1.)); + let mut el = h_stack().text_size(rems(1.)); for button in self.buttons { el = el.child(button.render(_view, cx)); diff --git a/crates/ui2/src/elements/icon.rs b/crates/ui2/src/elements/icon.rs index 6c1b3a4f08..8cc62f4a8d 100644 --- a/crates/ui2/src/elements/icon.rs +++ b/crates/ui2/src/elements/icon.rs @@ -1,4 +1,4 @@ -use gpui2::{svg, Hsla}; +use gpui2::{rems, svg, Hsla}; use strum::EnumIter; use crate::prelude::*; @@ -175,8 +175,8 @@ impl IconElement { fn render(self, _view: &mut V, cx: &mut ViewContext) -> impl Component { let fill = self.color.color(cx); let svg_size = match self.size { - IconSize::Small => ui_size(cx, 12. / 14.), - IconSize::Medium => ui_size(cx, 15. / 14.), + IconSize::Small => rems(0.75), + IconSize::Medium => rems(0.9375), }; svg() diff --git a/crates/ui2/src/elements/label.rs b/crates/ui2/src/elements/label.rs index ee8ac9a636..dcc28a3319 100644 --- a/crates/ui2/src/elements/label.rs +++ b/crates/ui2/src/elements/label.rs @@ -1,4 +1,4 @@ -use gpui2::{relative, Hsla, WindowContext}; +use gpui2::{relative, rems, Hsla, WindowContext}; use smallvec::SmallVec; use crate::prelude::*; @@ -86,7 +86,7 @@ impl Label { .bg(LabelColor::Hidden.hsla(cx)), ) }) - .text_size(ui_size(cx, 1.)) + .text_size(rems(1.)) .when(self.line_height_style == LineHeightStyle::UILabel, |this| { this.line_height(relative(1.)) }) diff --git a/crates/ui2/src/prelude.rs b/crates/ui2/src/prelude.rs index b424ce6123..c3f530d70c 100644 --- a/crates/ui2/src/prelude.rs +++ b/crates/ui2/src/prelude.rs @@ -4,21 +4,12 @@ pub use gpui2::{ }; pub use crate::elevation::*; -use crate::settings::user_settings; pub use crate::ButtonVariant; pub use theme2::ActiveTheme; -use gpui2::{rems, Hsla, Rems}; +use gpui2::Hsla; use strum::EnumIter; -pub fn ui_size(cx: &mut WindowContext, size: f32) -> Rems { - const UI_SCALE_RATIO: f32 = 0.875; - - let settings = user_settings(cx); - - rems(*settings.ui_scale * UI_SCALE_RATIO * size) -} - #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)] pub enum FileSystemStatus { #[default] diff --git a/crates/ui2/src/settings.rs b/crates/ui2/src/settings.rs index 48a2e8e7b4..6a9426f623 100644 --- a/crates/ui2/src/settings.rs +++ b/crates/ui2/src/settings.rs @@ -58,7 +58,6 @@ pub struct FakeSettings { pub list_disclosure_style: SettingValue, pub list_indent_depth: SettingValue, pub titlebar: TitlebarSettings, - pub ui_scale: SettingValue, } impl Default for FakeSettings { @@ -68,7 +67,6 @@ impl Default for FakeSettings { list_disclosure_style: SettingValue::Default(DisclosureControlStyle::ChevronOnHover), list_indent_depth: SettingValue::Default(rems(0.3).into()), default_panel_size: SettingValue::Default(rems(16.).into()), - ui_scale: SettingValue::Default(1.), } } } From 6aaeb23157dff73dcb4094d416dfd1bdc1d1aded Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 1 Nov 2023 10:52:15 -0700 Subject: [PATCH 12/15] Rename live kit bridge to 2 --- .../{LiveKitBridge => LiveKitBridge2}/Package.resolved | 0 .../{LiveKitBridge => LiveKitBridge2}/Package.swift | 8 ++++---- .../{LiveKitBridge => LiveKitBridge2}/README.md | 2 +- .../Sources/LiveKitBridge2/LiveKitBridge2.swift} | 0 crates/live_kit_client2/build.rs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename crates/live_kit_client2/{LiveKitBridge => LiveKitBridge2}/Package.resolved (100%) rename crates/live_kit_client2/{LiveKitBridge => LiveKitBridge2}/Package.swift (84%) rename crates/live_kit_client2/{LiveKitBridge => LiveKitBridge2}/README.md (65%) rename crates/live_kit_client2/{LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift => LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift} (100%) diff --git a/crates/live_kit_client2/LiveKitBridge/Package.resolved b/crates/live_kit_client2/LiveKitBridge2/Package.resolved similarity index 100% rename from crates/live_kit_client2/LiveKitBridge/Package.resolved rename to crates/live_kit_client2/LiveKitBridge2/Package.resolved diff --git a/crates/live_kit_client2/LiveKitBridge/Package.swift b/crates/live_kit_client2/LiveKitBridge2/Package.swift similarity index 84% rename from crates/live_kit_client2/LiveKitBridge/Package.swift rename to crates/live_kit_client2/LiveKitBridge2/Package.swift index d7b5c271b9..890eaa2f6d 100644 --- a/crates/live_kit_client2/LiveKitBridge/Package.swift +++ b/crates/live_kit_client2/LiveKitBridge2/Package.swift @@ -3,16 +3,16 @@ import PackageDescription let package = Package( - name: "LiveKitBridge", + name: "LiveKitBridge2", platforms: [ .macOS(.v10_15) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( - name: "LiveKitBridge", + name: "LiveKitBridge2", type: .static, - targets: ["LiveKitBridge"]), + targets: ["LiveKitBridge2"]), ], dependencies: [ .package(url: "https://github.com/livekit/client-sdk-swift.git", .exact("1.0.12")), @@ -21,7 +21,7 @@ let package = Package( // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( - name: "LiveKitBridge", + name: "LiveKitBridge2", dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]), ] ) diff --git a/crates/live_kit_client2/LiveKitBridge/README.md b/crates/live_kit_client2/LiveKitBridge2/README.md similarity index 65% rename from crates/live_kit_client2/LiveKitBridge/README.md rename to crates/live_kit_client2/LiveKitBridge2/README.md index b982c67286..1fceed8165 100644 --- a/crates/live_kit_client2/LiveKitBridge/README.md +++ b/crates/live_kit_client2/LiveKitBridge2/README.md @@ -1,3 +1,3 @@ -# LiveKitBridge +# LiveKitBridge2 A description of this package. diff --git a/crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift b/crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift similarity index 100% rename from crates/live_kit_client2/LiveKitBridge/Sources/LiveKitBridge/LiveKitBridge.swift rename to crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift diff --git a/crates/live_kit_client2/build.rs b/crates/live_kit_client2/build.rs index 1445704b46..b346b3168b 100644 --- a/crates/live_kit_client2/build.rs +++ b/crates/live_kit_client2/build.rs @@ -5,7 +5,7 @@ use std::{ process::Command, }; -const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge"; +const SWIFT_PACKAGE_NAME: &str = "LiveKitBridge2"; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] From 77dbb15aa3a942c80522bec64a55c37cf7e5a530 Mon Sep 17 00:00:00 2001 From: KCaverly Date: Wed, 1 Nov 2023 16:13:53 -0400 Subject: [PATCH 13/15] port text2 to zed2 --- Cargo.lock | 25 +- crates/text2/Cargo.toml | 37 + crates/text2/src/anchor.rs | 144 ++ crates/text2/src/locator.rs | 125 ++ crates/text2/src/network.rs | 69 + crates/text2/src/operation_queue.rs | 153 ++ crates/text2/src/patch.rs | 594 ++++++ crates/text2/src/selection.rs | 123 ++ crates/text2/src/subscription.rs | 48 + crates/text2/src/tests.rs | 764 ++++++++ crates/text2/src/text2.rs | 2682 +++++++++++++++++++++++++++ crates/text2/src/undo_map.rs | 112 ++ crates/zed2/Cargo.toml | 4 +- 13 files changed, 4877 insertions(+), 3 deletions(-) create mode 100644 crates/text2/Cargo.toml create mode 100644 crates/text2/src/anchor.rs create mode 100644 crates/text2/src/locator.rs create mode 100644 crates/text2/src/network.rs create mode 100644 crates/text2/src/operation_queue.rs create mode 100644 crates/text2/src/patch.rs create mode 100644 crates/text2/src/selection.rs create mode 100644 crates/text2/src/subscription.rs create mode 100644 crates/text2/src/tests.rs create mode 100644 crates/text2/src/text2.rs create mode 100644 crates/text2/src/undo_map.rs diff --git a/Cargo.lock b/Cargo.lock index 755f4440d9..5d6ce29b14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8806,6 +8806,29 @@ dependencies = [ "util", ] +[[package]] +name = "text2" +version = "0.1.0" +dependencies = [ + "anyhow", + "clock", + "collections", + "ctor", + "digest 0.9.0", + "env_logger 0.9.3", + "gpui2", + "lazy_static", + "log", + "parking_lot 0.11.2", + "postage", + "rand 0.8.5", + "regex", + "rope", + "smallvec", + "sum_tree", + "util", +] + [[package]] name = "textwrap" version = "0.16.0" @@ -11052,7 +11075,7 @@ dependencies = [ "smol", "sum_tree", "tempdir", - "text", + "text2", "theme2", "thiserror", "tiny_http", diff --git a/crates/text2/Cargo.toml b/crates/text2/Cargo.toml new file mode 100644 index 0000000000..6891fef680 --- /dev/null +++ b/crates/text2/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "text2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/text2.rs" +doctest = false + +[features] +test-support = ["rand"] + +[dependencies] +clock = { path = "../clock" } +collections = { path = "../collections" } +rope = { path = "../rope" } +sum_tree = { path = "../sum_tree" } +util = { path = "../util" } + +anyhow.workspace = true +digest = { version = "0.9", features = ["std"] } +lazy_static.workspace = true +log.workspace = true +parking_lot.workspace = true +postage.workspace = true +rand = { workspace = true, optional = true } +smallvec.workspace = true +regex.workspace = true + +[dev-dependencies] +collections = { path = "../collections", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +ctor.workspace = true +env_logger.workspace = true +rand.workspace = true diff --git a/crates/text2/src/anchor.rs b/crates/text2/src/anchor.rs new file mode 100644 index 0000000000..084be0e336 --- /dev/null +++ b/crates/text2/src/anchor.rs @@ -0,0 +1,144 @@ +use crate::{ + locator::Locator, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint, + ToPointUtf16, +}; +use anyhow::Result; +use std::{cmp::Ordering, fmt::Debug, ops::Range}; +use sum_tree::Bias; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Default)] +pub struct Anchor { + pub timestamp: clock::Lamport, + pub offset: usize, + pub bias: Bias, + pub buffer_id: Option, +} + +impl Anchor { + pub const MIN: Self = Self { + timestamp: clock::Lamport::MIN, + offset: usize::MIN, + bias: Bias::Left, + buffer_id: None, + }; + + pub const MAX: Self = Self { + timestamp: clock::Lamport::MAX, + offset: usize::MAX, + bias: Bias::Right, + buffer_id: None, + }; + + pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering { + let fragment_id_comparison = if self.timestamp == other.timestamp { + Ordering::Equal + } else { + buffer + .fragment_id_for_anchor(self) + .cmp(buffer.fragment_id_for_anchor(other)) + }; + + fragment_id_comparison + .then_with(|| self.offset.cmp(&other.offset)) + .then_with(|| self.bias.cmp(&other.bias)) + } + + pub fn min(&self, other: &Self, buffer: &BufferSnapshot) -> Self { + if self.cmp(other, buffer).is_le() { + *self + } else { + *other + } + } + + pub fn max(&self, other: &Self, buffer: &BufferSnapshot) -> Self { + if self.cmp(other, buffer).is_ge() { + *self + } else { + *other + } + } + + pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor { + if bias == Bias::Left { + self.bias_left(buffer) + } else { + self.bias_right(buffer) + } + } + + pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor { + if self.bias == Bias::Left { + *self + } else { + buffer.anchor_before(self) + } + } + + pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor { + if self.bias == Bias::Right { + *self + } else { + buffer.anchor_after(self) + } + } + + pub fn summary(&self, content: &BufferSnapshot) -> D + where + D: TextDimension, + { + content.summary_for_anchor(self) + } + + /// Returns true when the [Anchor] is located inside a visible fragment. + pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { + if *self == Anchor::MIN || *self == Anchor::MAX { + true + } else { + let fragment_id = buffer.fragment_id_for_anchor(self); + let mut fragment_cursor = buffer.fragments.cursor::<(Option<&Locator>, usize)>(); + fragment_cursor.seek(&Some(fragment_id), Bias::Left, &None); + fragment_cursor + .item() + .map_or(false, |fragment| fragment.visible) + } + } +} + +pub trait OffsetRangeExt { + fn to_offset(&self, snapshot: &BufferSnapshot) -> Range; + fn to_point(&self, snapshot: &BufferSnapshot) -> Range; + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range; +} + +impl OffsetRangeExt for Range +where + T: ToOffset, +{ + fn to_offset(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot)..self.end.to_offset(snapshot) + } + + fn to_point(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot).to_point(snapshot) + ..self.end.to_offset(snapshot).to_point(snapshot) + } + + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot).to_point_utf16(snapshot) + ..self.end.to_offset(snapshot).to_point_utf16(snapshot) + } +} + +pub trait AnchorRangeExt { + fn cmp(&self, b: &Range, buffer: &BufferSnapshot) -> Result; +} + +impl AnchorRangeExt for Range { + fn cmp(&self, other: &Range, buffer: &BufferSnapshot) -> Result { + Ok(match self.start.cmp(&other.start, buffer) { + Ordering::Equal => other.end.cmp(&self.end, buffer), + ord => ord, + }) + } +} diff --git a/crates/text2/src/locator.rs b/crates/text2/src/locator.rs new file mode 100644 index 0000000000..27fdb34cde --- /dev/null +++ b/crates/text2/src/locator.rs @@ -0,0 +1,125 @@ +use lazy_static::lazy_static; +use smallvec::{smallvec, SmallVec}; +use std::iter; + +lazy_static! { + static ref MIN: Locator = Locator::min(); + static ref MAX: Locator = Locator::max(); +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Locator(SmallVec<[u64; 4]>); + +impl Locator { + pub fn min() -> Self { + Self(smallvec![u64::MIN]) + } + + pub fn max() -> Self { + Self(smallvec![u64::MAX]) + } + + pub fn min_ref() -> &'static Self { + &*MIN + } + + pub fn max_ref() -> &'static Self { + &*MAX + } + + pub fn assign(&mut self, other: &Self) { + self.0.resize(other.0.len(), 0); + self.0.copy_from_slice(&other.0); + } + + pub fn between(lhs: &Self, rhs: &Self) -> Self { + let lhs = lhs.0.iter().copied().chain(iter::repeat(u64::MIN)); + let rhs = rhs.0.iter().copied().chain(iter::repeat(u64::MAX)); + let mut location = SmallVec::new(); + for (lhs, rhs) in lhs.zip(rhs) { + let mid = lhs + ((rhs.saturating_sub(lhs)) >> 48); + location.push(mid); + if mid > lhs { + break; + } + } + Self(location) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for Locator { + fn default() -> Self { + Self::min() + } +} + +impl sum_tree::Item for Locator { + type Summary = Locator; + + fn summary(&self) -> Self::Summary { + self.clone() + } +} + +impl sum_tree::KeyedItem for Locator { + type Key = Locator; + + fn key(&self) -> Self::Key { + self.clone() + } +} + +impl sum_tree::Summary for Locator { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + self.assign(summary); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::mem; + + #[gpui2::test(iterations = 100)] + fn test_locators(mut rng: StdRng) { + let mut lhs = Default::default(); + let mut rhs = Default::default(); + while lhs == rhs { + lhs = Locator( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + rhs = Locator( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + } + + if lhs > rhs { + mem::swap(&mut lhs, &mut rhs); + } + + let middle = Locator::between(&lhs, &rhs); + assert!(middle > lhs); + assert!(middle < rhs); + for ix in 0..middle.0.len() - 1 { + assert!( + middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0) + || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0) + ); + } + } +} diff --git a/crates/text2/src/network.rs b/crates/text2/src/network.rs new file mode 100644 index 0000000000..2f49756ca3 --- /dev/null +++ b/crates/text2/src/network.rs @@ -0,0 +1,69 @@ +use clock::ReplicaId; + +pub struct Network { + inboxes: std::collections::BTreeMap>>, + all_messages: Vec, + rng: R, +} + +#[derive(Clone)] +struct Envelope { + message: T, +} + +impl Network { + pub fn new(rng: R) -> Self { + Network { + inboxes: Default::default(), + all_messages: Vec::new(), + rng, + } + } + + pub fn add_peer(&mut self, id: ReplicaId) { + self.inboxes.insert(id, Vec::new()); + } + + pub fn replicate(&mut self, old_replica_id: ReplicaId, new_replica_id: ReplicaId) { + self.inboxes + .insert(new_replica_id, self.inboxes[&old_replica_id].clone()); + } + + pub fn is_idle(&self) -> bool { + self.inboxes.values().all(|i| i.is_empty()) + } + + pub fn broadcast(&mut self, sender: ReplicaId, messages: Vec) { + for (replica, inbox) in self.inboxes.iter_mut() { + if *replica != sender { + for message in &messages { + // Insert one or more duplicates of this message, potentially *before* the previous + // message sent by this peer to simulate out-of-order delivery. + for _ in 0..self.rng.gen_range(1..4) { + let insertion_index = self.rng.gen_range(0..inbox.len() + 1); + inbox.insert( + insertion_index, + Envelope { + message: message.clone(), + }, + ); + } + } + } + } + self.all_messages.extend(messages); + } + + pub fn has_unreceived(&self, receiver: ReplicaId) -> bool { + !self.inboxes[&receiver].is_empty() + } + + pub fn receive(&mut self, receiver: ReplicaId) -> Vec { + let inbox = self.inboxes.get_mut(&receiver).unwrap(); + let count = self.rng.gen_range(0..inbox.len() + 1); + inbox + .drain(0..count) + .map(|envelope| envelope.message) + .collect() + } +} diff --git a/crates/text2/src/operation_queue.rs b/crates/text2/src/operation_queue.rs new file mode 100644 index 0000000000..063f050665 --- /dev/null +++ b/crates/text2/src/operation_queue.rs @@ -0,0 +1,153 @@ +use std::{fmt::Debug, ops::Add}; +use sum_tree::{Dimension, Edit, Item, KeyedItem, SumTree, Summary}; + +pub trait Operation: Clone + Debug { + fn lamport_timestamp(&self) -> clock::Lamport; +} + +#[derive(Clone, Debug)] +struct OperationItem(T); + +#[derive(Clone, Debug)] +pub struct OperationQueue(SumTree>); + +#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +pub struct OperationKey(clock::Lamport); + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct OperationSummary { + pub key: OperationKey, + pub len: usize, +} + +impl OperationKey { + pub fn new(timestamp: clock::Lamport) -> Self { + Self(timestamp) + } +} + +impl Default for OperationQueue { + fn default() -> Self { + OperationQueue::new() + } +} + +impl OperationQueue { + pub fn new() -> Self { + OperationQueue(SumTree::new()) + } + + pub fn len(&self) -> usize { + self.0.summary().len + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn insert(&mut self, mut ops: Vec) { + ops.sort_by_key(|op| op.lamport_timestamp()); + ops.dedup_by_key(|op| op.lamport_timestamp()); + self.0.edit( + ops.into_iter() + .map(|op| Edit::Insert(OperationItem(op))) + .collect(), + &(), + ); + } + + pub fn drain(&mut self) -> Self { + let clone = self.clone(); + self.0 = SumTree::new(); + clone + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|i| &i.0) + } +} + +impl Summary for OperationSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + assert!(self.key < other.key); + self.key = other.key; + self.len += other.len; + } +} + +impl<'a> Add<&'a Self> for OperationSummary { + type Output = Self; + + fn add(self, other: &Self) -> Self { + assert!(self.key < other.key); + OperationSummary { + key: other.key, + len: self.len + other.len, + } + } +} + +impl<'a> Dimension<'a, OperationSummary> for OperationKey { + fn add_summary(&mut self, summary: &OperationSummary, _: &()) { + assert!(*self <= summary.key); + *self = summary.key; + } +} + +impl Item for OperationItem { + type Summary = OperationSummary; + + fn summary(&self) -> Self::Summary { + OperationSummary { + key: OperationKey::new(self.0.lamport_timestamp()), + len: 1, + } + } +} + +impl KeyedItem for OperationItem { + type Key = OperationKey; + + fn key(&self) -> Self::Key { + OperationKey::new(self.0.lamport_timestamp()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len() { + let mut clock = clock::Lamport::new(0); + + let mut queue = OperationQueue::new(); + assert_eq!(queue.len(), 0); + + queue.insert(vec![ + TestOperation(clock.tick()), + TestOperation(clock.tick()), + ]); + assert_eq!(queue.len(), 2); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 3); + + drop(queue.drain()); + assert_eq!(queue.len(), 0); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 1); + } + + #[derive(Clone, Debug, Eq, PartialEq)] + struct TestOperation(clock::Lamport); + + impl Operation for TestOperation { + fn lamport_timestamp(&self) -> clock::Lamport { + self.0 + } + } +} diff --git a/crates/text2/src/patch.rs b/crates/text2/src/patch.rs new file mode 100644 index 0000000000..20e4a4d889 --- /dev/null +++ b/crates/text2/src/patch.rs @@ -0,0 +1,594 @@ +use crate::Edit; +use std::{ + cmp, mem, + ops::{Add, AddAssign, Sub}, +}; + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct Patch(Vec>); + +impl Patch +where + T: 'static + + Clone + + Copy + + Ord + + Sub + + Add + + AddAssign + + Default + + PartialEq, +{ + pub fn new(edits: Vec>) -> Self { + #[cfg(debug_assertions)] + { + let mut last_edit: Option<&Edit> = None; + for edit in &edits { + if let Some(last_edit) = last_edit { + assert!(edit.old.start > last_edit.old.end); + assert!(edit.new.start > last_edit.new.end); + } + last_edit = Some(edit); + } + } + Self(edits) + } + + pub fn edits(&self) -> &[Edit] { + &self.0 + } + + pub fn into_inner(self) -> Vec> { + self.0 + } + + pub fn compose(&self, new_edits_iter: impl IntoIterator>) -> Self { + let mut old_edits_iter = self.0.iter().cloned().peekable(); + let mut new_edits_iter = new_edits_iter.into_iter().peekable(); + let mut composed = Patch(Vec::new()); + + let mut old_start = T::default(); + let mut new_start = T::default(); + loop { + let old_edit = old_edits_iter.peek_mut(); + let new_edit = new_edits_iter.peek_mut(); + + // Push the old edit if its new end is before the new edit's old start. + if let Some(old_edit) = old_edit.as_ref() { + let new_edit = new_edit.as_ref(); + if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + old_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + continue; + } + } + + // Push the new edit if its old end is before the old edit's new start. + if let Some(new_edit) = new_edit.as_ref() { + let old_edit = old_edit.as_ref(); + if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + new_edit.old_len(); + let new_end = new_start + new_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + continue; + } + } + + // If we still have edits by this point then they must intersect, so we compose them. + if let Some((old_edit, new_edit)) = old_edit.zip(new_edit) { + if old_edit.new.start < new_edit.old.start { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let overshoot = new_edit.old.start - old_edit.new.start; + let old_end = cmp::min(old_start + overshoot, old_edit.old.end); + let new_end = new_start + overshoot; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start += overshoot; + old_start = old_end; + new_start = new_end; + } else { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let overshoot = old_edit.new.start - new_edit.old.start; + let old_end = old_start + overshoot; + let new_end = cmp::min(new_start + overshoot, new_edit.new.end); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start += overshoot; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + } + + if old_edit.new.end > new_edit.old.end { + let old_end = old_start + cmp::min(old_edit.old_len(), new_edit.old_len()); + let new_end = new_start + new_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start = new_edit.old.end; + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + } else { + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + cmp::min(old_edit.new_len(), new_edit.new_len()); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start = old_edit.new.end; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + } + } else { + break; + } + } + + composed + } + + pub fn invert(&mut self) -> &mut Self { + for edit in &mut self.0 { + mem::swap(&mut edit.old, &mut edit.new); + } + self + } + + pub fn clear(&mut self) { + self.0.clear(); + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn push(&mut self, edit: Edit) { + if edit.is_empty() { + return; + } + + if let Some(last) = self.0.last_mut() { + if last.old.end >= edit.old.start { + last.old.end = edit.old.end; + last.new.end = edit.new.end; + } else { + self.0.push(edit); + } + } else { + self.0.push(edit); + } + } + + pub fn old_to_new(&self, old: T) -> T { + let ix = match self.0.binary_search_by(|probe| probe.old.start.cmp(&old)) { + Ok(ix) => ix, + Err(ix) => { + if ix == 0 { + return old; + } else { + ix - 1 + } + } + }; + if let Some(edit) = self.0.get(ix) { + if old >= edit.old.end { + edit.new.end + (old - edit.old.end) + } else { + edit.new.start + } + } else { + old + } + } +} + +impl IntoIterator for Patch { + type Item = Edit; + type IntoIter = std::vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T: Clone> IntoIterator for &'a Patch { + type Item = Edit; + type IntoIter = std::iter::Cloned>>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().cloned() + } +} + +impl<'a, T: Clone> IntoIterator for &'a mut Patch { + type Item = Edit; + type IntoIter = std::iter::Cloned>>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().cloned() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::env; + + #[gpui2::test] + fn test_one_disjoint_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 0..0, + new: 0..4, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..3, + new: 5..8, + }, + ]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 5..9, + new: 5..7, + }]), + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 4..8, + new: 5..7, + }, + ]), + ); + } + + #[gpui2::test] + fn test_one_overlapping_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 3..5, + new: 3..6, + }]), + Patch(vec![Edit { + old: 1..4, + new: 1..6, + }]), + ); + } + + #[gpui2::test] + fn test_two_disjoint_and_overlapping() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 8..12, + new: 9..11, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 3..10, + new: 7..9, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..12, + new: 5..10, + }, + ]), + ); + } + + #[gpui2::test] + fn test_two_new_edits_overlapping_one_old_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..1, + }, + Edit { + old: 1..2, + new: 2..2, + }, + ]), + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 2..3, + new: 2..4, + }]), + Patch(vec![ + Edit { + old: 0..2, + new: 0..1, + }, + Edit { + old: 3..3, + new: 2..5, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..6, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..2, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..2, + }, + Edit { + old: 2..5, + new: 4..4, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..4, + }]), + ); + } + + #[gpui2::test] + fn test_two_new_edits_touching_one_old_edit() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..2, + }, + Edit { + old: 4..4, + new: 3..4, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + ); + } + + #[gpui2::test] + fn test_old_to_new() { + let patch = Patch(vec![ + Edit { + old: 2..4, + new: 2..4, + }, + Edit { + old: 7..8, + new: 7..11, + }, + ]); + assert_eq!(patch.old_to_new(0), 0); + assert_eq!(patch.old_to_new(1), 1); + assert_eq!(patch.old_to_new(2), 2); + assert_eq!(patch.old_to_new(3), 2); + assert_eq!(patch.old_to_new(4), 4); + assert_eq!(patch.old_to_new(5), 5); + assert_eq!(patch.old_to_new(6), 6); + assert_eq!(patch.old_to_new(7), 7); + assert_eq!(patch.old_to_new(8), 11); + assert_eq!(patch.old_to_new(9), 12); + } + + #[gpui2::test(iterations = 100)] + fn test_random_patch_compositions(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(20); + + let initial_chars = (0..rng.gen_range(0..=100)) + .map(|_| rng.gen_range(b'a'..=b'z') as char) + .collect::>(); + log::info!("initial chars: {:?}", initial_chars); + + // Generate two sequential patches + let mut patches = Vec::new(); + let mut expected_chars = initial_chars.clone(); + for i in 0..2 { + log::info!("patch {}:", i); + + let mut delta = 0i32; + let mut last_edit_end = 0; + let mut edits = Vec::new(); + + for _ in 0..operations { + if last_edit_end >= expected_chars.len() { + break; + } + + let end = rng.gen_range(last_edit_end..=expected_chars.len()); + let start = rng.gen_range(last_edit_end..=end); + let old_len = end - start; + + let mut new_len = rng.gen_range(0..=3); + if start == end && new_len == 0 { + new_len += 1; + } + + last_edit_end = start + new_len + 1; + + let new_chars = (0..new_len) + .map(|_| rng.gen_range(b'A'..=b'Z') as char) + .collect::>(); + log::info!( + " editing {:?}: {:?}", + start..end, + new_chars.iter().collect::() + ); + edits.push(Edit { + old: (start as i32 - delta) as u32..(end as i32 - delta) as u32, + new: start as u32..(start + new_len) as u32, + }); + expected_chars.splice(start..end, new_chars); + + delta += new_len as i32 - old_len as i32; + } + + patches.push(Patch(edits)); + } + + log::info!("old patch: {:?}", &patches[0]); + log::info!("new patch: {:?}", &patches[1]); + log::info!("initial chars: {:?}", initial_chars); + log::info!("final chars: {:?}", expected_chars); + + // Compose the patches, and verify that it has the same effect as applying the + // two patches separately. + let composed = patches[0].compose(&patches[1]); + log::info!("composed patch: {:?}", &composed); + + let mut actual_chars = initial_chars; + for edit in composed.0 { + actual_chars.splice( + edit.new.start as usize..edit.new.start as usize + edit.old.len(), + expected_chars[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + + assert_eq!(actual_chars, expected_chars); + } + + #[track_caller] + fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) { + let original = ('a'..'z').collect::>(); + let inserted = ('A'..'Z').collect::>(); + + let mut expected = original.clone(); + apply_patch(&mut expected, &old, &inserted); + apply_patch(&mut expected, &new, &inserted); + + let mut actual = original; + apply_patch(&mut actual, &composed, &expected); + assert_eq!( + actual.into_iter().collect::(), + expected.into_iter().collect::(), + "expected patch is incorrect" + ); + + assert_eq!(old.compose(&new), composed); + } + + fn apply_patch(text: &mut Vec, patch: &Patch, new_text: &[char]) { + for edit in patch.0.iter().rev() { + text.splice( + edit.old.start as usize..edit.old.end as usize, + new_text[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + } +} diff --git a/crates/text2/src/selection.rs b/crates/text2/src/selection.rs new file mode 100644 index 0000000000..480cb99d74 --- /dev/null +++ b/crates/text2/src/selection.rs @@ -0,0 +1,123 @@ +use crate::{Anchor, BufferSnapshot, TextDimension}; +use std::cmp::Ordering; +use std::ops::Range; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SelectionGoal { + None, + HorizontalPosition(f32), + HorizontalRange { start: f32, end: f32 }, + WrappedHorizontalPosition((u32, f32)), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Selection { + pub id: usize, + pub start: T, + pub end: T, + pub reversed: bool, + pub goal: SelectionGoal, +} + +impl Default for SelectionGoal { + fn default() -> Self { + Self::None + } +} + +impl Selection { + pub fn head(&self) -> T { + if self.reversed { + self.start.clone() + } else { + self.end.clone() + } + } + + pub fn tail(&self) -> T { + if self.reversed { + self.end.clone() + } else { + self.start.clone() + } + } + + pub fn map(&self, f: F) -> Selection + where + F: Fn(T) -> S, + { + Selection:: { + id: self.id, + start: f(self.start.clone()), + end: f(self.end.clone()), + reversed: self.reversed, + goal: self.goal, + } + } + + pub fn collapse_to(&mut self, point: T, new_goal: SelectionGoal) { + self.start = point.clone(); + self.end = point; + self.goal = new_goal; + self.reversed = false; + } +} + +impl Selection { + pub fn is_empty(&self) -> bool { + self.start == self.end + } + + pub fn set_head(&mut self, head: T, new_goal: SelectionGoal) { + if head.cmp(&self.tail()) < Ordering::Equal { + if !self.reversed { + self.end = self.start; + self.reversed = true; + } + self.start = head; + } else { + if self.reversed { + self.start = self.end; + self.reversed = false; + } + self.end = head; + } + self.goal = new_goal; + } + + pub fn range(&self) -> Range { + self.start..self.end + } +} + +impl Selection { + #[cfg(feature = "test-support")] + pub fn from_offset(offset: usize) -> Self { + Selection { + id: 0, + start: offset, + end: offset, + goal: SelectionGoal::None, + reversed: false, + } + } + + pub fn equals(&self, offset_range: &Range) -> bool { + self.start == offset_range.start && self.end == offset_range.end + } +} + +impl Selection { + pub fn resolve<'a, D: 'a + TextDimension>( + &'a self, + snapshot: &'a BufferSnapshot, + ) -> Selection { + Selection { + id: self.id, + start: snapshot.summary_for_anchor(&self.start), + end: snapshot.summary_for_anchor(&self.end), + reversed: self.reversed, + goal: self.goal, + } + } +} diff --git a/crates/text2/src/subscription.rs b/crates/text2/src/subscription.rs new file mode 100644 index 0000000000..b636dfcc92 --- /dev/null +++ b/crates/text2/src/subscription.rs @@ -0,0 +1,48 @@ +use crate::{Edit, Patch}; +use parking_lot::Mutex; +use std::{ + mem, + sync::{Arc, Weak}, +}; + +#[derive(Default)] +pub struct Topic(Mutex>>>>); + +pub struct Subscription(Arc>>); + +impl Topic { + pub fn subscribe(&mut self) -> Subscription { + let subscription = Subscription(Default::default()); + self.0.get_mut().push(Arc::downgrade(&subscription.0)); + subscription + } + + pub fn publish(&self, edits: impl Clone + IntoIterator>) { + publish(&mut *self.0.lock(), edits); + } + + pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator>) { + publish(self.0.get_mut(), edits); + } +} + +impl Subscription { + pub fn consume(&self) -> Patch { + mem::take(&mut *self.0.lock()) + } +} + +fn publish( + subscriptions: &mut Vec>>>, + edits: impl Clone + IntoIterator>, +) { + subscriptions.retain(|subscription| { + if let Some(subscription) = subscription.upgrade() { + let mut patch = subscription.lock(); + *patch = patch.compose(edits.clone()); + true + } else { + false + } + }); +} diff --git a/crates/text2/src/tests.rs b/crates/text2/src/tests.rs new file mode 100644 index 0000000000..96248285ea --- /dev/null +++ b/crates/text2/src/tests.rs @@ -0,0 +1,764 @@ +use super::{network::Network, *}; +use clock::ReplicaId; +use rand::prelude::*; +use std::{ + cmp::Ordering, + env, + iter::Iterator, + time::{Duration, Instant}, +}; + +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } +} + +#[test] +fn test_edit() { + let mut buffer = Buffer::new(0, 0, "abc".into()); + assert_eq!(buffer.text(), "abc"); + buffer.edit([(3..3, "def")]); + assert_eq!(buffer.text(), "abcdef"); + buffer.edit([(0..0, "ghi")]); + assert_eq!(buffer.text(), "ghiabcdef"); + buffer.edit([(5..5, "jkl")]); + assert_eq!(buffer.text(), "ghiabjklcdef"); + buffer.edit([(6..7, "")]); + assert_eq!(buffer.text(), "ghiabjlcdef"); + buffer.edit([(4..9, "mno")]); + assert_eq!(buffer.text(), "ghiamnoef"); +} + +#[gpui2::test(iterations = 100)] +fn test_random_edits(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let reference_string_len = rng.gen_range(0..3); + let mut reference_string = RandomCharIter::new(&mut rng) + .take(reference_string_len) + .collect::(); + let mut buffer = Buffer::new(0, 0, reference_string.clone()); + LineEnding::normalize(&mut reference_string); + + buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200))); + let mut buffer_versions = Vec::new(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + for _i in 0..operations { + let (edits, _) = buffer.randomly_edit(&mut rng, 5); + for (old_range, new_text) in edits.iter().rev() { + reference_string.replace_range(old_range.clone(), new_text); + } + + assert_eq!(buffer.text(), reference_string); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + if rng.gen_bool(0.25) { + buffer.randomly_undo_redo(&mut rng); + reference_string = buffer.text(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + } + + let range = buffer.random_byte_range(0, &mut rng); + assert_eq!( + buffer.text_summary_for_range::(range.clone()), + TextSummary::from(&reference_string[range]) + ); + + buffer.check_invariants(); + + if rng.gen_bool(0.3) { + buffer_versions.push((buffer.clone(), buffer.subscribe())); + } + } + + for (old_buffer, subscription) in buffer_versions { + let edits = buffer + .edits_since::(&old_buffer.version) + .collect::>(); + + log::info!( + "applying edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in edits { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + + for _ in 0..5 { + let end_ix = old_buffer.clip_offset(rng.gen_range(0..=old_buffer.len()), Bias::Right); + let start_ix = old_buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let range = old_buffer.anchor_before(start_ix)..old_buffer.anchor_after(end_ix); + let mut old_text = old_buffer.text_for_range(range.clone()).collect::(); + let edits = buffer + .edits_since_in_range::(&old_buffer.version, range.clone()) + .collect::>(); + log::info!( + "applying edits since version {:?} to old text in range {:?}: {:?}: {:?}", + old_buffer.version(), + start_ix..end_ix, + old_text, + edits, + ); + + let new_text = buffer.text_for_range(range).collect::(); + for edit in edits { + old_text.replace_range( + edit.new.start..edit.new.start + edit.old_len(), + &new_text[edit.new], + ); + } + assert_eq!(old_text, new_text); + } + + let subscription_edits = subscription.consume(); + log::info!( + "applying subscription edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + subscription_edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in subscription_edits.into_inner() { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + } +} + +#[test] +fn test_line_endings() { + assert_eq!(LineEnding::detect(&"🍐✅\n".repeat(1000)), LineEnding::Unix); + assert_eq!(LineEnding::detect(&"abcd\n".repeat(1000)), LineEnding::Unix); + assert_eq!( + LineEnding::detect(&"🍐✅\r\n".repeat(1000)), + LineEnding::Windows + ); + assert_eq!( + LineEnding::detect(&"abcd\r\n".repeat(1000)), + LineEnding::Windows + ); + + let mut buffer = Buffer::new(0, 0, "one\r\ntwo\rthree".into()); + assert_eq!(buffer.text(), "one\ntwo\nthree"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer.check_invariants(); + + buffer.edit([(buffer.len()..buffer.len(), "\r\nfour")]); + buffer.edit([(0..0, "zero\r\n")]); + assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer.check_invariants(); +} + +#[test] +fn test_line_len() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abcd\nefg\nhij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs\n")]); + buffer.edit([(18..21, "\nPQ")]); + + assert_eq!(buffer.line_len(0), 4); + assert_eq!(buffer.line_len(1), 3); + assert_eq!(buffer.line_len(2), 5); + assert_eq!(buffer.line_len(3), 3); + assert_eq!(buffer.line_len(4), 4); + assert_eq!(buffer.line_len(5), 0); +} + +#[test] +fn test_common_prefix_at_position() { + let text = "a = str; b = δα"; + let buffer = Buffer::new(0, 0, text.into()); + + let offset1 = offset_after(text, "str"); + let offset2 = offset_after(text, "δα"); + + // the preceding word is a prefix of the suggestion + assert_eq!( + buffer.common_prefix_at(offset1, "string"), + range_of(text, "str"), + ); + // a suffix of the preceding word is a prefix of the suggestion + assert_eq!( + buffer.common_prefix_at(offset1, "tree"), + range_of(text, "tr"), + ); + // the preceding word is a substring of the suggestion, but not a prefix + assert_eq!( + buffer.common_prefix_at(offset1, "astro"), + empty_range_after(text, "str"), + ); + + // prefix matching is case insensitive. + assert_eq!( + buffer.common_prefix_at(offset1, "Strαngε"), + range_of(text, "str"), + ); + assert_eq!( + buffer.common_prefix_at(offset2, "ΔΑΜΝ"), + range_of(text, "δα"), + ); + + fn offset_after(text: &str, part: &str) -> usize { + text.find(part).unwrap() + part.len() + } + + fn empty_range_after(text: &str, part: &str) -> Range { + let offset = offset_after(text, part); + offset..offset + } + + fn range_of(text: &str, part: &str) -> Range { + let start = text.find(part).unwrap(); + start..start + part.len() + } +} + +#[test] +fn test_text_summary_for_range() { + let buffer = Buffer::new(0, 0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz".into()); + assert_eq!( + buffer.text_summary_for_range::(1..3), + TextSummary { + len: 2, + len_utf16: OffsetUtf16(2), + lines: Point::new(1, 0), + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 0, + longest_row_chars: 1, + } + ); + assert_eq!( + buffer.text_summary_for_range::(1..12), + TextSummary { + len: 11, + len_utf16: OffsetUtf16(11), + lines: Point::new(3, 0), + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 2, + longest_row_chars: 4, + } + ); + assert_eq!( + buffer.text_summary_for_range::(0..20), + TextSummary { + len: 20, + len_utf16: OffsetUtf16(20), + lines: Point::new(4, 1), + first_line_chars: 2, + last_line_chars: 1, + last_line_len_utf16: 1, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range::(0..22), + TextSummary { + len: 22, + len_utf16: OffsetUtf16(22), + lines: Point::new(4, 3), + first_line_chars: 2, + last_line_chars: 3, + last_line_len_utf16: 3, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range::(7..22), + TextSummary { + len: 15, + len_utf16: OffsetUtf16(15), + lines: Point::new(2, 3), + first_line_chars: 4, + last_line_chars: 3, + last_line_len_utf16: 3, + longest_row: 1, + longest_row_chars: 6, + } + ); +} + +#[test] +fn test_chars_at() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abcd\nefgh\nij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs")]); + buffer.edit([(18..21, "\nPQ")]); + + let chars = buffer.chars_at(Point::new(0, 0)); + assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(1, 0)); + assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(2, 0)); + assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(3, 0)); + assert_eq!(chars.collect::(), "mno\nPQrs"); + + let chars = buffer.chars_at(Point::new(4, 0)); + assert_eq!(chars.collect::(), "PQrs"); + + // Regression test: + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n")]); + buffer.edit([(60..60, "\n")]); + + let chars = buffer.chars_at(Point::new(6, 0)); + assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); +} + +#[test] +fn test_anchors() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abc")]); + let left_anchor = buffer.anchor_before(2); + let right_anchor = buffer.anchor_after(2); + + buffer.edit([(1..1, "def\n")]); + assert_eq!(buffer.text(), "adef\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 6); + assert_eq!(right_anchor.to_offset(&buffer), 6); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit([(2..3, "")]); + assert_eq!(buffer.text(), "adf\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 5); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit([(5..5, "ghi\n")]); + assert_eq!(buffer.text(), "adf\nbghi\nc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 9); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); + + buffer.edit([(7..9, "")]); + assert_eq!(buffer.text(), "adf\nbghc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 7); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 }); + + // Ensure anchoring to a point is equivalent to anchoring to an offset. + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 0 }), + buffer.anchor_before(0) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 1 }), + buffer.anchor_before(1) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 2 }), + buffer.anchor_before(2) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 3 }), + buffer.anchor_before(3) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 0 }), + buffer.anchor_before(4) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 1 }), + buffer.anchor_before(5) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 2 }), + buffer.anchor_before(6) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 3 }), + buffer.anchor_before(7) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 4 }), + buffer.anchor_before(8) + ); + + // Comparison between anchors. + let anchor_at_offset_0 = buffer.anchor_before(0); + let anchor_at_offset_1 = buffer.anchor_before(1); + let anchor_at_offset_2 = buffer.anchor_before(2); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer), + Ordering::Equal + ); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer), + Ordering::Less + ); + + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer), + Ordering::Greater + ); +} + +#[test] +fn test_anchors_at_start_and_end() { + let mut buffer = Buffer::new(0, 0, "".into()); + let before_start_anchor = buffer.anchor_before(0); + let after_end_anchor = buffer.anchor_after(0); + + buffer.edit([(0..0, "abc")]); + assert_eq!(buffer.text(), "abc"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_end_anchor.to_offset(&buffer), 3); + + let after_start_anchor = buffer.anchor_after(0); + let before_end_anchor = buffer.anchor_before(3); + + buffer.edit([(3..3, "def")]); + buffer.edit([(0..0, "ghi")]); + assert_eq!(buffer.text(), "ghiabcdef"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_start_anchor.to_offset(&buffer), 3); + assert_eq!(before_end_anchor.to_offset(&buffer), 6); + assert_eq!(after_end_anchor.to_offset(&buffer), 9); +} + +#[test] +fn test_undo_redo() { + let mut buffer = Buffer::new(0, 0, "1234".into()); + // Set group interval to zero so as to not group edits in the undo stack. + buffer.set_group_interval(Duration::from_secs(0)); + + buffer.edit([(1..1, "abx")]); + buffer.edit([(3..4, "yzef")]); + buffer.edit([(3..5, "cd")]); + assert_eq!(buffer.text(), "1abcdef234"); + + let entries = buffer.history.undo_stack.clone(); + assert_eq!(entries.len(), 3); + + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1cdef234"); + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdx234"); + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abx234"); + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1yzef234"); + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1234"); +} + +#[test] +fn test_history() { + let mut now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "123456".into()); + buffer.set_group_interval(Duration::from_millis(300)); + + let transaction_1 = buffer.start_transaction_at(now).unwrap(); + buffer.edit([(2..4, "cd")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56"); + + buffer.start_transaction_at(now); + buffer.edit([(4..5, "e")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + + now += buffer.transaction_group_interval() + Duration::from_millis(1); + buffer.start_transaction_at(now); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + + // Last transaction happened past the group interval, undo it on its own. + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + + // First two transactions happened within the group interval, undo them together. + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + // Redo the first two transactions together. + buffer.redo(); + assert_eq!(buffer.text(), "12cde6"); + + // Redo the last transaction on its own. + buffer.redo(); + assert_eq!(buffer.text(), "ab2cde6"); + + buffer.start_transaction_at(now); + assert!(buffer.end_transaction_at(now).is_none()); + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + + // Redo stack gets cleared after performing an edit. + buffer.start_transaction_at(now); + buffer.edit([(0..0, "X")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "X12cde6"); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + // Transactions can be grouped manually. + buffer.redo(); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); + buffer.group_until_transaction(transaction_1); + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); +} + +#[test] +fn test_finalize_last_transaction() { + let now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "123456".into()); + + buffer.start_transaction_at(now); + buffer.edit([(2..4, "cd")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56"); + + buffer.finalize_last_transaction(); + buffer.start_transaction_at(now); + buffer.edit([(4..5, "e")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + + buffer.start_transaction_at(now); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + + buffer.undo(); + assert_eq!(buffer.text(), "12cd56"); + + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + buffer.redo(); + assert_eq!(buffer.text(), "12cd56"); + + buffer.redo(); + assert_eq!(buffer.text(), "ab2cde6"); +} + +#[test] +fn test_edited_ranges_for_transaction() { + let now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "1234567".into()); + + buffer.start_transaction_at(now); + buffer.edit([(2..4, "cd")]); + buffer.edit([(6..6, "efg")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56efg7"); + + let tx = buffer.finalize_last_transaction().unwrap().clone(); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 6..9] + ); + + buffer.edit([(5..5, "hijk")]); + assert_eq!(buffer.text(), "12cd5hijk6efg7"); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 10..13] + ); + + buffer.edit([(4..4, "l")]); + assert_eq!(buffer.text(), "12cdl5hijk6efg7"); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 11..14] + ); +} + +#[test] +fn test_concurrent_edits() { + let text = "abcdef"; + + let mut buffer1 = Buffer::new(1, 0, text.into()); + let mut buffer2 = Buffer::new(2, 0, text.into()); + let mut buffer3 = Buffer::new(3, 0, text.into()); + + let buf1_op = buffer1.edit([(1..2, "12")]); + assert_eq!(buffer1.text(), "a12cdef"); + let buf2_op = buffer2.edit([(3..4, "34")]); + assert_eq!(buffer2.text(), "abc34ef"); + let buf3_op = buffer3.edit([(5..6, "56")]); + assert_eq!(buffer3.text(), "abcde56"); + + buffer1.apply_op(buf2_op.clone()).unwrap(); + buffer1.apply_op(buf3_op.clone()).unwrap(); + buffer2.apply_op(buf1_op.clone()).unwrap(); + buffer2.apply_op(buf3_op).unwrap(); + buffer3.apply_op(buf1_op).unwrap(); + buffer3.apply_op(buf2_op).unwrap(); + + assert_eq!(buffer1.text(), "a12c34e56"); + assert_eq!(buffer2.text(), "a12c34e56"); + assert_eq!(buffer3.text(), "a12c34e56"); +} + +#[gpui2::test(iterations = 100)] +fn test_random_concurrent_edits(mut rng: StdRng) { + let peers = env::var("PEERS") + .map(|i| i.parse().expect("invalid `PEERS` variable")) + .unwrap_or(5); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let base_text_len = rng.gen_range(0..10); + let base_text = RandomCharIter::new(&mut rng) + .take(base_text_len) + .collect::(); + let mut replica_ids = Vec::new(); + let mut buffers = Vec::new(); + let mut network = Network::new(rng.clone()); + + for i in 0..peers { + let mut buffer = Buffer::new(i as ReplicaId, 0, base_text.clone()); + buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); + buffers.push(buffer); + replica_ids.push(i as u16); + network.add_peer(i as u16); + } + + log::info!("initial text: {:?}", base_text); + + let mut mutation_count = operations; + loop { + let replica_index = rng.gen_range(0..peers); + let replica_id = replica_ids[replica_index]; + let buffer = &mut buffers[replica_index]; + match rng.gen_range(0..=100) { + 0..=50 if mutation_count != 0 => { + let op = buffer.randomly_edit(&mut rng, 5).1; + network.broadcast(buffer.replica_id, vec![op]); + log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); + mutation_count -= 1; + } + 51..=70 if mutation_count != 0 => { + let ops = buffer.randomly_undo_redo(&mut rng); + network.broadcast(buffer.replica_id, ops); + mutation_count -= 1; + } + 71..=100 if network.has_unreceived(replica_id) => { + let ops = network.receive(replica_id); + if !ops.is_empty() { + log::info!( + "peer {} applying {} ops from the network.", + replica_id, + ops.len() + ); + buffer.apply_ops(ops).unwrap(); + } + } + _ => {} + } + buffer.check_invariants(); + + if mutation_count == 0 && network.is_idle() { + break; + } + } + + let first_buffer = &buffers[0]; + for buffer in &buffers[1..] { + assert_eq!( + buffer.text(), + first_buffer.text(), + "Replica {} text != Replica 0 text", + buffer.replica_id + ); + buffer.check_invariants(); + } +} diff --git a/crates/text2/src/text2.rs b/crates/text2/src/text2.rs new file mode 100644 index 0000000000..c05ea1109c --- /dev/null +++ b/crates/text2/src/text2.rs @@ -0,0 +1,2682 @@ +mod anchor; +pub mod locator; +#[cfg(any(test, feature = "test-support"))] +pub mod network; +pub mod operation_queue; +mod patch; +mod selection; +pub mod subscription; +#[cfg(test)] +mod tests; +mod undo_map; + +pub use anchor::*; +use anyhow::{anyhow, Result}; +pub use clock::ReplicaId; +use collections::{HashMap, HashSet}; +use locator::Locator; +use operation_queue::OperationQueue; +pub use patch::Patch; +use postage::{oneshot, prelude::*}; + +use lazy_static::lazy_static; +use regex::Regex; +pub use rope::*; +pub use selection::*; +use std::{ + borrow::Cow, + cmp::{self, Ordering, Reverse}, + future::Future, + iter::Iterator, + ops::{self, Deref, Range, Sub}, + str, + sync::Arc, + time::{Duration, Instant}, +}; +pub use subscription::*; +pub use sum_tree::Bias; +use sum_tree::{FilterCursor, SumTree, TreeMap}; +use undo_map::UndoMap; +use util::ResultExt; + +#[cfg(any(test, feature = "test-support"))] +use util::RandomCharIter; + +lazy_static! { + static ref LINE_SEPARATORS_REGEX: Regex = Regex::new("\r\n|\r|\u{2028}|\u{2029}").unwrap(); +} + +pub type TransactionId = clock::Lamport; + +pub struct Buffer { + snapshot: BufferSnapshot, + history: History, + deferred_ops: OperationQueue, + deferred_replicas: HashSet, + pub lamport_clock: clock::Lamport, + subscriptions: Topic, + edit_id_resolvers: HashMap>>, + wait_for_version_txs: Vec<(clock::Global, oneshot::Sender<()>)>, +} + +#[derive(Clone)] +pub struct BufferSnapshot { + replica_id: ReplicaId, + remote_id: u64, + visible_text: Rope, + deleted_text: Rope, + line_ending: LineEnding, + undo_map: UndoMap, + fragments: SumTree, + insertions: SumTree, + pub version: clock::Global, +} + +#[derive(Clone, Debug)] +pub struct HistoryEntry { + transaction: Transaction, + first_edit_at: Instant, + last_edit_at: Instant, + suppress_grouping: bool, +} + +#[derive(Clone, Debug)] +pub struct Transaction { + pub id: TransactionId, + pub edit_ids: Vec, + pub start: clock::Global, +} + +impl HistoryEntry { + pub fn transaction_id(&self) -> TransactionId { + self.transaction.id + } +} + +struct History { + base_text: Rope, + operations: TreeMap, + insertion_slices: HashMap>, + undo_stack: Vec, + redo_stack: Vec, + transaction_depth: usize, + group_interval: Duration, +} + +#[derive(Clone, Debug)] +struct InsertionSlice { + insertion_id: clock::Lamport, + range: Range, +} + +impl History { + pub fn new(base_text: Rope) -> Self { + Self { + base_text, + operations: Default::default(), + insertion_slices: Default::default(), + undo_stack: Vec::new(), + redo_stack: Vec::new(), + transaction_depth: 0, + // Don't group transactions in tests unless we opt in, because it's a footgun. + #[cfg(any(test, feature = "test-support"))] + group_interval: Duration::ZERO, + #[cfg(not(any(test, feature = "test-support")))] + group_interval: Duration::from_millis(300), + } + } + + fn push(&mut self, op: Operation) { + self.operations.insert(op.timestamp(), op); + } + + fn start_transaction( + &mut self, + start: clock::Global, + now: Instant, + clock: &mut clock::Lamport, + ) -> Option { + self.transaction_depth += 1; + if self.transaction_depth == 1 { + let id = clock.tick(); + self.undo_stack.push(HistoryEntry { + transaction: Transaction { + id, + start, + edit_ids: Default::default(), + }, + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + Some(id) + } else { + None + } + } + + fn end_transaction(&mut self, now: Instant) -> Option<&HistoryEntry> { + assert_ne!(self.transaction_depth, 0); + self.transaction_depth -= 1; + if self.transaction_depth == 0 { + if self + .undo_stack + .last() + .unwrap() + .transaction + .edit_ids + .is_empty() + { + self.undo_stack.pop(); + None + } else { + self.redo_stack.clear(); + let entry = self.undo_stack.last_mut().unwrap(); + entry.last_edit_at = now; + Some(entry) + } + } else { + None + } + } + + fn group(&mut self) -> Option { + let mut count = 0; + let mut entries = self.undo_stack.iter(); + if let Some(mut entry) = entries.next_back() { + while let Some(prev_entry) = entries.next_back() { + if !prev_entry.suppress_grouping + && entry.first_edit_at - prev_entry.last_edit_at <= self.group_interval + { + entry = prev_entry; + count += 1; + } else { + break; + } + } + } + self.group_trailing(count) + } + + fn group_until(&mut self, transaction_id: TransactionId) { + let mut count = 0; + for entry in self.undo_stack.iter().rev() { + if entry.transaction_id() == transaction_id { + self.group_trailing(count); + break; + } else if entry.suppress_grouping { + break; + } else { + count += 1; + } + } + } + + fn group_trailing(&mut self, n: usize) -> Option { + let new_len = self.undo_stack.len() - n; + let (entries_to_keep, entries_to_merge) = self.undo_stack.split_at_mut(new_len); + if let Some(last_entry) = entries_to_keep.last_mut() { + for entry in &*entries_to_merge { + for edit_id in &entry.transaction.edit_ids { + last_entry.transaction.edit_ids.push(*edit_id); + } + } + + if let Some(entry) = entries_to_merge.last_mut() { + last_entry.last_edit_at = entry.last_edit_at; + } + } + + self.undo_stack.truncate(new_len); + self.undo_stack.last().map(|e| e.transaction.id) + } + + fn finalize_last_transaction(&mut self) -> Option<&Transaction> { + self.undo_stack.last_mut().map(|entry| { + entry.suppress_grouping = true; + &entry.transaction + }) + } + + fn push_transaction(&mut self, transaction: Transaction, now: Instant) { + assert_eq!(self.transaction_depth, 0); + self.undo_stack.push(HistoryEntry { + transaction, + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + self.redo_stack.clear(); + } + + fn push_undo(&mut self, op_id: clock::Lamport) { + assert_ne!(self.transaction_depth, 0); + if let Some(Operation::Edit(_)) = self.operations.get(&op_id) { + let last_transaction = self.undo_stack.last_mut().unwrap(); + last_transaction.transaction.edit_ids.push(op_id); + } + } + + fn pop_undo(&mut self) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + if let Some(entry) = self.undo_stack.pop() { + self.redo_stack.push(entry); + self.redo_stack.last() + } else { + None + } + } + + fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + + let entry_ix = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id)?; + let entry = self.undo_stack.remove(entry_ix); + self.redo_stack.push(entry); + self.redo_stack.last() + } + + fn remove_from_undo_until(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + assert_eq!(self.transaction_depth, 0); + + let redo_stack_start_len = self.redo_stack.len(); + if let Some(entry_ix) = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + self.redo_stack + .extend(self.undo_stack.drain(entry_ix..).rev()); + } + &self.redo_stack[redo_stack_start_len..] + } + + fn forget(&mut self, transaction_id: TransactionId) -> Option { + assert_eq!(self.transaction_depth, 0); + if let Some(entry_ix) = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + Some(self.undo_stack.remove(entry_ix).transaction) + } else if let Some(entry_ix) = self + .redo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + Some(self.redo_stack.remove(entry_ix).transaction) + } else { + None + } + } + + fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> { + let entry = self + .undo_stack + .iter_mut() + .rfind(|entry| entry.transaction.id == transaction_id) + .or_else(|| { + self.redo_stack + .iter_mut() + .rfind(|entry| entry.transaction.id == transaction_id) + })?; + Some(&mut entry.transaction) + } + + fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { + if let Some(transaction) = self.forget(transaction) { + if let Some(destination) = self.transaction_mut(destination) { + destination.edit_ids.extend(transaction.edit_ids); + } + } + } + + fn pop_redo(&mut self) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + if let Some(entry) = self.redo_stack.pop() { + self.undo_stack.push(entry); + self.undo_stack.last() + } else { + None + } + } + + fn remove_from_redo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + assert_eq!(self.transaction_depth, 0); + + let undo_stack_start_len = self.undo_stack.len(); + if let Some(entry_ix) = self + .redo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + self.undo_stack + .extend(self.redo_stack.drain(entry_ix..).rev()); + } + &self.undo_stack[undo_stack_start_len..] + } +} + +struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> { + visible_cursor: rope::Cursor<'a>, + deleted_cursor: rope::Cursor<'a>, + fragments_cursor: Option>, + undos: &'a UndoMap, + since: &'a clock::Global, + old_end: D, + new_end: D, + range: Range<(&'a Locator, usize)>, + buffer_id: u64, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Edit { + pub old: Range, + pub new: Range, +} + +impl Edit +where + D: Sub + PartialEq + Copy, +{ + pub fn old_len(&self) -> D { + self.old.end - self.old.start + } + + pub fn new_len(&self) -> D { + self.new.end - self.new.start + } + + pub fn is_empty(&self) -> bool { + self.old.start == self.old.end && self.new.start == self.new.end + } +} + +impl Edit<(D1, D2)> { + pub fn flatten(self) -> (Edit, Edit) { + ( + Edit { + old: self.old.start.0..self.old.end.0, + new: self.new.start.0..self.new.end.0, + }, + Edit { + old: self.old.start.1..self.old.end.1, + new: self.new.start.1..self.new.end.1, + }, + ) + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct Fragment { + pub id: Locator, + pub timestamp: clock::Lamport, + pub insertion_offset: usize, + pub len: usize, + pub visible: bool, + pub deletions: HashSet, + pub max_undos: clock::Global, +} + +#[derive(Eq, PartialEq, Clone, Debug)] +pub struct FragmentSummary { + text: FragmentTextSummary, + max_id: Locator, + max_version: clock::Global, + min_insertion_version: clock::Global, + max_insertion_version: clock::Global, +} + +#[derive(Copy, Default, Clone, Debug, PartialEq, Eq)] +struct FragmentTextSummary { + visible: usize, + deleted: usize, +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary { + fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { + self.visible += summary.text.visible; + self.deleted += summary.text.deleted; + } +} + +#[derive(Eq, PartialEq, Clone, Debug)] +struct InsertionFragment { + timestamp: clock::Lamport, + split_offset: usize, + fragment_id: Locator, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct InsertionFragmentKey { + timestamp: clock::Lamport, + split_offset: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Operation { + Edit(EditOperation), + Undo(UndoOperation), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EditOperation { + pub timestamp: clock::Lamport, + pub version: clock::Global, + pub ranges: Vec>, + pub new_text: Vec>, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UndoOperation { + pub timestamp: clock::Lamport, + pub version: clock::Global, + pub counts: HashMap, +} + +impl Buffer { + pub fn new(replica_id: u16, remote_id: u64, mut base_text: String) -> Buffer { + let line_ending = LineEnding::detect(&base_text); + LineEnding::normalize(&mut base_text); + + let history = History::new(Rope::from(base_text.as_ref())); + let mut fragments = SumTree::new(); + let mut insertions = SumTree::new(); + + let mut lamport_clock = clock::Lamport::new(replica_id); + let mut version = clock::Global::new(); + + let visible_text = history.base_text.clone(); + if !visible_text.is_empty() { + let insertion_timestamp = clock::Lamport { + replica_id: 0, + value: 1, + }; + lamport_clock.observe(insertion_timestamp); + version.observe(insertion_timestamp); + let fragment_id = Locator::between(&Locator::min(), &Locator::max()); + let fragment = Fragment { + id: fragment_id, + timestamp: insertion_timestamp, + insertion_offset: 0, + len: visible_text.len(), + visible: true, + deletions: Default::default(), + max_undos: Default::default(), + }; + insertions.push(InsertionFragment::new(&fragment), &()); + fragments.push(fragment, &None); + } + + Buffer { + snapshot: BufferSnapshot { + replica_id, + remote_id, + visible_text, + deleted_text: Rope::new(), + line_ending, + fragments, + insertions, + version, + undo_map: Default::default(), + }, + history, + deferred_ops: OperationQueue::new(), + deferred_replicas: HashSet::default(), + lamport_clock, + subscriptions: Default::default(), + edit_id_resolvers: Default::default(), + wait_for_version_txs: Default::default(), + } + } + + pub fn version(&self) -> clock::Global { + self.version.clone() + } + + pub fn snapshot(&self) -> BufferSnapshot { + self.snapshot.clone() + } + + pub fn replica_id(&self) -> ReplicaId { + self.lamport_clock.replica_id + } + + pub fn remote_id(&self) -> u64 { + self.remote_id + } + + pub fn deferred_ops_len(&self) -> usize { + self.deferred_ops.len() + } + + pub fn transaction_group_interval(&self) -> Duration { + self.history.group_interval + } + + pub fn edit(&mut self, edits: R) -> Operation + where + R: IntoIterator, + I: ExactSizeIterator, T)>, + S: ToOffset, + T: Into>, + { + let edits = edits + .into_iter() + .map(|(range, new_text)| (range, new_text.into())); + + self.start_transaction(); + let timestamp = self.lamport_clock.tick(); + let operation = Operation::Edit(self.apply_local_edit(edits, timestamp)); + + self.history.push(operation.clone()); + self.history.push_undo(operation.timestamp()); + self.snapshot.version.observe(operation.timestamp()); + self.end_transaction(); + operation + } + + fn apply_local_edit>>( + &mut self, + edits: impl ExactSizeIterator, T)>, + timestamp: clock::Lamport, + ) -> EditOperation { + let mut edits_patch = Patch::default(); + let mut edit_op = EditOperation { + timestamp, + version: self.version(), + ranges: Vec::with_capacity(edits.len()), + new_text: Vec::with_capacity(edits.len()), + }; + let mut new_insertions = Vec::new(); + let mut insertion_offset = 0; + let mut insertion_slices = Vec::new(); + + let mut edits = edits + .map(|(range, new_text)| (range.to_offset(&*self), new_text)) + .peekable(); + + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::(); + let mut new_fragments = + old_fragments.slice(&edits.peek().unwrap().0.start, Bias::Right, &None); + new_ropes.append(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().visible; + for (range, new_text) in edits { + let new_text = LineEnding::normalize_arc(new_text.into()); + let fragment_end = old_fragments.end(&None).visible; + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().visible { + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + suffix.insertion_offset += fragment_start - old_fragments.start().visible; + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let slice = old_fragments.slice(&range.start, Bias::Right, &None); + new_ropes.append(slice.summary().text); + new_fragments.append(slice, &None); + fragment_start = old_fragments.start().visible; + } + + let full_range_start = FullOffset(range.start + old_fragments.start().deleted); + + // Preserve any portion of the current fragment that precedes this range. + if fragment_start < range.start { + let mut prefix = old_fragments.item().unwrap().clone(); + prefix.len = range.start - fragment_start; + prefix.insertion_offset += fragment_start - old_fragments.start().visible; + prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id); + new_insertions.push(InsertionFragment::insert_new(&prefix)); + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + fragment_start = range.start; + } + + // Insert the new text before any existing fragments within the range. + if !new_text.is_empty() { + let new_start = new_fragments.summary().text.visible; + + let fragment = Fragment { + id: Locator::between( + &new_fragments.summary().max_id, + old_fragments + .item() + .map_or(&Locator::max(), |old_fragment| &old_fragment.id), + ), + timestamp, + insertion_offset, + len: new_text.len(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, + }; + edits_patch.push(Edit { + old: fragment_start..fragment_start, + new: new_start..new_start + new_text.len(), + }); + insertion_slices.push(fragment.insertion_slice()); + new_insertions.push(InsertionFragment::insert_new(&fragment)); + new_ropes.push_str(new_text.as_ref()); + new_fragments.push(fragment, &None); + insertion_offset += new_text.len(); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&None).visible; + let mut intersection = fragment.clone(); + let intersection_end = cmp::min(range.end, fragment_end); + if fragment.visible { + intersection.len = intersection_end - fragment_start; + intersection.insertion_offset += fragment_start - old_fragments.start().visible; + intersection.id = + Locator::between(&new_fragments.summary().max_id, &intersection.id); + intersection.deletions.insert(timestamp); + intersection.visible = false; + } + if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let new_start = new_fragments.summary().text.visible; + edits_patch.push(Edit { + old: fragment_start..intersection_end, + new: new_start..new_start, + }); + insertion_slices.push(intersection.insertion_slice()); + } + new_insertions.push(InsertionFragment::insert_new(&intersection)); + new_ropes.push_fragment(&intersection, fragment.visible); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&None); + } + } + + let full_range_end = FullOffset(range.end + old_fragments.start().deleted); + edit_op.ranges.push(full_range_start..full_range_end); + edit_op.new_text.push(new_text); + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().visible { + let fragment_end = old_fragments.end(&None).visible; + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + suffix.insertion_offset += fragment_start - old_fragments.start().visible; + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let suffix = old_fragments.suffix(&None); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.snapshot.fragments = new_fragments; + self.snapshot.insertions.edit(new_insertions, &()); + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.subscriptions.publish_mut(&edits_patch); + self.history + .insertion_slices + .insert(timestamp, insertion_slices); + edit_op + } + + pub fn set_line_ending(&mut self, line_ending: LineEnding) { + self.snapshot.line_ending = line_ending; + } + + pub fn apply_ops>(&mut self, ops: I) -> Result<()> { + let mut deferred_ops = Vec::new(); + for op in ops { + self.history.push(op.clone()); + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + self.flush_deferred_ops()?; + Ok(()) + } + + fn apply_op(&mut self, op: Operation) -> Result<()> { + match op { + Operation::Edit(edit) => { + if !self.version.observed(edit.timestamp) { + self.apply_remote_edit( + &edit.version, + &edit.ranges, + &edit.new_text, + edit.timestamp, + ); + self.snapshot.version.observe(edit.timestamp); + self.lamport_clock.observe(edit.timestamp); + self.resolve_edit(edit.timestamp); + } + } + Operation::Undo(undo) => { + if !self.version.observed(undo.timestamp) { + self.apply_undo(&undo)?; + self.snapshot.version.observe(undo.timestamp); + self.lamport_clock.observe(undo.timestamp); + } + } + } + self.wait_for_version_txs.retain_mut(|(version, tx)| { + if self.snapshot.version().observed_all(version) { + tx.try_send(()).ok(); + false + } else { + true + } + }); + Ok(()) + } + + fn apply_remote_edit( + &mut self, + version: &clock::Global, + ranges: &[Range], + new_text: &[Arc], + timestamp: clock::Lamport, + ) { + if ranges.is_empty() { + return; + } + + let edits = ranges.iter().zip(new_text.iter()); + let mut edits_patch = Patch::default(); + let mut insertion_slices = Vec::new(); + let cx = Some(version.clone()); + let mut new_insertions = Vec::new(); + let mut insertion_offset = 0; + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); + let mut new_fragments = old_fragments.slice( + &VersionedFullOffset::Offset(ranges[0].start), + Bias::Left, + &cx, + ); + new_ropes.append(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().0.full_offset(); + for (range, new_text) in edits { + let fragment_end = old_fragments.end(&cx).0.full_offset(); + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().0.full_offset() { + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end.0 - fragment_start.0; + suffix.insertion_offset += + fragment_start - old_fragments.start().0.full_offset(); + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let slice = + old_fragments.slice(&VersionedFullOffset::Offset(range.start), Bias::Left, &cx); + new_ropes.append(slice.summary().text); + new_fragments.append(slice, &None); + fragment_start = old_fragments.start().0.full_offset(); + } + + // If we are at the end of a non-concurrent fragment, advance to the next one. + let fragment_end = old_fragments.end(&cx).0.full_offset(); + if fragment_end == range.start && fragment_end > fragment_start { + let mut fragment = old_fragments.item().unwrap().clone(); + fragment.len = fragment_end.0 - fragment_start.0; + fragment.insertion_offset += fragment_start - old_fragments.start().0.full_offset(); + new_insertions.push(InsertionFragment::insert_new(&fragment)); + new_ropes.push_fragment(&fragment, fragment.visible); + new_fragments.push(fragment, &None); + old_fragments.next(&cx); + fragment_start = old_fragments.start().0.full_offset(); + } + + // Skip over insertions that are concurrent to this edit, but have a lower lamport + // timestamp. + while let Some(fragment) = old_fragments.item() { + if fragment_start == range.start && fragment.timestamp > timestamp { + new_ropes.push_fragment(fragment, fragment.visible); + new_fragments.push(fragment.clone(), &None); + old_fragments.next(&cx); + debug_assert_eq!(fragment_start, range.start); + } else { + break; + } + } + debug_assert!(fragment_start <= range.start); + + // Preserve any portion of the current fragment that precedes this range. + if fragment_start < range.start { + let mut prefix = old_fragments.item().unwrap().clone(); + prefix.len = range.start.0 - fragment_start.0; + prefix.insertion_offset += fragment_start - old_fragments.start().0.full_offset(); + prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id); + new_insertions.push(InsertionFragment::insert_new(&prefix)); + fragment_start = range.start; + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + } + + // Insert the new text before any existing fragments within the range. + if !new_text.is_empty() { + let mut old_start = old_fragments.start().1; + if old_fragments.item().map_or(false, |f| f.visible) { + old_start += fragment_start.0 - old_fragments.start().0.full_offset().0; + } + let new_start = new_fragments.summary().text.visible; + let fragment = Fragment { + id: Locator::between( + &new_fragments.summary().max_id, + old_fragments + .item() + .map_or(&Locator::max(), |old_fragment| &old_fragment.id), + ), + timestamp, + insertion_offset, + len: new_text.len(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, + }; + edits_patch.push(Edit { + old: old_start..old_start, + new: new_start..new_start + new_text.len(), + }); + insertion_slices.push(fragment.insertion_slice()); + new_insertions.push(InsertionFragment::insert_new(&fragment)); + new_ropes.push_str(new_text); + new_fragments.push(fragment, &None); + insertion_offset += new_text.len(); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); + let mut intersection = fragment.clone(); + let intersection_end = cmp::min(range.end, fragment_end); + if fragment.was_visible(version, &self.undo_map) { + intersection.len = intersection_end.0 - fragment_start.0; + intersection.insertion_offset += + fragment_start - old_fragments.start().0.full_offset(); + intersection.id = + Locator::between(&new_fragments.summary().max_id, &intersection.id); + intersection.deletions.insert(timestamp); + intersection.visible = false; + insertion_slices.push(intersection.insertion_slice()); + } + if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let old_start = old_fragments.start().1 + + (fragment_start.0 - old_fragments.start().0.full_offset().0); + let new_start = new_fragments.summary().text.visible; + edits_patch.push(Edit { + old: old_start..old_start + intersection.len, + new: new_start..new_start, + }); + } + new_insertions.push(InsertionFragment::insert_new(&intersection)); + new_ropes.push_fragment(&intersection, fragment.visible); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&cx); + } + } + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().0.full_offset() { + let fragment_end = old_fragments.end(&cx).0.full_offset(); + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end.0 - fragment_start.0; + suffix.insertion_offset += fragment_start - old_fragments.start().0.full_offset(); + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let suffix = old_fragments.suffix(&cx); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.snapshot.fragments = new_fragments; + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.snapshot.insertions.edit(new_insertions, &()); + self.history + .insertion_slices + .insert(timestamp, insertion_slices); + self.subscriptions.publish_mut(&edits_patch) + } + + fn fragment_ids_for_edits<'a>( + &'a self, + edit_ids: impl Iterator, + ) -> Vec<&'a Locator> { + // Get all of the insertion slices changed by the given edits. + let mut insertion_slices = Vec::new(); + for edit_id in edit_ids { + if let Some(slices) = self.history.insertion_slices.get(edit_id) { + insertion_slices.extend_from_slice(slices) + } + } + insertion_slices + .sort_unstable_by_key(|s| (s.insertion_id, s.range.start, Reverse(s.range.end))); + + // Get all of the fragments corresponding to these insertion slices. + let mut fragment_ids = Vec::new(); + let mut insertions_cursor = self.insertions.cursor::(); + for insertion_slice in &insertion_slices { + if insertion_slice.insertion_id != insertions_cursor.start().timestamp + || insertion_slice.range.start > insertions_cursor.start().split_offset + { + insertions_cursor.seek_forward( + &InsertionFragmentKey { + timestamp: insertion_slice.insertion_id, + split_offset: insertion_slice.range.start, + }, + Bias::Left, + &(), + ); + } + while let Some(item) = insertions_cursor.item() { + if item.timestamp != insertion_slice.insertion_id + || item.split_offset >= insertion_slice.range.end + { + break; + } + fragment_ids.push(&item.fragment_id); + insertions_cursor.next(&()); + } + } + fragment_ids.sort_unstable(); + fragment_ids + } + + fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { + self.snapshot.undo_map.insert(undo); + + let mut edits = Patch::default(); + let mut old_fragments = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let mut new_fragments = SumTree::new(); + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + + for fragment_id in self.fragment_ids_for_edits(undo.counts.keys()) { + let preceding_fragments = old_fragments.slice(&Some(fragment_id), Bias::Left, &None); + new_ropes.append(preceding_fragments.summary().text); + new_fragments.append(preceding_fragments, &None); + + if let Some(fragment) = old_fragments.item() { + let mut fragment = fragment.clone(); + let fragment_was_visible = fragment.visible; + + fragment.visible = fragment.is_visible(&self.undo_map); + fragment.max_undos.observe(undo.timestamp); + + let old_start = old_fragments.start().1; + let new_start = new_fragments.summary().text.visible; + if fragment_was_visible && !fragment.visible { + edits.push(Edit { + old: old_start..old_start + fragment.len, + new: new_start..new_start, + }); + } else if !fragment_was_visible && fragment.visible { + edits.push(Edit { + old: old_start..old_start, + new: new_start..new_start + fragment.len, + }); + } + new_ropes.push_fragment(&fragment, fragment_was_visible); + new_fragments.push(fragment, &None); + + old_fragments.next(&None); + } + } + + let suffix = old_fragments.suffix(&None); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + + drop(old_fragments); + let (visible_text, deleted_text) = new_ropes.finish(); + self.snapshot.fragments = new_fragments; + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.subscriptions.publish_mut(&edits); + Ok(()) + } + + fn flush_deferred_ops(&mut self) -> Result<()> { + self.deferred_replicas.clear(); + let mut deferred_ops = Vec::new(); + for op in self.deferred_ops.drain().iter().cloned() { + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + Ok(()) + } + + fn can_apply_op(&self, op: &Operation) -> bool { + if self.deferred_replicas.contains(&op.replica_id()) { + false + } else { + self.version.observed_all(match op { + Operation::Edit(edit) => &edit.version, + Operation::Undo(undo) => &undo.version, + }) + } + } + + pub fn peek_undo_stack(&self) -> Option<&HistoryEntry> { + self.history.undo_stack.last() + } + + pub fn peek_redo_stack(&self) -> Option<&HistoryEntry> { + self.history.redo_stack.last() + } + + pub fn start_transaction(&mut self) -> Option { + self.start_transaction_at(Instant::now()) + } + + pub fn start_transaction_at(&mut self, now: Instant) -> Option { + self.history + .start_transaction(self.version.clone(), now, &mut self.lamport_clock) + } + + pub fn end_transaction(&mut self) -> Option<(TransactionId, clock::Global)> { + self.end_transaction_at(Instant::now()) + } + + pub fn end_transaction_at(&mut self, now: Instant) -> Option<(TransactionId, clock::Global)> { + if let Some(entry) = self.history.end_transaction(now) { + let since = entry.transaction.start.clone(); + let id = self.history.group().unwrap(); + Some((id, since)) + } else { + None + } + } + + pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> { + self.history.finalize_last_transaction() + } + + pub fn group_until_transaction(&mut self, transaction_id: TransactionId) { + self.history.group_until(transaction_id); + } + + pub fn base_text(&self) -> &Rope { + &self.history.base_text + } + + pub fn operations(&self) -> &TreeMap { + &self.history.operations + } + + pub fn undo(&mut self) -> Option<(TransactionId, Operation)> { + if let Some(entry) = self.history.pop_undo() { + let transaction = entry.transaction.clone(); + let transaction_id = transaction.id; + let op = self.undo_or_redo(transaction).unwrap(); + Some((transaction_id, op)) + } else { + None + } + } + + pub fn undo_transaction(&mut self, transaction_id: TransactionId) -> Option { + let transaction = self + .history + .remove_from_undo(transaction_id)? + .transaction + .clone(); + self.undo_or_redo(transaction).log_err() + } + + #[allow(clippy::needless_collect)] + pub fn undo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { + let transactions = self + .history + .remove_from_undo_until(transaction_id) + .iter() + .map(|entry| entry.transaction.clone()) + .collect::>(); + + transactions + .into_iter() + .map(|transaction| self.undo_or_redo(transaction).unwrap()) + .collect() + } + + pub fn forget_transaction(&mut self, transaction_id: TransactionId) { + self.history.forget(transaction_id); + } + + pub fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { + self.history.merge_transactions(transaction, destination); + } + + pub fn redo(&mut self) -> Option<(TransactionId, Operation)> { + if let Some(entry) = self.history.pop_redo() { + let transaction = entry.transaction.clone(); + let transaction_id = transaction.id; + let op = self.undo_or_redo(transaction).unwrap(); + Some((transaction_id, op)) + } else { + None + } + } + + #[allow(clippy::needless_collect)] + pub fn redo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { + let transactions = self + .history + .remove_from_redo(transaction_id) + .iter() + .map(|entry| entry.transaction.clone()) + .collect::>(); + + transactions + .into_iter() + .map(|transaction| self.undo_or_redo(transaction).unwrap()) + .collect() + } + + fn undo_or_redo(&mut self, transaction: Transaction) -> Result { + let mut counts = HashMap::default(); + for edit_id in transaction.edit_ids { + counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1); + } + + let undo = UndoOperation { + timestamp: self.lamport_clock.tick(), + version: self.version(), + counts, + }; + self.apply_undo(&undo)?; + self.snapshot.version.observe(undo.timestamp); + let operation = Operation::Undo(undo); + self.history.push(operation.clone()); + Ok(operation) + } + + pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) { + self.history.push_transaction(transaction, now); + self.history.finalize_last_transaction(); + } + + pub fn edited_ranges_for_transaction<'a, D>( + &'a self, + transaction: &'a Transaction, + ) -> impl 'a + Iterator> + where + D: TextDimension, + { + // get fragment ranges + let mut cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let offset_ranges = self + .fragment_ids_for_edits(transaction.edit_ids.iter()) + .into_iter() + .filter_map(move |fragment_id| { + cursor.seek_forward(&Some(fragment_id), Bias::Left, &None); + let fragment = cursor.item()?; + let start_offset = cursor.start().1; + let end_offset = start_offset + if fragment.visible { fragment.len } else { 0 }; + Some(start_offset..end_offset) + }); + + // combine adjacent ranges + let mut prev_range: Option> = None; + let disjoint_ranges = offset_ranges + .map(Some) + .chain([None]) + .filter_map(move |range| { + if let Some((range, prev_range)) = range.as_ref().zip(prev_range.as_mut()) { + if prev_range.end == range.start { + prev_range.end = range.end; + return None; + } + } + let result = prev_range.clone(); + prev_range = range; + result + }); + + // convert to the desired text dimension. + let mut position = D::default(); + let mut rope_cursor = self.visible_text.cursor(0); + disjoint_ranges.map(move |range| { + position.add_assign(&rope_cursor.summary(range.start)); + let start = position.clone(); + position.add_assign(&rope_cursor.summary(range.end)); + let end = position.clone(); + start..end + }) + } + + pub fn subscribe(&mut self) -> Subscription { + self.subscriptions.subscribe() + } + + pub fn wait_for_edits( + &mut self, + edit_ids: impl IntoIterator, + ) -> impl 'static + Future> { + let mut futures = Vec::new(); + for edit_id in edit_ids { + if !self.version.observed(edit_id) { + let (tx, rx) = oneshot::channel(); + self.edit_id_resolvers.entry(edit_id).or_default().push(tx); + futures.push(rx); + } + } + + async move { + for mut future in futures { + if future.recv().await.is_none() { + Err(anyhow!("gave up waiting for edits"))?; + } + } + Ok(()) + } + } + + pub fn wait_for_anchors( + &mut self, + anchors: impl IntoIterator, + ) -> impl 'static + Future> { + let mut futures = Vec::new(); + for anchor in anchors { + if !self.version.observed(anchor.timestamp) + && anchor != Anchor::MAX + && anchor != Anchor::MIN + { + let (tx, rx) = oneshot::channel(); + self.edit_id_resolvers + .entry(anchor.timestamp) + .or_default() + .push(tx); + futures.push(rx); + } + } + + async move { + for mut future in futures { + if future.recv().await.is_none() { + Err(anyhow!("gave up waiting for anchors"))?; + } + } + Ok(()) + } + } + + pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future> { + let mut rx = None; + if !self.snapshot.version.observed_all(&version) { + let channel = oneshot::channel(); + self.wait_for_version_txs.push((version, channel.0)); + rx = Some(channel.1); + } + async move { + if let Some(mut rx) = rx { + if rx.recv().await.is_none() { + Err(anyhow!("gave up waiting for version"))?; + } + } + Ok(()) + } + } + + pub fn give_up_waiting(&mut self) { + self.edit_id_resolvers.clear(); + self.wait_for_version_txs.clear(); + } + + fn resolve_edit(&mut self, edit_id: clock::Lamport) { + for mut tx in self + .edit_id_resolvers + .remove(&edit_id) + .into_iter() + .flatten() + { + tx.try_send(()).ok(); + } + } +} + +#[cfg(any(test, feature = "test-support"))] +impl Buffer { + pub fn edit_via_marked_text(&mut self, marked_string: &str) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits); + } + + pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range, String)> { + let old_text = self.text(); + let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false); + if ranges.is_empty() { + ranges.push(0..new_text.len()); + } + + assert_eq!( + old_text[..ranges[0].start], + new_text[..ranges[0].start], + "invalid edit" + ); + + let mut delta = 0; + let mut edits = Vec::new(); + let mut ranges = ranges.into_iter().peekable(); + + while let Some(inserted_range) = ranges.next() { + let new_start = inserted_range.start; + let old_start = (new_start as isize - delta) as usize; + + let following_text = if let Some(next_range) = ranges.peek() { + &new_text[inserted_range.end..next_range.start] + } else { + &new_text[inserted_range.end..] + }; + + let inserted_len = inserted_range.len(); + let deleted_len = old_text[old_start..] + .find(following_text) + .expect("invalid edit"); + + let old_range = old_start..old_start + deleted_len; + edits.push((old_range, new_text[inserted_range].to_string())); + delta += inserted_len as isize - deleted_len as isize; + } + + assert_eq!( + old_text.len() as isize + delta, + new_text.len() as isize, + "invalid edit" + ); + + edits + } + + pub fn check_invariants(&self) { + // Ensure every fragment is ordered by locator in the fragment tree and corresponds + // to an insertion fragment in the insertions tree. + let mut prev_fragment_id = Locator::min(); + for fragment in self.snapshot.fragments.items(&None) { + assert!(fragment.id > prev_fragment_id); + prev_fragment_id = fragment.id.clone(); + + let insertion_fragment = self + .snapshot + .insertions + .get( + &InsertionFragmentKey { + timestamp: fragment.timestamp, + split_offset: fragment.insertion_offset, + }, + &(), + ) + .unwrap(); + assert_eq!( + insertion_fragment.fragment_id, fragment.id, + "fragment: {:?}\ninsertion: {:?}", + fragment, insertion_fragment + ); + } + + let mut cursor = self.snapshot.fragments.cursor::>(); + for insertion_fragment in self.snapshot.insertions.cursor::<()>() { + cursor.seek(&Some(&insertion_fragment.fragment_id), Bias::Left, &None); + let fragment = cursor.item().unwrap(); + assert_eq!(insertion_fragment.fragment_id, fragment.id); + assert_eq!(insertion_fragment.split_offset, fragment.insertion_offset); + } + + let fragment_summary = self.snapshot.fragments.summary(); + assert_eq!( + fragment_summary.text.visible, + self.snapshot.visible_text.len() + ); + assert_eq!( + fragment_summary.text.deleted, + self.snapshot.deleted_text.len() + ); + + assert!(!self.text().contains("\r\n")); + } + + pub fn set_group_interval(&mut self, group_interval: Duration) { + self.history.group_interval = group_interval; + } + + pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range { + let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); + let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right); + start..end + } + + pub fn get_random_edits( + &self, + rng: &mut T, + edit_count: usize, + ) -> Vec<(Range, Arc)> + where + T: rand::Rng, + { + let mut edits: Vec<(Range, Arc)> = Vec::new(); + let mut last_end = None; + for _ in 0..edit_count { + if last_end.map_or(false, |last_end| last_end >= self.len()) { + break; + } + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let range = self.random_byte_range(new_start, rng); + last_end = Some(range.end); + + let new_text_len = rng.gen_range(0..10); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + edits.push((range, new_text.into())); + } + edits + } + + #[allow(clippy::type_complexity)] + pub fn randomly_edit( + &mut self, + rng: &mut T, + edit_count: usize, + ) -> (Vec<(Range, Arc)>, Operation) + where + T: rand::Rng, + { + let mut edits = self.get_random_edits(rng, edit_count); + log::info!("mutating buffer {} with {:?}", self.replica_id, edits); + + let op = self.edit(edits.iter().cloned()); + if let Operation::Edit(edit) = &op { + assert_eq!(edits.len(), edit.new_text.len()); + for (edit, new_text) in edits.iter_mut().zip(&edit.new_text) { + edit.1 = new_text.clone(); + } + } else { + unreachable!() + } + + (edits, op) + } + + pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec { + use rand::prelude::*; + + let mut ops = Vec::new(); + for _ in 0..rng.gen_range(1..=5) { + if let Some(entry) = self.history.undo_stack.choose(rng) { + let transaction = entry.transaction.clone(); + log::info!( + "undoing buffer {} transaction {:?}", + self.replica_id, + transaction + ); + ops.push(self.undo_or_redo(transaction).unwrap()); + } + } + ops + } +} + +impl Deref for Buffer { + type Target = BufferSnapshot; + + fn deref(&self) -> &Self::Target { + &self.snapshot + } +} + +impl BufferSnapshot { + pub fn as_rope(&self) -> &Rope { + &self.visible_text + } + + pub fn remote_id(&self) -> u64 { + self.remote_id + } + + pub fn replica_id(&self) -> ReplicaId { + self.replica_id + } + + pub fn row_count(&self) -> u32 { + self.max_point().row + 1 + } + + pub fn len(&self) -> usize { + self.visible_text.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn chars(&self) -> impl Iterator + '_ { + self.chars_at(0) + } + + pub fn chars_for_range(&self, range: Range) -> impl Iterator + '_ { + self.text_for_range(range).flat_map(str::chars) + } + + pub fn reversed_chars_for_range( + &self, + range: Range, + ) -> impl Iterator + '_ { + self.reversed_chunks_in_range(range) + .flat_map(|chunk| chunk.chars().rev()) + } + + pub fn contains_str_at(&self, position: T, needle: &str) -> bool + where + T: ToOffset, + { + let position = position.to_offset(self); + position == self.clip_offset(position, Bias::Left) + && self + .bytes_in_range(position..self.len()) + .flatten() + .copied() + .take(needle.len()) + .eq(needle.bytes()) + } + + pub fn common_prefix_at(&self, position: T, needle: &str) -> Range + where + T: ToOffset + TextDimension, + { + let offset = position.to_offset(self); + let common_prefix_len = needle + .char_indices() + .map(|(index, _)| index) + .chain([needle.len()]) + .take_while(|&len| len <= offset) + .filter(|&len| { + let left = self + .chars_for_range(offset - len..offset) + .flat_map(char::to_lowercase); + let right = needle[..len].chars().flat_map(char::to_lowercase); + left.eq(right) + }) + .last() + .unwrap_or(0); + let start_offset = offset - common_prefix_len; + let start = self.text_summary_for_range(0..start_offset); + start..position + } + + pub fn text(&self) -> String { + self.visible_text.to_string() + } + + pub fn line_ending(&self) -> LineEnding { + self.line_ending + } + + pub fn deleted_text(&self) -> String { + self.deleted_text.to_string() + } + + pub fn fragments(&self) -> impl Iterator { + self.fragments.iter() + } + + pub fn text_summary(&self) -> TextSummary { + self.visible_text.summary() + } + + pub fn max_point(&self) -> Point { + self.visible_text.max_point() + } + + pub fn max_point_utf16(&self) -> PointUtf16 { + self.visible_text.max_point_utf16() + } + + pub fn point_to_offset(&self, point: Point) -> usize { + self.visible_text.point_to_offset(point) + } + + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + self.visible_text.point_utf16_to_offset(point) + } + + pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped) -> usize { + self.visible_text.unclipped_point_utf16_to_offset(point) + } + + pub fn unclipped_point_utf16_to_point(&self, point: Unclipped) -> Point { + self.visible_text.unclipped_point_utf16_to_point(point) + } + + pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize { + self.visible_text.offset_utf16_to_offset(offset) + } + + pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 { + self.visible_text.offset_to_offset_utf16(offset) + } + + pub fn offset_to_point(&self, offset: usize) -> Point { + self.visible_text.offset_to_point(offset) + } + + pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { + self.visible_text.offset_to_point_utf16(offset) + } + + pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 { + self.visible_text.point_to_point_utf16(point) + } + + pub fn version(&self) -> &clock::Global { + &self.version + } + + pub fn chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.chars_at(offset) + } + + pub fn reversed_chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.reversed_chars_at(offset) + } + + pub fn reversed_chunks_in_range(&self, range: Range) -> rope::Chunks { + let range = range.start.to_offset(self)..range.end.to_offset(self); + self.visible_text.reversed_chunks_in_range(range) + } + + pub fn bytes_in_range(&self, range: Range) -> rope::Bytes<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.bytes_in_range(start..end) + } + + pub fn reversed_bytes_in_range(&self, range: Range) -> rope::Bytes<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.reversed_bytes_in_range(start..end) + } + + pub fn text_for_range(&self, range: Range) -> Chunks<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.chunks_in_range(start..end) + } + + pub fn line_len(&self, row: u32) -> u32 { + let row_start_offset = Point::new(row, 0).to_offset(self); + let row_end_offset = if row >= self.max_point().row { + self.len() + } else { + Point::new(row + 1, 0).to_offset(self) - 1 + }; + (row_end_offset - row_start_offset) as u32 + } + + pub fn is_line_blank(&self, row: u32) -> bool { + self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row))) + .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) + } + + pub fn text_summary_for_range(&self, range: Range) -> D + where + D: TextDimension, + { + self.visible_text + .cursor(range.start.to_offset(self)) + .summary(range.end.to_offset(self)) + } + + pub fn summaries_for_anchors<'a, D, A>(&'a self, anchors: A) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))) + .map(|d| d.0) + } + + pub fn summaries_for_anchors_with_payload<'a, D, A, T>( + &'a self, + anchors: A, + ) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + let mut insertion_cursor = self.insertions.cursor::(); + let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let mut text_cursor = self.visible_text.cursor(0); + let mut position = D::default(); + + anchors.map(move |(anchor, payload)| { + if *anchor == Anchor::MIN { + return (D::default(), payload); + } else if *anchor == Anchor::MAX { + return (D::from_text_summary(&self.visible_text.summary()), payload); + } + + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + + fragment_cursor.seek_forward(&Some(&insertion.fragment_id), Bias::Left, &None); + let fragment = fragment_cursor.item().unwrap(); + let mut fragment_offset = fragment_cursor.start().1; + if fragment.visible { + fragment_offset += anchor.offset - insertion.split_offset; + } + + position.add_assign(&text_cursor.summary(fragment_offset)); + (position.clone(), payload) + }) + } + + fn summary_for_anchor(&self, anchor: &Anchor) -> D + where + D: TextDimension, + { + if *anchor == Anchor::MIN { + D::default() + } else if *anchor == Anchor::MAX { + D::from_text_summary(&self.visible_text.summary()) + } else { + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + let mut insertion_cursor = self.insertions.cursor::(); + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + + let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + fragment_cursor.seek(&Some(&insertion.fragment_id), Bias::Left, &None); + let fragment = fragment_cursor.item().unwrap(); + let mut fragment_offset = fragment_cursor.start().1; + if fragment.visible { + fragment_offset += anchor.offset - insertion.split_offset; + } + self.text_summary_for_range(0..fragment_offset) + } + } + + fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { + if *anchor == Anchor::MIN { + Locator::min_ref() + } else if *anchor == Anchor::MAX { + Locator::max_ref() + } else { + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + let mut insertion_cursor = self.insertions.cursor::(); + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + &insertion.fragment_id + } + } + + pub fn anchor_before(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Left) + } + + pub fn anchor_after(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Right) + } + + pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { + self.anchor_at_offset(position.to_offset(self), bias) + } + + fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor { + if bias == Bias::Left && offset == 0 { + Anchor::MIN + } else if bias == Bias::Right && offset == self.len() { + Anchor::MAX + } else { + let mut fragment_cursor = self.fragments.cursor::(); + fragment_cursor.seek(&offset, bias, &None); + let fragment = fragment_cursor.item().unwrap(); + let overshoot = offset - *fragment_cursor.start(); + Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset + overshoot, + bias, + buffer_id: Some(self.remote_id), + } + } + } + + pub fn can_resolve(&self, anchor: &Anchor) -> bool { + *anchor == Anchor::MIN + || *anchor == Anchor::MAX + || (Some(self.remote_id) == anchor.buffer_id && self.version.observed(anchor.timestamp)) + } + + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + self.visible_text.clip_offset(offset, bias) + } + + pub fn clip_point(&self, point: Point, bias: Bias) -> Point { + self.visible_text.clip_point(point, bias) + } + + pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { + self.visible_text.clip_offset_utf16(offset, bias) + } + + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { + self.visible_text.clip_point_utf16(point, bias) + } + + pub fn edits_since<'a, D>( + &'a self, + since: &'a clock::Global, + ) -> impl 'a + Iterator> + where + D: TextDimension + Ord, + { + self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + } + + pub fn anchored_edits_since<'a, D>( + &'a self, + since: &'a clock::Global, + ) -> impl 'a + Iterator, Range)> + where + D: TextDimension + Ord, + { + self.anchored_edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + } + + pub fn edits_since_in_range<'a, D>( + &'a self, + since: &'a clock::Global, + range: Range, + ) -> impl 'a + Iterator> + where + D: TextDimension + Ord, + { + self.anchored_edits_since_in_range(since, range) + .map(|item| item.0) + } + + pub fn anchored_edits_since_in_range<'a, D>( + &'a self, + since: &'a clock::Global, + range: Range, + ) -> impl 'a + Iterator, Range)> + where + D: TextDimension + Ord, + { + let fragments_cursor = if *since == self.version { + None + } else { + let mut cursor = self + .fragments + .filter(move |summary| !since.observed_all(&summary.max_version)); + cursor.next(&None); + Some(cursor) + }; + let mut cursor = self + .fragments + .cursor::<(Option<&Locator>, FragmentTextSummary)>(); + + let start_fragment_id = self.fragment_id_for_anchor(&range.start); + cursor.seek(&Some(start_fragment_id), Bias::Left, &None); + let mut visible_start = cursor.start().1.visible; + let mut deleted_start = cursor.start().1.deleted; + if let Some(fragment) = cursor.item() { + let overshoot = range.start.offset - fragment.insertion_offset; + if fragment.visible { + visible_start += overshoot; + } else { + deleted_start += overshoot; + } + } + let end_fragment_id = self.fragment_id_for_anchor(&range.end); + + Edits { + visible_cursor: self.visible_text.cursor(visible_start), + deleted_cursor: self.deleted_text.cursor(deleted_start), + fragments_cursor, + undos: &self.undo_map, + since, + old_end: Default::default(), + new_end: Default::default(), + range: (start_fragment_id, range.start.offset)..(end_fragment_id, range.end.offset), + buffer_id: self.remote_id, + } + } +} + +struct RopeBuilder<'a> { + old_visible_cursor: rope::Cursor<'a>, + old_deleted_cursor: rope::Cursor<'a>, + new_visible: Rope, + new_deleted: Rope, +} + +impl<'a> RopeBuilder<'a> { + fn new(old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>) -> Self { + Self { + old_visible_cursor, + old_deleted_cursor, + new_visible: Rope::new(), + new_deleted: Rope::new(), + } + } + + fn append(&mut self, len: FragmentTextSummary) { + self.push(len.visible, true, true); + self.push(len.deleted, false, false); + } + + fn push_fragment(&mut self, fragment: &Fragment, was_visible: bool) { + debug_assert!(fragment.len > 0); + self.push(fragment.len, was_visible, fragment.visible) + } + + fn push(&mut self, len: usize, was_visible: bool, is_visible: bool) { + let text = if was_visible { + self.old_visible_cursor + .slice(self.old_visible_cursor.offset() + len) + } else { + self.old_deleted_cursor + .slice(self.old_deleted_cursor.offset() + len) + }; + if is_visible { + self.new_visible.append(text); + } else { + self.new_deleted.append(text); + } + } + + fn push_str(&mut self, text: &str) { + self.new_visible.push(text); + } + + fn finish(mut self) -> (Rope, Rope) { + self.new_visible.append(self.old_visible_cursor.suffix()); + self.new_deleted.append(self.old_deleted_cursor.suffix()); + (self.new_visible, self.new_deleted) + } +} + +impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Edits<'a, D, F> { + type Item = (Edit, Range); + + fn next(&mut self) -> Option { + let mut pending_edit: Option = None; + let cursor = self.fragments_cursor.as_mut()?; + + while let Some(fragment) = cursor.item() { + if fragment.id < *self.range.start.0 { + cursor.next(&None); + continue; + } else if fragment.id > *self.range.end.0 { + break; + } + + if cursor.start().visible > self.visible_cursor.offset() { + let summary = self.visible_cursor.summary(cursor.start().visible); + self.old_end.add_assign(&summary); + self.new_end.add_assign(&summary); + } + + if pending_edit + .as_ref() + .map_or(false, |(change, _)| change.new.end < self.new_end) + { + break; + } + + let start_anchor = Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset, + bias: Bias::Right, + buffer_id: Some(self.buffer_id), + }; + let end_anchor = Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset + fragment.len, + bias: Bias::Left, + buffer_id: Some(self.buffer_id), + }; + + if !fragment.was_visible(self.since, self.undos) && fragment.visible { + let mut visible_end = cursor.end(&None).visible; + if fragment.id == *self.range.end.0 { + visible_end = cmp::min( + visible_end, + cursor.start().visible + (self.range.end.1 - fragment.insertion_offset), + ); + } + + let fragment_summary = self.visible_cursor.summary(visible_end); + let mut new_end = self.new_end.clone(); + new_end.add_assign(&fragment_summary); + if let Some((edit, range)) = pending_edit.as_mut() { + edit.new.end = new_end.clone(); + range.end = end_anchor; + } else { + pending_edit = Some(( + Edit { + old: self.old_end.clone()..self.old_end.clone(), + new: self.new_end.clone()..new_end.clone(), + }, + start_anchor..end_anchor, + )); + } + + self.new_end = new_end; + } else if fragment.was_visible(self.since, self.undos) && !fragment.visible { + let mut deleted_end = cursor.end(&None).deleted; + if fragment.id == *self.range.end.0 { + deleted_end = cmp::min( + deleted_end, + cursor.start().deleted + (self.range.end.1 - fragment.insertion_offset), + ); + } + + if cursor.start().deleted > self.deleted_cursor.offset() { + self.deleted_cursor.seek_forward(cursor.start().deleted); + } + let fragment_summary = self.deleted_cursor.summary(deleted_end); + let mut old_end = self.old_end.clone(); + old_end.add_assign(&fragment_summary); + if let Some((edit, range)) = pending_edit.as_mut() { + edit.old.end = old_end.clone(); + range.end = end_anchor; + } else { + pending_edit = Some(( + Edit { + old: self.old_end.clone()..old_end.clone(), + new: self.new_end.clone()..self.new_end.clone(), + }, + start_anchor..end_anchor, + )); + } + + self.old_end = old_end; + } + + cursor.next(&None); + } + + pending_edit + } +} + +impl Fragment { + fn insertion_slice(&self) -> InsertionSlice { + InsertionSlice { + insertion_id: self.timestamp, + range: self.insertion_offset..self.insertion_offset + self.len, + } + } + + fn is_visible(&self, undos: &UndoMap) -> bool { + !undos.is_undone(self.timestamp) && self.deletions.iter().all(|d| undos.is_undone(*d)) + } + + fn was_visible(&self, version: &clock::Global, undos: &UndoMap) -> bool { + (version.observed(self.timestamp) && !undos.was_undone(self.timestamp, version)) + && self + .deletions + .iter() + .all(|d| !version.observed(*d) || undos.was_undone(*d, version)) + } +} + +impl sum_tree::Item for Fragment { + type Summary = FragmentSummary; + + fn summary(&self) -> Self::Summary { + let mut max_version = clock::Global::new(); + max_version.observe(self.timestamp); + for deletion in &self.deletions { + max_version.observe(*deletion); + } + max_version.join(&self.max_undos); + + let mut min_insertion_version = clock::Global::new(); + min_insertion_version.observe(self.timestamp); + let max_insertion_version = min_insertion_version.clone(); + if self.visible { + FragmentSummary { + max_id: self.id.clone(), + text: FragmentTextSummary { + visible: self.len, + deleted: 0, + }, + max_version, + min_insertion_version, + max_insertion_version, + } + } else { + FragmentSummary { + max_id: self.id.clone(), + text: FragmentTextSummary { + visible: 0, + deleted: self.len, + }, + max_version, + min_insertion_version, + max_insertion_version, + } + } + } +} + +impl sum_tree::Summary for FragmentSummary { + type Context = Option; + + fn add_summary(&mut self, other: &Self, _: &Self::Context) { + self.max_id.assign(&other.max_id); + self.text.visible += &other.text.visible; + self.text.deleted += &other.text.deleted; + self.max_version.join(&other.max_version); + self.min_insertion_version + .meet(&other.min_insertion_version); + self.max_insertion_version + .join(&other.max_insertion_version); + } +} + +impl Default for FragmentSummary { + fn default() -> Self { + FragmentSummary { + max_id: Locator::min(), + text: FragmentTextSummary::default(), + max_version: clock::Global::new(), + min_insertion_version: clock::Global::new(), + max_insertion_version: clock::Global::new(), + } + } +} + +impl sum_tree::Item for InsertionFragment { + type Summary = InsertionFragmentKey; + + fn summary(&self) -> Self::Summary { + InsertionFragmentKey { + timestamp: self.timestamp, + split_offset: self.split_offset, + } + } +} + +impl sum_tree::KeyedItem for InsertionFragment { + type Key = InsertionFragmentKey; + + fn key(&self) -> Self::Key { + sum_tree::Item::summary(self) + } +} + +impl InsertionFragment { + fn new(fragment: &Fragment) -> Self { + Self { + timestamp: fragment.timestamp, + split_offset: fragment.insertion_offset, + fragment_id: fragment.id.clone(), + } + } + + fn insert_new(fragment: &Fragment) -> sum_tree::Edit { + sum_tree::Edit::Insert(Self::new(fragment)) + } +} + +impl sum_tree::Summary for InsertionFragmentKey { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + *self = *summary; + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FullOffset(pub usize); + +impl ops::AddAssign for FullOffset { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +impl ops::Add for FullOffset { + type Output = Self; + + fn add(mut self, rhs: usize) -> Self::Output { + self += rhs; + self + } +} + +impl ops::Sub for FullOffset { + type Output = usize; + + fn sub(self, rhs: Self) -> Self::Output { + self.0 - rhs.0 + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for usize { + fn add_summary(&mut self, summary: &FragmentSummary, _: &Option) { + *self += summary.text.visible; + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FullOffset { + fn add_summary(&mut self, summary: &FragmentSummary, _: &Option) { + self.0 += summary.text.visible + summary.text.deleted; + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Option<&'a Locator> { + fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { + *self = Some(&summary.max_id); + } +} + +impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, FragmentTextSummary> for usize { + fn cmp( + &self, + cursor_location: &FragmentTextSummary, + _: &Option, + ) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.visible) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum VersionedFullOffset { + Offset(FullOffset), + Invalid, +} + +impl VersionedFullOffset { + fn full_offset(&self) -> FullOffset { + if let Self::Offset(position) = self { + *position + } else { + panic!("invalid version") + } + } +} + +impl Default for VersionedFullOffset { + fn default() -> Self { + Self::Offset(Default::default()) + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for VersionedFullOffset { + fn add_summary(&mut self, summary: &'a FragmentSummary, cx: &Option) { + if let Self::Offset(offset) = self { + let version = cx.as_ref().unwrap(); + if version.observed_all(&summary.max_insertion_version) { + *offset += summary.text.visible + summary.text.deleted; + } else if version.observed_any(&summary.min_insertion_version) { + *self = Self::Invalid; + } + } + } +} + +impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, Self> for VersionedFullOffset { + fn cmp(&self, cursor_position: &Self, _: &Option) -> cmp::Ordering { + match (self, cursor_position) { + (Self::Offset(a), Self::Offset(b)) => Ord::cmp(a, b), + (Self::Offset(_), Self::Invalid) => cmp::Ordering::Less, + (Self::Invalid, _) => unreachable!(), + } + } +} + +impl Operation { + fn replica_id(&self) -> ReplicaId { + operation_queue::Operation::lamport_timestamp(self).replica_id + } + + pub fn timestamp(&self) -> clock::Lamport { + match self { + Operation::Edit(edit) => edit.timestamp, + Operation::Undo(undo) => undo.timestamp, + } + } + + pub fn as_edit(&self) -> Option<&EditOperation> { + match self { + Operation::Edit(edit) => Some(edit), + _ => None, + } + } + + pub fn is_edit(&self) -> bool { + matches!(self, Operation::Edit { .. }) + } +} + +impl operation_queue::Operation for Operation { + fn lamport_timestamp(&self) -> clock::Lamport { + match self { + Operation::Edit(edit) => edit.timestamp, + Operation::Undo(undo) => undo.timestamp, + } + } +} + +pub trait ToOffset { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize; +} + +impl ToOffset for Point { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_to_offset(*self) + } +} + +impl ToOffset for usize { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + assert!( + *self <= snapshot.len(), + "offset {} is out of range, max allowed is {}", + self, + snapshot.len() + ); + *self + } +} + +impl ToOffset for Anchor { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.summary_for_anchor(self) + } +} + +impl<'a, T: ToOffset> ToOffset for &'a T { + fn to_offset(&self, content: &BufferSnapshot) -> usize { + (*self).to_offset(content) + } +} + +impl ToOffset for PointUtf16 { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_utf16_to_offset(*self) + } +} + +impl ToOffset for Unclipped { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.unclipped_point_utf16_to_offset(*self) + } +} + +pub trait ToPoint { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point; +} + +impl ToPoint for Anchor { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.summary_for_anchor(self) + } +} + +impl ToPoint for usize { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.offset_to_point(*self) + } +} + +impl ToPoint for Point { + fn to_point(&self, _: &BufferSnapshot) -> Point { + *self + } +} + +impl ToPoint for Unclipped { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.unclipped_point_utf16_to_point(*self) + } +} + +pub trait ToPointUtf16 { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16; +} + +impl ToPointUtf16 for Anchor { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.summary_for_anchor(self) + } +} + +impl ToPointUtf16 for usize { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.offset_to_point_utf16(*self) + } +} + +impl ToPointUtf16 for PointUtf16 { + fn to_point_utf16(&self, _: &BufferSnapshot) -> PointUtf16 { + *self + } +} + +impl ToPointUtf16 for Point { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.point_to_point_utf16(*self) + } +} + +pub trait ToOffsetUtf16 { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16; +} + +impl ToOffsetUtf16 for Anchor { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + snapshot.summary_for_anchor(self) + } +} + +impl ToOffsetUtf16 for usize { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + snapshot.offset_to_offset_utf16(*self) + } +} + +impl ToOffsetUtf16 for OffsetUtf16 { + fn to_offset_utf16(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 { + *self + } +} + +pub trait FromAnchor { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; +} + +impl FromAnchor for Point { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +impl FromAnchor for PointUtf16 { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +impl FromAnchor for usize { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LineEnding { + Unix, + Windows, +} + +impl Default for LineEnding { + fn default() -> Self { + #[cfg(unix)] + return Self::Unix; + + #[cfg(not(unix))] + return Self::CRLF; + } +} + +impl LineEnding { + pub fn as_str(&self) -> &'static str { + match self { + LineEnding::Unix => "\n", + LineEnding::Windows => "\r\n", + } + } + + pub fn detect(text: &str) -> Self { + let mut max_ix = cmp::min(text.len(), 1000); + while !text.is_char_boundary(max_ix) { + max_ix -= 1; + } + + if let Some(ix) = text[..max_ix].find(&['\n']) { + if ix > 0 && text.as_bytes()[ix - 1] == b'\r' { + Self::Windows + } else { + Self::Unix + } + } else { + Self::default() + } + } + + pub fn normalize(text: &mut String) { + if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") { + *text = replaced; + } + } + + pub fn normalize_arc(text: Arc) -> Arc { + if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") { + replaced.into() + } else { + text + } + } +} diff --git a/crates/text2/src/undo_map.rs b/crates/text2/src/undo_map.rs new file mode 100644 index 0000000000..f95809c02e --- /dev/null +++ b/crates/text2/src/undo_map.rs @@ -0,0 +1,112 @@ +use crate::UndoOperation; +use std::cmp; +use sum_tree::{Bias, SumTree}; + +#[derive(Copy, Clone, Debug)] +struct UndoMapEntry { + key: UndoMapKey, + undo_count: u32, +} + +impl sum_tree::Item for UndoMapEntry { + type Summary = UndoMapKey; + + fn summary(&self) -> Self::Summary { + self.key + } +} + +impl sum_tree::KeyedItem for UndoMapEntry { + type Key = UndoMapKey; + + fn key(&self) -> Self::Key { + self.key + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct UndoMapKey { + edit_id: clock::Lamport, + undo_id: clock::Lamport, +} + +impl sum_tree::Summary for UndoMapKey { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + *self = cmp::max(*self, *summary); + } +} + +#[derive(Clone, Default)] +pub struct UndoMap(SumTree); + +impl UndoMap { + pub fn insert(&mut self, undo: &UndoOperation) { + let edits = undo + .counts + .iter() + .map(|(edit_id, count)| { + sum_tree::Edit::Insert(UndoMapEntry { + key: UndoMapKey { + edit_id: *edit_id, + undo_id: undo.timestamp, + }, + undo_count: *count, + }) + }) + .collect::>(); + self.0.edit(edits, &()); + } + + pub fn is_undone(&self, edit_id: clock::Lamport) -> bool { + self.undo_count(edit_id) % 2 == 1 + } + + pub fn was_undone(&self, edit_id: clock::Lamport, version: &clock::Global) -> bool { + let mut cursor = self.0.cursor::(); + cursor.seek( + &UndoMapKey { + edit_id, + undo_id: Default::default(), + }, + Bias::Left, + &(), + ); + + let mut undo_count = 0; + for entry in cursor { + if entry.key.edit_id != edit_id { + break; + } + + if version.observed(entry.key.undo_id) { + undo_count = cmp::max(undo_count, entry.undo_count); + } + } + + undo_count % 2 == 1 + } + + pub fn undo_count(&self, edit_id: clock::Lamport) -> u32 { + let mut cursor = self.0.cursor::(); + cursor.seek( + &UndoMapKey { + edit_id, + undo_id: Default::default(), + }, + Bias::Left, + &(), + ); + + let mut undo_count = 0; + for entry in cursor { + if entry.key.edit_id != edit_id { + break; + } + + undo_count = cmp::max(undo_count, entry.undo_count); + } + undo_count + } +} diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index b75e2d881f..a54e68b51a 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -63,7 +63,7 @@ settings2 = { path = "../settings2" } feature_flags2 = { path = "../feature_flags2" } sum_tree = { path = "../sum_tree" } shellexpand = "2.1.0" -text = { path = "../text" } +text2 = { path = "../text2" } # terminal_view = { path = "../terminal_view" } theme2 = { path = "../theme2" } # theme_selector = { path = "../theme_selector" } @@ -152,7 +152,7 @@ language2 = { path = "../language2", features = ["test-support"] } project2 = { path = "../project2", features = ["test-support"] } # rpc = { path = "../rpc", features = ["test-support"] } # settings = { path = "../settings", features = ["test-support"] } -# text = { path = "../text", features = ["test-support"] } +text2 = { path = "../text2", features = ["test-support"] } # util = { path = "../util", features = ["test-support"] } # workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true From 69aafe9ff6319f2afb8eec3a91630e2394fa9807 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 02:10:50 +0100 Subject: [PATCH 14/15] Adjust `ColorScale` representation (#3204) This PR adjusts the representations of `ColorScale`s to allow us to remove an unsafe `From` impl when converting from the statically-defined representation of the scale. Release Notes: - N/A --- crates/theme2/src/default_colors.rs | 218 ++++++++++++++++------------ crates/theme2/src/scale.rs | 22 ++- 2 files changed, 147 insertions(+), 93 deletions(-) diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 8fb38e9661..4dc30e6736 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -1,9 +1,12 @@ -use gpui2::{hsla, Rgba}; +use std::num::ParseIntError; + +use gpui2::{hsla, Hsla, Rgba}; use crate::{ colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors}, scale::{ColorScaleSet, ColorScales}, syntax::SyntaxTheme, + ColorScale, }; impl Default for SystemColors { @@ -269,31 +272,35 @@ impl ThemeColors { } } -struct DefaultColorScaleSet { +type StaticColorScale = [&'static str; 12]; + +struct StaticColorScaleSet { scale: &'static str, - light: [&'static str; 12], - light_alpha: [&'static str; 12], - dark: [&'static str; 12], - dark_alpha: [&'static str; 12], + light: StaticColorScale, + light_alpha: StaticColorScale, + dark: StaticColorScale, + dark_alpha: StaticColorScale, } -impl From for ColorScaleSet { - fn from(default: DefaultColorScaleSet) -> Self { - Self::new( - default.scale, - default - .light - .map(|color| Rgba::try_from(color).unwrap().into()), - default - .light_alpha - .map(|color| Rgba::try_from(color).unwrap().into()), - default - .dark - .map(|color| Rgba::try_from(color).unwrap().into()), - default - .dark_alpha - .map(|color| Rgba::try_from(color).unwrap().into()), - ) +impl TryFrom for ColorScaleSet { + type Error = ParseIntError; + + fn try_from(value: StaticColorScaleSet) -> Result { + fn to_color_scale(scale: StaticColorScale) -> Result { + scale + .into_iter() + .map(|color| Rgba::try_from(color).map(Hsla::from)) + .collect::, _>>() + .map(ColorScale::from_iter) + } + + Ok(Self::new( + value.scale, + to_color_scale(value.light)?, + to_color_scale(value.light_alpha)?, + to_color_scale(value.dark)?, + to_color_scale(value.dark_alpha)?, + )) } } @@ -336,7 +343,7 @@ pub fn default_color_scales() -> ColorScales { } fn gray() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Gray", light: [ "#fcfcfcff", @@ -395,11 +402,12 @@ fn gray() -> ColorScaleSet { "#ffffffed", ], } - .into() + .try_into() + .unwrap() } fn mauve() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Mauve", light: [ "#fdfcfdff", @@ -458,11 +466,12 @@ fn mauve() -> ColorScaleSet { "#fdfdffef", ], } - .into() + .try_into() + .unwrap() } fn slate() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Slate", light: [ "#fcfcfdff", @@ -521,11 +530,12 @@ fn slate() -> ColorScaleSet { "#fcfdffef", ], } - .into() + .try_into() + .unwrap() } fn sage() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Sage", light: [ "#fbfdfcff", @@ -584,11 +594,12 @@ fn sage() -> ColorScaleSet { "#fdfffeed", ], } - .into() + .try_into() + .unwrap() } fn olive() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Olive", light: [ "#fcfdfcff", @@ -647,11 +658,12 @@ fn olive() -> ColorScaleSet { "#fdfffded", ], } - .into() + .try_into() + .unwrap() } fn sand() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Sand", light: [ "#fdfdfcff", @@ -710,11 +722,12 @@ fn sand() -> ColorScaleSet { "#fffffded", ], } - .into() + .try_into() + .unwrap() } fn gold() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Gold", light: [ "#fdfdfcff", @@ -773,11 +786,12 @@ fn gold() -> ColorScaleSet { "#fef7ede7", ], } - .into() + .try_into() + .unwrap() } fn bronze() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Bronze", light: [ "#fdfcfcff", @@ -836,11 +850,12 @@ fn bronze() -> ColorScaleSet { "#fff1e9ec", ], } - .into() + .try_into() + .unwrap() } fn brown() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Brown", light: [ "#fefdfcff", @@ -899,11 +914,12 @@ fn brown() -> ColorScaleSet { "#feecd4f2", ], } - .into() + .try_into() + .unwrap() } fn yellow() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Yellow", light: [ "#fdfdf9ff", @@ -962,11 +978,12 @@ fn yellow() -> ColorScaleSet { "#fef6baf6", ], } - .into() + .try_into() + .unwrap() } fn amber() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Amber", light: [ "#fefdfbff", @@ -1025,11 +1042,12 @@ fn amber() -> ColorScaleSet { "#ffe7b3ff", ], } - .into() + .try_into() + .unwrap() } fn orange() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Orange", light: [ "#fefcfbff", @@ -1088,11 +1106,12 @@ fn orange() -> ColorScaleSet { "#ffe0c2ff", ], } - .into() + .try_into() + .unwrap() } fn tomato() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Tomato", light: [ "#fffcfcff", @@ -1151,11 +1170,12 @@ fn tomato() -> ColorScaleSet { "#ffd6cefb", ], } - .into() + .try_into() + .unwrap() } fn red() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Red", light: [ "#fffcfcff", @@ -1214,11 +1234,12 @@ fn red() -> ColorScaleSet { "#ffd1d9ff", ], } - .into() + .try_into() + .unwrap() } fn ruby() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Ruby", light: [ "#fffcfdff", @@ -1277,11 +1298,12 @@ fn ruby() -> ColorScaleSet { "#ffd3e2fe", ], } - .into() + .try_into() + .unwrap() } fn crimson() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Crimson", light: [ "#fffcfdff", @@ -1340,11 +1362,12 @@ fn crimson() -> ColorScaleSet { "#ffd5eafd", ], } - .into() + .try_into() + .unwrap() } fn pink() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Pink", light: [ "#fffcfeff", @@ -1403,11 +1426,12 @@ fn pink() -> ColorScaleSet { "#ffd3ecfd", ], } - .into() + .try_into() + .unwrap() } fn plum() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Plum", light: [ "#fefcffff", @@ -1466,11 +1490,12 @@ fn plum() -> ColorScaleSet { "#feddfef4", ], } - .into() + .try_into() + .unwrap() } fn purple() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Purple", light: [ "#fefcfeff", @@ -1529,11 +1554,12 @@ fn purple() -> ColorScaleSet { "#f1ddfffa", ], } - .into() + .try_into() + .unwrap() } fn violet() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Violet", light: [ "#fdfcfeff", @@ -1592,11 +1618,12 @@ fn violet() -> ColorScaleSet { "#e3defffe", ], } - .into() + .try_into() + .unwrap() } fn iris() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Iris", light: [ "#fdfdffff", @@ -1655,11 +1682,12 @@ fn iris() -> ColorScaleSet { "#e1e0fffe", ], } - .into() + .try_into() + .unwrap() } fn indigo() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Indigo", light: [ "#fdfdfeff", @@ -1718,11 +1746,12 @@ fn indigo() -> ColorScaleSet { "#d6e1ffff", ], } - .into() + .try_into() + .unwrap() } fn blue() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Blue", light: [ "#fbfdffff", @@ -1781,11 +1810,12 @@ fn blue() -> ColorScaleSet { "#c2e6ffff", ], } - .into() + .try_into() + .unwrap() } fn cyan() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Cyan", light: [ "#fafdfeff", @@ -1844,11 +1874,12 @@ fn cyan() -> ColorScaleSet { "#bbf3fef7", ], } - .into() + .try_into() + .unwrap() } fn teal() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Teal", light: [ "#fafefdff", @@ -1907,11 +1938,12 @@ fn teal() -> ColorScaleSet { "#b8ffebef", ], } - .into() + .try_into() + .unwrap() } fn jade() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Jade", light: [ "#fbfefdff", @@ -1970,11 +2002,12 @@ fn jade() -> ColorScaleSet { "#b8ffe1ef", ], } - .into() + .try_into() + .unwrap() } fn green() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Green", light: [ "#fbfefcff", @@ -2033,11 +2066,12 @@ fn green() -> ColorScaleSet { "#bbffd7f0", ], } - .into() + .try_into() + .unwrap() } fn grass() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Grass", light: [ "#fbfefbff", @@ -2096,11 +2130,12 @@ fn grass() -> ColorScaleSet { "#ceffceef", ], } - .into() + .try_into() + .unwrap() } fn lime() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Lime", light: [ "#fcfdfaff", @@ -2159,11 +2194,12 @@ fn lime() -> ColorScaleSet { "#e9febff7", ], } - .into() + .try_into() + .unwrap() } fn mint() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Mint", light: [ "#f9fefdff", @@ -2222,11 +2258,12 @@ fn mint() -> ColorScaleSet { "#cbfee9f5", ], } - .into() + .try_into() + .unwrap() } fn sky() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Sky", light: [ "#f9feffff", @@ -2285,11 +2322,12 @@ fn sky() -> ColorScaleSet { "#c2f3ffff", ], } - .into() + .try_into() + .unwrap() } fn black() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "Black", light: [ "#0000000d", @@ -2348,11 +2386,12 @@ fn black() -> ColorScaleSet { "#000000f2", ], } - .into() + .try_into() + .unwrap() } fn white() -> ColorScaleSet { - DefaultColorScaleSet { + StaticColorScaleSet { scale: "White", light: [ "#ffffff0d", @@ -2411,5 +2450,6 @@ fn white() -> ColorScaleSet { "#fffffff2", ], } - .into() + .try_into() + .unwrap() } diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index 21c8592d81..a22036df8d 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -2,7 +2,24 @@ use gpui2::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; -pub type ColorScale = [Hsla; 12]; +/// A one-based step in a [`ColorScale`]. +pub type ColorScaleStep = usize; + +pub struct ColorScale(Vec); + +impl FromIterator for ColorScale { + fn from_iter>(iter: T) -> Self { + Self(Vec::from_iter(iter)) + } +} + +impl std::ops::Index for ColorScale { + type Output = Hsla; + + fn index(&self, index: ColorScaleStep) -> &Self::Output { + &self.0[index - 1] + } +} pub struct ColorScales { pub gray: ColorScaleSet, @@ -85,9 +102,6 @@ impl IntoIterator for ColorScales { } } -/// A one-based step in a [`ColorScale`]. -pub type ColorScaleStep = usize; - pub struct ColorScaleSet { name: SharedString, light: ColorScale, From 72d060108d23de34db50efeef51047d47989b42a Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 2 Nov 2023 02:52:42 +0100 Subject: [PATCH 15/15] Make indexing into `ColorScale`s safe (#3205) This PR makes indexing into `ColorScale`s safe by constraining the `ColorScaleStep`s to a set of known values. Release Notes: - N/A --- crates/storybook2/src/stories/colors.rs | 13 +- crates/theme2/src/default_colors.rs | 2 +- crates/theme2/src/scale.rs | 162 +++++++++++++++++++++--- 3 files changed, 155 insertions(+), 22 deletions(-) diff --git a/crates/storybook2/src/stories/colors.rs b/crates/storybook2/src/stories/colors.rs index 0dd56071c8..c1c65d62fa 100644 --- a/crates/storybook2/src/stories/colors.rs +++ b/crates/storybook2/src/stories/colors.rs @@ -1,6 +1,6 @@ use crate::story::Story; use gpui2::{px, Div, Render}; -use theme2::default_color_scales; +use theme2::{default_color_scales, ColorScaleStep}; use ui::prelude::*; pub struct ColorsStory; @@ -30,9 +30,14 @@ impl Render for ColorsStory { .line_height(px(24.)) .child(scale.name().to_string()), ) - .child(div().flex().gap_1().children( - (1..=12).map(|step| div().flex().size_6().bg(scale.step(cx, step))), - )) + .child( + div() + .flex() + .gap_1() + .children(ColorScaleStep::ALL.map(|step| { + div().flex().size_6().bg(scale.step(cx, step)) + })), + ) })), ) } diff --git a/crates/theme2/src/default_colors.rs b/crates/theme2/src/default_colors.rs index 4dc30e6736..335b6801d5 100644 --- a/crates/theme2/src/default_colors.rs +++ b/crates/theme2/src/default_colors.rs @@ -23,7 +23,7 @@ impl Default for SystemColors { impl Default for StatusColors { fn default() -> Self { Self { - conflict: gpui2::black(), + conflict: red().dark().step_11(), created: gpui2::black(), deleted: gpui2::black(), error: gpui2::black(), diff --git a/crates/theme2/src/scale.rs b/crates/theme2/src/scale.rs index a22036df8d..bd28e43db9 100644 --- a/crates/theme2/src/scale.rs +++ b/crates/theme2/src/scale.rs @@ -3,7 +3,62 @@ use gpui2::{AppContext, Hsla, SharedString}; use crate::{ActiveTheme, Appearance}; /// A one-based step in a [`ColorScale`]. -pub type ColorScaleStep = usize; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct ColorScaleStep(usize); + +impl ColorScaleStep { + /// The first step in a [`ColorScale`]. + pub const ONE: Self = Self(1); + + /// The second step in a [`ColorScale`]. + pub const TWO: Self = Self(2); + + /// The third step in a [`ColorScale`]. + pub const THREE: Self = Self(3); + + /// The fourth step in a [`ColorScale`]. + pub const FOUR: Self = Self(4); + + /// The fifth step in a [`ColorScale`]. + pub const FIVE: Self = Self(5); + + /// The sixth step in a [`ColorScale`]. + pub const SIX: Self = Self(6); + + /// The seventh step in a [`ColorScale`]. + pub const SEVEN: Self = Self(7); + + /// The eighth step in a [`ColorScale`]. + pub const EIGHT: Self = Self(8); + + /// The ninth step in a [`ColorScale`]. + pub const NINE: Self = Self(9); + + /// The tenth step in a [`ColorScale`]. + pub const TEN: Self = Self(10); + + /// The eleventh step in a [`ColorScale`]. + pub const ELEVEN: Self = Self(11); + + /// The twelfth step in a [`ColorScale`]. + pub const TWELVE: Self = Self(12); + + /// All of the steps in a [`ColorScale`]. + pub const ALL: [ColorScaleStep; 12] = [ + Self::ONE, + Self::TWO, + Self::THREE, + Self::FOUR, + Self::FIVE, + Self::SIX, + Self::SEVEN, + Self::EIGHT, + Self::NINE, + Self::TEN, + Self::ELEVEN, + Self::TWELVE, + ]; +} pub struct ColorScale(Vec); @@ -13,11 +68,84 @@ impl FromIterator for ColorScale { } } -impl std::ops::Index for ColorScale { - type Output = Hsla; +impl ColorScale { + /// Returns the specified step in the [`ColorScale`]. + #[inline] + pub fn step(&self, step: ColorScaleStep) -> Hsla { + // Steps are one-based, so we need convert to the zero-based vec index. + self.0[step.0 - 1] + } - fn index(&self, index: ColorScaleStep) -> &Self::Output { - &self.0[index - 1] + /// Returns the first step in the [`ColorScale`]. + #[inline] + pub fn step_1(&self) -> Hsla { + self.step(ColorScaleStep::ONE) + } + + /// Returns the second step in the [`ColorScale`]. + #[inline] + pub fn step_2(&self) -> Hsla { + self.step(ColorScaleStep::TWO) + } + + /// Returns the third step in the [`ColorScale`]. + #[inline] + pub fn step_3(&self) -> Hsla { + self.step(ColorScaleStep::THREE) + } + + /// Returns the fourth step in the [`ColorScale`]. + #[inline] + pub fn step_4(&self) -> Hsla { + self.step(ColorScaleStep::FOUR) + } + + /// Returns the fifth step in the [`ColorScale`]. + #[inline] + pub fn step_5(&self) -> Hsla { + self.step(ColorScaleStep::FIVE) + } + + /// Returns the sixth step in the [`ColorScale`]. + #[inline] + pub fn step_6(&self) -> Hsla { + self.step(ColorScaleStep::SIX) + } + + /// Returns the seventh step in the [`ColorScale`]. + #[inline] + pub fn step_7(&self) -> Hsla { + self.step(ColorScaleStep::SEVEN) + } + + /// Returns the eighth step in the [`ColorScale`]. + #[inline] + pub fn step_8(&self) -> Hsla { + self.step(ColorScaleStep::EIGHT) + } + + /// Returns the ninth step in the [`ColorScale`]. + #[inline] + pub fn step_9(&self) -> Hsla { + self.step(ColorScaleStep::NINE) + } + + /// Returns the tenth step in the [`ColorScale`]. + #[inline] + pub fn step_10(&self) -> Hsla { + self.step(ColorScaleStep::TEN) + } + + /// Returns the eleventh step in the [`ColorScale`]. + #[inline] + pub fn step_11(&self) -> Hsla { + self.step(ColorScaleStep::ELEVEN) + } + + /// Returns the twelfth step in the [`ColorScale`]. + #[inline] + pub fn step_12(&self) -> Hsla { + self.step(ColorScaleStep::TWELVE) } } @@ -131,33 +259,33 @@ impl ColorScaleSet { &self.name } - pub fn light(&self, step: ColorScaleStep) -> Hsla { - self.light[step - 1] + pub fn light(&self) -> &ColorScale { + &self.light } - pub fn light_alpha(&self, step: ColorScaleStep) -> Hsla { - self.light_alpha[step - 1] + pub fn light_alpha(&self) -> &ColorScale { + &self.light_alpha } - pub fn dark(&self, step: ColorScaleStep) -> Hsla { - self.dark[step - 1] + pub fn dark(&self) -> &ColorScale { + &self.dark } - pub fn dark_alpha(&self, step: ColorScaleStep) -> Hsla { - self.dark_alpha[step - 1] + pub fn dark_alpha(&self) -> &ColorScale { + &self.dark_alpha } pub fn step(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { match cx.theme().appearance { - Appearance::Light => self.light(step), - Appearance::Dark => self.dark(step), + Appearance::Light => self.light().step(step), + Appearance::Dark => self.dark().step(step), } } pub fn step_alpha(&self, cx: &AppContext, step: ColorScaleStep) -> Hsla { match cx.theme().appearance { - Appearance::Light => self.light_alpha(step), - Appearance::Dark => self.dark_alpha(step), + Appearance::Light => self.light_alpha.step(step), + Appearance::Dark => self.dark_alpha.step(step), } } }