Merge branch 'main' into zed2-workspace

Mikayla created

Change summary

Cargo.lock                                                                         |  101 
Cargo.toml                                                                         |    2 
crates/assistant/src/codegen.rs                                                    |  108 
crates/assistant/src/prompts.rs                                                    |   18 
crates/call2/Cargo.toml                                                            |    6 
crates/call2/src/participant.rs                                                    |   31 
crates/call2/src/room.rs                                                           |  927 
crates/editor/Cargo.toml                                                           |    1 
crates/gpui2/src/subscription.rs                                                   |    2 
crates/gpui2/src/view.rs                                                           |    9 
crates/gpui2/src/window.rs                                                         |    6 
crates/language2/src/language2.rs                                                  |   10 
crates/live_kit_client/LiveKitBridge/Package.resolved                              |    4 
crates/live_kit_client2/.cargo/config.toml                                         |    2 
crates/live_kit_client2/Cargo.toml                                                 |   71 
crates/live_kit_client2/LiveKitBridge2/Package.resolved                            |   52 
crates/live_kit_client2/LiveKitBridge2/Package.swift                               |   27 
crates/live_kit_client2/LiveKitBridge2/README.md                                   |    3 
crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.swift |  327 
crates/live_kit_client2/build.rs                                                   |  172 
crates/live_kit_client2/examples/test_app.rs                                       |  178 
crates/live_kit_client2/src/live_kit_client2.rs                                    |   11 
crates/live_kit_client2/src/prod.rs                                                |  947 
crates/live_kit_client2/src/test.rs                                                |  651 
crates/multi_buffer2/Cargo.toml                                                    |   78 
crates/multi_buffer2/src/anchor.rs                                                 |  138 
crates/multi_buffer2/src/multi_buffer2.rs                                          | 5393 
crates/prettier/src/prettier_server.js                                             |    9 
crates/project/src/project.rs                                                      |  143 
crates/project/src/worktree.rs                                                     |   12 
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                                                |   10 
crates/theme2/Cargo.toml                                                           |   12 
crates/theme2/src/colors.rs                                                        |  144 
crates/theme2/src/default_colors.rs                                                |  511 
crates/theme2/src/default_theme.rs                                                 |   58 
crates/theme2/src/registry.rs                                                      |   63 
crates/theme2/src/scale.rs                                                         |  193 
crates/theme2/src/settings.rs                                                      |   14 
crates/theme2/src/syntax.rs                                                        |   37 
crates/theme2/src/theme2.rs                                                        |  163 
crates/theme2/src/themes/andromeda.rs                                              |  130 
crates/theme2/src/themes/atelier_cave_dark.rs                                      |  136 
crates/theme2/src/themes/atelier_cave_light.rs                                     |  136 
crates/theme2/src/themes/atelier_dune_dark.rs                                      |  136 
crates/theme2/src/themes/atelier_dune_light.rs                                     |  136 
crates/theme2/src/themes/atelier_estuary_dark.rs                                   |  136 
crates/theme2/src/themes/atelier_estuary_light.rs                                  |  136 
crates/theme2/src/themes/atelier_forest_dark.rs                                    |  136 
crates/theme2/src/themes/atelier_forest_light.rs                                   |  136 
crates/theme2/src/themes/atelier_heath_dark.rs                                     |  136 
crates/theme2/src/themes/atelier_heath_light.rs                                    |  136 
crates/theme2/src/themes/atelier_lakeside_dark.rs                                  |  136 
crates/theme2/src/themes/atelier_lakeside_light.rs                                 |  136 
crates/theme2/src/themes/atelier_plateau_dark.rs                                   |  136 
crates/theme2/src/themes/atelier_plateau_light.rs                                  |  136 
crates/theme2/src/themes/atelier_savanna_dark.rs                                   |  136 
crates/theme2/src/themes/atelier_savanna_light.rs                                  |  136 
crates/theme2/src/themes/atelier_seaside_dark.rs                                   |  136 
crates/theme2/src/themes/atelier_seaside_light.rs                                  |  136 
crates/theme2/src/themes/atelier_sulphurpool_dark.rs                               |  136 
crates/theme2/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 
crates/theme2/src/themes/gruvbox_light_hard.rs                                     |  131 
crates/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 
crates/ui2/Cargo.toml                                                              |    1 
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                                           |   20 
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 
crates/ui2/src/components/notification_toast.rs                                    |    7 
crates/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                                             |   50 
crates/ui2/src/elements/avatar.rs                                                  |    5 
crates/ui2/src/elements/button.rs                                                  |   28 
crates/ui2/src/elements/details.rs                                                 |    7 
crates/ui2/src/elements/icon.rs                                                    |   19 
crates/ui2/src/elements/input.rs                                                   |   16 
crates/ui2/src/elements/label.rs                                                   |   20 
crates/ui2/src/elements/player.rs                                                  |    8 
crates/ui2/src/elements/tool_divider.rs                                            |    4 
crates/ui2/src/prelude.rs                                                          |   27 
crates/ui2/src/settings.rs                                                         |    2 
crates/ui2/src/static_data.rs                                                      |   96 
crates/ui2/src/story.rs                                                            |   12 
crates/zed/Cargo.toml                                                              |    2 
126 files changed, 9,904 insertions(+), 7,207 deletions(-)

Detailed changes

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"
@@ -5035,6 +5068,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"
@@ -8759,6 +8839,7 @@ dependencies = [
  "gpui2",
  "indexmap 1.9.3",
  "parking_lot 0.11.2",
+ "refineable",
  "schemars",
  "serde",
  "serde_derive",
@@ -8768,21 +8849,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"
@@ -9600,6 +9666,7 @@ dependencies = [
  "itertools 0.11.0",
  "rand 0.8.5",
  "serde",
+ "settings2",
  "smallvec",
  "strum",
  "theme2",
@@ -10825,7 +10892,7 @@ dependencies = [
 
 [[package]]
 name = "zed"
-version = "0.111.0"
+version = "0.112.0"
 dependencies = [
  "activity_indicator",
  "ai",

Cargo.toml 🔗

@@ -61,6 +61,7 @@ members = [
     "crates/menu",
     "crates/menu2",
     "crates/multi_buffer",
+    "crates/multi_buffer2",
     "crates/node_runtime",
     "crates/notifications",
     "crates/outline",
@@ -92,7 +93,6 @@ members = [
     "crates/text",
     "crates/theme",
     "crates/theme2",
-    "crates/theme_converter",
     "crates/theme_selector",
     "crates/ui2",
     "crates/util",

crates/assistant/src/codegen.rs 🔗

@@ -118,7 +118,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());
 
@@ -279,12 +279,13 @@ impl Codegen {
     }
 }
 
-fn strip_markdown_codeblock(
+fn strip_invalid_spans_from_codeblock(
     stream: impl Stream<Item = Result<String>>,
 ) -> impl Stream<Item = Result<String>> {
     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,
@@ -292,11 +293,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;
@@ -306,16 +327,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') {
@@ -328,6 +359,7 @@ fn strip_markdown_codeblock(
         } else {
             Some(Ok(buffer.clone()))
         };
+
         buffer = remainder;
         future::ready(result)
     })
@@ -558,50 +590,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::<String>()
                 .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::<String>()
                 .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::<String>()
                 .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::<String>()
                 .await,
             "Lorem ipsum dolor"
         );
         assert_eq!(
-            strip_markdown_codeblock(chunks("```html\n```js\nLorem ipsum dolor\n```\n```", 2))
+            strip_invalid_spans_from_codeblock(chunks(
+                "```html\n```js\nLorem ipsum dolor\n```\n```",
+                2
+            ))
+            .map(|chunk| chunk.unwrap())
+            .collect::<String>()
+            .await,
+            "```js\nLorem ipsum dolor\n```"
+        );
+        assert_eq!(
+            strip_invalid_spans_from_codeblock(chunks("``\nLorem ipsum dolor\n```", 2))
                 .map(|chunk| chunk.unwrap())
                 .collect::<String>()
                 .await,
-            "```js\nLorem ipsum dolor\n```"
+            "``\nLorem ipsum dolor\n```"
         );
         assert_eq!(
-            strip_markdown_codeblock(chunks("``\nLorem ipsum dolor\n```", 2))
+            strip_invalid_spans_from_codeblock(chunks("<|S|Lorem ipsum|E|>", 2))
                 .map(|chunk| chunk.unwrap())
                 .collect::<String>()
                 .await,
-            "``\nLorem ipsum dolor\n```"
+            "Lorem ipsum"
         );
 
+        assert_eq!(
+            strip_invalid_spans_from_codeblock(chunks("<|S|>Lorem ipsum", 2))
+                .map(|chunk| chunk.unwrap())
+                .collect::<String>()
+                .await,
+            "Lorem ipsum"
+        );
+
+        assert_eq!(
+            strip_invalid_spans_from_codeblock(chunks("```\n<|S|>Lorem ipsum\n```", 2))
+                .map(|chunk| chunk.unwrap())
+                .collect::<String>()
+                .await,
+            "Lorem ipsum"
+        );
+        assert_eq!(
+            strip_invalid_spans_from_codeblock(chunks("```\n<|S|Lorem ipsum|E|>\n```", 2))
+                .map(|chunk| chunk.unwrap())
+                .collect::<String>()
+                .await,
+            "Lorem ipsum"
+        );
         fn chunks(text: &str, size: usize) -> impl Stream<Item = Result<String>> {
             stream::iter(
                 text.chars()

crates/assistant/src/prompts.rs 🔗

@@ -80,12 +80,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range<impl ToOffset>) -> 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;
@@ -107,12 +107,12 @@ fn summarize(buffer: &BufferSnapshot, selected_range: Range<impl ToOffset>) -> 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;
     }
@@ -260,7 +260,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,
                 }
 
@@ -286,7 +286,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 }
                     }
@@ -307,7 +307,7 @@ pub(crate) mod tests {
                 }
 
                 impl X {
-                <|START|>
+                <|S|>
                     fn new() -> Self {}
 
                     pub fn a(&self, param: bool) -> usize {}
@@ -333,7 +333,7 @@ pub(crate) mod tests {
 
                     pub fn b(&self) -> usize {}
                 }
-                <|START|>"}
+                <|S|>"}
         );
 
         // Ensure nested functions get collapsed properly.
@@ -369,7 +369,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,
                 }

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"] }

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_client::Frame;
+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<live_kit_client::Sid, Arc<RemoteVideoTrack>>,
-    // pub audio_tracks: HashMap<live_kit_client::Sid, Arc<RemoteAudioTrack>>,
-}
-
-#[derive(Clone)]
-pub struct RemoteVideoTrack {
-    pub(crate) live_kit_track: Arc<live_kit_client::RemoteVideoTrack>,
-}
-
-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<Frame> {
-        self.live_kit_track.frames()
-    }
+    pub video_tracks: HashMap<live_kit_client2::Sid, Arc<RemoteVideoTrack>>,
+    pub audio_tracks: HashMap<live_kit_client2::Sid, Arc<RemoteAudioTrack>>,
 }

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_client::{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);
 
@@ -59,7 +59,7 @@ pub enum Event {
 pub struct Room {
     id: u64,
     channel_id: Option<u64>,
-    // live_kit: Option<LiveKitRoom>,
+    live_kit: Option<LiveKitRoom>,
     status: RoomStatus,
     shared_projects: HashSet<WeakModel<Project>>,
     joined_projects: HashSet<WeakModel<Project>>,
@@ -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(
@@ -114,125 +113,130 @@ impl Room {
         user_store: Model<UserStore>,
         cx: &mut ModelContext<Self>,
     ) -> 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;
-        //             };
-
-        //             if status == live_kit_client::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;
-        //             };
-
-        //             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;
-        //             };
-
-        //             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?;
-
-        //         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);
-
-        //     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()
-        // });
-
-        // Audio::play_sound(Sound::Joined, cx);
-
-        // 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,
-        // }
+        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_client2::ConnectionState::Disconnected {
+                        this.update(&mut cx, |this, cx| this.leave(cx).log_err())
+                            .ok();
+                        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();
+                    }
+                }
+            });
+
+            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();
+                    }
+                }
+            });
+
+            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?;
+                }
+
+                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
+        };
+
+        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);
+
+        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_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(
@@ -418,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();
     }
@@ -794,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());
-
-                            //     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();
-                            //     }
-                            // }
+                            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, publication) in
+                                    audio_tracks.iter().zip(publications.iter())
+                                {
+                                    this.remote_audio_track_updated(
+                                        RemoteAudioTrackUpdate::Subscribed(
+                                            track.clone(),
+                                            publication.clone(),
+                                        ),
+                                        cx,
+                                    )
+                                    .log_err();
+                                }
+                            }
                         }
                     }
 
@@ -918,7 +922,6 @@ impl Room {
         change: RemoteVideoTrackUpdate,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
-        todo!();
         match change {
             RemoteVideoTrackUpdate::Subscribed(track) => {
                 let user_id = track.publisher_id().parse()?;
@@ -927,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,
                 });
@@ -946,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,
                 });
@@ -976,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();
-
-                // cx.emit(Event::RemoteAudioTracksChanged {
-                //     participant_id: participant.peer_id,
-                // });
+                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,
+                });
             }
             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,
+                });
             }
         }
 
@@ -1215,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<bool> {
-        // 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<Self>) -> Task<Result<()>> {
-        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")));
-        // }
-
-        // 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
-        //     };
-
-        //     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)
-        //             };
-
-        //             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)
-        //                     }
-        //                 }
-        //             }
-        //         })?
-        // })
+        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")));
+        };
+
+        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 (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)
+                            }
+                        }
+                    }
+                })?
+        })
     }
 
     pub fn share_screen(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
-        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")));
-        // }
-
-        // 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
-        //     };
-
-        //     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)
-        //             };
-
-        //             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);
-
-        //                     Ok(())
-        //                 }
-        //                 Err(error) => {
-        //                     if canceled {
-        //                         Ok(())
-        //                     } else {
-        //                         live_kit.screen_track = LocalTrack::None;
-        //                         cx.notify();
-        //                         Err(error)
-        //                     }
-        //                 }
-        //             }
-        //         })?
-        // })
+        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")));
+        };
+
+        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 (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();
+                            }
+
+                            Audio::play_sound(Sound::StartScreenshare, cx);
+
+                            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<Self>) -> Result<Task<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 (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();
-        //         }
-        //     }
-
-        //     Ok(ret_task)
-        // } else {
-        //     Err(anyhow!("LiveKit not started"))
-        // }
+        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;
+
+            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"))
+        }
     }
 
     pub fn toggle_deafen(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
-        todo!()
-        // 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)),
-        //             );
-        //         }
-        //     }
-
-        //     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"))
-        // }
+        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)),
+                    );
+                }
+            }
+
+            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<Self>) -> Result<()> {

crates/editor/Cargo.toml 🔗

@@ -80,6 +80,7 @@ util = { path = "../util", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
 settings = { path = "../settings", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
+multi_buffer = { path = "../multi_buffer", features = ["test-support"] }
 
 ctor.workspace = true
 env_logger.workspace = true

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

crates/gpui2/src/view.rs 🔗

@@ -352,9 +352,12 @@ impl<V: Render> From<View<V>> for AnyView {
             initialize: |view, cx| {
                 cx.with_element_id(view.model.entity_id, |_, cx| {
                     let view = view.clone().downcast::<V>().unwrap();
-                    Box::new(AnyElement::new(
-                        view.update(cx, |view, cx| Render::render(view, cx)),
-                    ))
+                    let element = view.update(cx, |view, cx| {
+                        let mut element = AnyElement::new(view.render(cx));
+                        element.initialize(view, cx);
+                        element
+                    });
+                    Box::new(element)
                 })
             },
             layout: |view, element, cx| {

crates/gpui2/src/window.rs 🔗

@@ -571,6 +571,12 @@ impl<'a> WindowContext<'a> {
         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<Pixels>) {
+        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();

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<AvailableLanguageId, Vec<oneshot::Sender<Result<Arc<Language>>>>>,
     subscription: (watch::Sender<()>, watch::Receiver<()>),
-    theme: Option<Arc<Theme>>,
+    theme: Option<Arc<ThemeVariant>>,
     version: usize,
     reload_count: usize,
 }
@@ -743,11 +743,11 @@ impl LanguageRegistry {
         self.state.read().reload_count
     }
 
-    pub fn set_theme(&self, theme: Arc<Theme>) {
+    pub fn set_theme(&self, theme: Arc<ThemeVariant>) {
         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<Language>) {
         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;

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"
         }
       }
     ]

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

crates/live_kit_client2/LiveKitBridge2/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
+}

crates/live_kit_client2/LiveKitBridge2/Package.swift 🔗

@@ -0,0 +1,27 @@
+// swift-tools-version: 5.5
+
+import PackageDescription
+
+let package = Package(
+    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: "LiveKitBridge2",
+            type: .static,
+            targets: ["LiveKitBridge2"]),
+    ],
+    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: "LiveKitBridge2",
+            dependencies: [.product(name: "LiveKit", package: "client-sdk-swift")]),
+    ]
+)

crates/live_kit_client2/LiveKitBridge2/Sources/LiveKitBridge2/LiveKitBridge2.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<LKRoomDelegate>.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<Room>.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<Room>.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<Room>.fromOpaque(room).takeUnretainedValue()
+    let track = Unmanaged<LocalVideoTrack>.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<Room>.fromOpaque(room).takeUnretainedValue()
+    let track = Unmanaged<LocalAudioTrack>.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<Room>.fromOpaque(room).takeUnretainedValue()
+    let publication = Unmanaged<LocalTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+    let _ = room.localParticipant?.unpublish(publication: publication)
+}
+
+@_cdecl("LKRoomAudioTracksForRemoteParticipant")
+public func LKRoomAudioTracksForRemoteParticipant(room: UnsafeRawPointer, participantId: CFString) -> CFArray? {
+    let room = Unmanaged<Room>.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<Room>.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<Room>.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<MacOSDisplay>.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<Track>.fromOpaque(track).takeUnretainedValue() as! VideoTrack
+    let renderer = Unmanaged<LKVideoRenderer>.fromOpaque(renderer).takeRetainedValue()
+    renderer.track = track
+    track.add(videoRenderer: renderer)
+}
+
+@_cdecl("LKRemoteVideoTrackGetSid")
+public func LKRemoteVideoTrackGetSid(track: UnsafeRawPointer) -> CFString {
+    let track = Unmanaged<RemoteVideoTrack>.fromOpaque(track).takeUnretainedValue()
+    return track.sid! as CFString
+}
+
+@_cdecl("LKRemoteAudioTrackGetSid")
+public func LKRemoteAudioTrackGetSid(track: UnsafeRawPointer) -> CFString {
+    let track = Unmanaged<RemoteAudioTrack>.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<LocalTrackPublication>.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<RemoteTrackPublication>.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<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+
+    return publication.muted
+}
+
+@_cdecl("LKRemoteTrackPublicationGetSid")
+public func LKRemoteTrackPublicationGetSid(
+    publication: UnsafeRawPointer
+) -> CFString {
+    let publication = Unmanaged<RemoteTrackPublication>.fromOpaque(publication).takeUnretainedValue()
+
+    return publication.sid as CFString
+}

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 = "LiveKitBridge2";
+
+#[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<String>,
+    pub runtime_library_import_paths: Vec<String>,
+    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())
+    }
+}

crates/live_kit_client2/examples/test_app.rs 🔗

@@ -0,0 +1,178 @@
+use std::{sync::Arc, time::Duration};
+
+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;
+
+#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
+struct Quit;
+
+fn main() {
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    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");
+
+        cx.activate(true);
+
+        cx.on_action(quit);
+        cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
+
+        // todo!()
+        // 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_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 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.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);
+
+            // 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.update(|cx| cx.quit()).ok();
+        })
+        .detach();
+    });
+}
+
+fn quit(_: &Quit, cx: &mut gpui2::AppContext) {
+    cx.quit();
+}

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::*;

crates/live_kit_client2/src/prod.rs 🔗

@@ -0,0 +1,947 @@
+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<swift::Room>,
+    connection: Mutex<(
+        watch::Sender<ConnectionState>,
+        watch::Receiver<ConnectionState>,
+    )>,
+    remote_audio_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteAudioTrackUpdate>>>,
+    remote_video_track_subscribers: Mutex<Vec<mpsc::UnboundedSender<RemoteVideoTrackUpdate>>>,
+    _delegate: Mutex<RoomDelegate>,
+}
+
+trait AssertSendSync: Send {}
+impl AssertSendSync for Room {}
+
+impl Room {
+    pub fn new() -> Arc<Self> {
+        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<ConnectionState> {
+        self.connection.lock().1.clone()
+    }
+
+    pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
+        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<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
+        extern "C" fn callback(tx: *mut c_void, sources: CFArrayRef, error: CFStringRef) {
+            unsafe {
+                let tx = Box::from_raw(tx as *mut oneshot::Sender<Result<Vec<MacOSDisplay>>>);
+
+                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<Self>,
+        track: LocalVideoTrack,
+    ) -> impl Future<Output = Result<LocalTrackPublication>> {
+        let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
+        extern "C" fn callback(
+            tx: *mut c_void,
+            publication: swift::LocalTrackPublication,
+            error: CFStringRef,
+        ) {
+            let tx =
+                unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
+            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<Self>,
+        track: LocalAudioTrack,
+    ) -> impl Future<Output = Result<LocalTrackPublication>> {
+        let (tx, rx) = oneshot::channel::<Result<LocalTrackPublication>>();
+        extern "C" fn callback(
+            tx: *mut c_void,
+            publication: swift::LocalTrackPublication,
+            error: CFStringRef,
+        ) {
+            let tx =
+                unsafe { Box::from_raw(tx as *mut oneshot::Sender<Result<LocalTrackPublication>>) };
+            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<Arc<RemoteVideoTrack>> {
+        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<Arc<RemoteAudioTrack>> {
+        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<Arc<RemoteTrackPublication>> {
+        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<RemoteAudioTrackUpdate> {
+        let (tx, rx) = mpsc::unbounded();
+        self.remote_audio_track_subscribers.lock().push(tx);
+        rx
+    }
+
+    pub fn remote_video_track_updates(&self) -> mpsc::UnboundedReceiver<RemoteVideoTrackUpdate> {
+        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<String>) {
+        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<Result<()>>,
+    ) {
+        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<Result<()>>) };
+            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,
+        )
+    }
+
+    pub fn set_display_sources(&self, _: Vec<MacOSDisplay>) {
+        unreachable!("This is a test-only function")
+    }
+}
+
+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<Room>,
+}
+
+impl RoomDelegate {
+    fn new(weak_room: Weak<Room>) -> 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<Output = Result<()>> {
+        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<Result<()>>) };
+            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<swift::RemoteTrackPublication>,
+}
+
+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<Output = Result<()>> {
+        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<Result<()>>) };
+            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<swift::RemoteAudioTrack>,
+    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<Output = Result<()>> {
+        async { Ok(()) }
+    }
+
+    pub fn disable(&self) -> impl Future<Output = Result<()>> {
+        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<swift::RemoteVideoTrack>,
+    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<Frame> {
+        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<Frame>);
+                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<Frame>);
+            }
+        }
+
+        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<RemoteVideoTrack>),
+    Unsubscribed { publisher_id: Sid, track_id: Sid },
+}
+
+pub enum RemoteAudioTrackUpdate {
+    ActiveSpeakersChanged { speakers: Vec<Sid> },
+    MuteChanged { track_id: Sid, muted: bool },
+    Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
+    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()
+    }
+}

crates/live_kit_client2/src/test.rs 🔗

@@ -0,0 +1,651 @@
+use anyhow::{anyhow, Context, 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<BTreeMap<String, Arc<TestServer>>> = Mutex::new(BTreeMap::new());
+
+pub struct TestServer {
+    pub url: String,
+    pub api_key: String,
+    pub secret_key: String,
+    rooms: Mutex<HashMap<String, TestServerRoom>>,
+    executor: Arc<Executor>,
+}
+
+impl TestServer {
+    pub fn create(
+        url: String,
+        api_key: String,
+        secret_key: String,
+        executor: Arc<Executor>,
+    ) -> Result<Arc<TestServer>> {
+        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<Arc<TestServer>> {
+        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<Room>) -> 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<Vec<Arc<RemoteVideoTrack>>> {
+        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<Vec<Arc<RemoteAudioTrack>>> {
+        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<Sid, Arc<Room>>,
+    video_tracks: Vec<Arc<RemoteVideoTrack>>,
+    audio_tracks: Vec<Arc<RemoteAudioTrack>>,
+}
+
+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<String> {
+        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<String> {
+        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<ConnectionState>,
+        watch::Receiver<ConnectionState>,
+    ),
+    display_sources: Vec<MacOSDisplay>,
+    audio_track_updates: (
+        async_broadcast::Sender<RemoteAudioTrackUpdate>,
+        async_broadcast::Receiver<RemoteAudioTrackUpdate>,
+    ),
+    video_track_updates: (
+        async_broadcast::Sender<RemoteVideoTrackUpdate>,
+        async_broadcast::Receiver<RemoteVideoTrackUpdate>,
+    ),
+}
+
+#[derive(Clone, Eq, PartialEq)]
+pub enum ConnectionState {
+    Disconnected,
+    Connected { url: String, token: String },
+}
+
+pub struct Room(Mutex<RoomState>);
+
+impl Room {
+    pub fn new() -> Arc<Self> {
+        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<ConnectionState> {
+        self.0.lock().connection.1.clone()
+    }
+
+    pub fn connect(self: &Arc<Self>, url: &str, token: &str) -> impl Future<Output = Result<()>> {
+        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
+                .context("room join")?;
+            *this.0.lock().connection.0.borrow_mut() = ConnectionState::Connected { url, token };
+            Ok(())
+        }
+    }
+
+    pub fn display_sources(self: &Arc<Self>) -> impl Future<Output = Result<Vec<MacOSDisplay>>> {
+        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<Self>,
+        track: LocalVideoTrack,
+    ) -> impl Future<Output = Result<LocalTrackPublication>> {
+        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<Self>,
+        track: LocalAudioTrack,
+    ) -> impl Future<Output = Result<LocalTrackPublication>> {
+        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<Arc<RemoteAudioTrack>> {
+        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<Arc<RemoteTrackPublication>> {
+        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<Arc<RemoteVideoTrack>> {
+        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<Item = RemoteAudioTrackUpdate> {
+        self.0.lock().audio_track_updates.1.clone()
+    }
+
+    pub fn remote_video_track_updates(&self) -> impl Stream<Item = RemoteVideoTrackUpdate> {
+        self.0.lock().video_track_updates.1.clone()
+    }
+
+    pub fn set_display_sources(&self, sources: Vec<MacOSDisplay>) {
+        self.0.lock().display_sources = sources;
+    }
+
+    fn test_server(&self) -> Arc<TestServer> {
+        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<Output = Result<()>> {
+        async { Ok(()) }
+    }
+}
+
+pub struct RemoteTrackPublication;
+
+impl RemoteTrackPublication {
+    pub fn set_enabled(&self, _enabled: bool) -> impl Future<Output = Result<()>> {
+        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<Frame>,
+}
+
+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
+    }
+}
+
+#[derive(Debug)]
+pub struct RemoteVideoTrack {
+    sid: Sid,
+    publisher_id: Sid,
+    frames_rx: async_broadcast::Receiver<Frame>,
+}
+
+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<Frame> {
+        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<Output = Result<()>> {
+        async { Ok(()) }
+    }
+
+    pub fn disable(&self) -> impl Future<Output = Result<()>> {
+        async { Ok(()) }
+    }
+}
+
+#[derive(Clone)]
+pub enum RemoteVideoTrackUpdate {
+    Subscribed(Arc<RemoteVideoTrack>),
+    Unsubscribed { publisher_id: Sid, track_id: Sid },
+}
+
+#[derive(Clone)]
+pub enum RemoteAudioTrackUpdate {
+    ActiveSpeakersChanged { speakers: Vec<Sid> },
+    MuteChanged { track_id: Sid, muted: bool },
+    Subscribed(Arc<RemoteAudioTrack>, Arc<RemoteTrackPublication>),
+    Unsubscribed { publisher_id: Sid, track_id: Sid },
+}
+
+#[derive(Clone)]
+pub struct MacOSDisplay {
+    frames: (
+        async_broadcast::Sender<Frame>,
+        async_broadcast::Receiver<Frame>,
+    ),
+}
+
+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")
+    }
+}

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

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<u64>,
+    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<D>(&self, snapshot: &MultiBufferSnapshot) -> D
+    where
+        D: TextDimension + Ord + Sub<D, Output = D>,
+    {
+        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<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
+    fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
+    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
+}
+
+impl AnchorRangeExt for Range<Anchor> {
+    fn cmp(&self, other: &Range<Anchor>, 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<usize> {
+        self.start.to_offset(content)..self.end.to_offset(content)
+    }
+
+    fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point> {
+        self.start.to_point(content)..self.end.to_point(content)
+    }
+}

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<MultiBufferSnapshot>,
+    buffers: RefCell<HashMap<u64, BufferState>>,
+    next_excerpt_id: usize,
+    subscriptions: Topic,
+    singleton: bool,
+    replica_id: ReplicaId,
+    history: History,
+    title: Option<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum Event {
+    ExcerptsAdded {
+        buffer: Model<Buffer>,
+        predecessor: ExcerptId,
+        excerpts: Vec<(ExcerptId, ExcerptRange<language2::Anchor>)>,
+    },
+    ExcerptsRemoved {
+        ids: Vec<ExcerptId>,
+    },
+    ExcerptsEdited {
+        ids: Vec<ExcerptId>,
+    },
+    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<Transaction>,
+    redo_stack: Vec<Transaction>,
+    transaction_depth: usize,
+    group_interval: Duration,
+}
+
+#[derive(Clone)]
+struct Transaction {
+    id: TransactionId,
+    buffer_transactions: HashMap<u64, text::TransactionId>,
+    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<Buffer>,
+    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<Locator>,
+    _subscriptions: [gpui2::Subscription; 2],
+}
+
+#[derive(Clone, Default)]
+pub struct MultiBufferSnapshot {
+    singleton: bool,
+    excerpts: SumTree<Excerpt>,
+    excerpt_ids: SumTree<ExcerptIdMapping>,
+    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<text::Anchor>,
+    pub starts_new_buffer: bool,
+}
+
+#[derive(Clone)]
+struct Excerpt {
+    id: ExcerptId,
+    locator: Locator,
+    buffer_id: u64,
+    buffer: BufferSnapshot,
+    range: ExcerptRange<text::Anchor>,
+    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<T> {
+    pub context: Range<T>,
+    pub primary: Option<Range<T>>,
+}
+
+#[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<u32>,
+    excerpts: Cursor<'a, Excerpt, Point>,
+}
+
+pub struct MultiBufferChunks<'a> {
+    range: Range<usize>,
+    excerpts: Cursor<'a, Excerpt, usize>,
+    excerpt_chunks: Option<ExcerptChunks<'a>>,
+    language_aware: bool,
+}
+
+pub struct MultiBufferBytes<'a> {
+    range: Range<usize>,
+    excerpts: Cursor<'a, Excerpt, usize>,
+    excerpt_bytes: Option<ExcerptBytes<'a>>,
+    chunk: &'a [u8],
+}
+
+pub struct ReversedMultiBufferBytes<'a> {
+    range: Range<usize>,
+    excerpts: Cursor<'a, Excerpt, usize>,
+    excerpt_bytes: Option<ExcerptBytes<'a>>,
+    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>) -> 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<Buffer>, cx: &mut ModelContext<Self>) -> 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<MultiBufferSnapshot> {
+        self.sync(cx);
+        self.snapshot.borrow()
+    }
+
+    pub fn as_singleton(&self) -> Option<Model<Buffer>> {
+        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<T: ToOffset>(
+        &self,
+        offset: T,
+        theme: Option<&SyntaxTheme>,
+        cx: &AppContext,
+    ) -> Option<(u64, Vec<OutlineItem<Anchor>>)> {
+        self.read(cx).symbols_containing(offset, theme)
+    }
+
+    pub fn edit<I, S, T>(
+        &mut self,
+        edits: I,
+        mut autoindent_mode: Option<AutoindentMode>,
+        cx: &mut ModelContext<Self>,
+    ) where
+        I: IntoIterator<Item = (Range<S>, T)>,
+        S: ToOffset,
+        T: Into<Arc<str>>,
+    {
+        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<usize>,
+            new_text: Arc<str>,
+            is_insertion: bool,
+            original_indent_column: u32,
+        }
+        let mut buffer_edits: HashMap<u64, Vec<BufferEdit>> = Default::default();
+        let mut edited_excerpt_ids = Vec::new();
+        let mut cursor = snapshot.excerpts.cursor::<usize>();
+        for (ix, (range, new_text)) in edits.enumerate() {
+            let new_text: Arc<str> = 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<u64, Vec<BufferEdit>>,
+            autoindent_mode: Option<AutoindentMode>,
+            edited_excerpt_ids: Vec<ExcerptId>,
+            cx: &mut ModelContext<MultiBuffer>,
+        ) {
+            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<str> = "".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<Self>) -> Option<TransactionId> {
+        self.start_transaction_at(Instant::now(), cx)
+    }
+
+    pub fn start_transaction_at(
+        &mut self,
+        now: Instant,
+        cx: &mut ModelContext<Self>,
+    ) -> Option<TransactionId> {
+        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<Self>) -> Option<TransactionId> {
+        self.end_transaction_at(Instant::now(), cx)
+    }
+
+    pub fn end_transaction_at(
+        &mut self,
+        now: Instant,
+        cx: &mut ModelContext<Self>,
+    ) -> Option<TransactionId> {
+        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<Self>,
+    ) {
+        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>) {
+        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<Self>)
+    where
+        T: IntoIterator<Item = (&'a Model<Buffer>, &'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<Self>,
+    ) {
+        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<Anchor>],
+        line_mode: bool,
+        cursor_shape: CursorShape,
+        cx: &mut ModelContext<Self>,
+    ) {
+        let mut selections_by_buffer: HashMap<u64, Vec<Selection<text::Anchor>>> =
+            Default::default();
+        let snapshot = self.read(cx);
+        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
+        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<Self>) {
+        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<Self>) -> Option<TransactionId> {
+        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<Self>) -> Option<TransactionId> {
+        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<Self>) {
+        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<Buffer>,
+        ranges: Vec<Range<text::Anchor>>,
+        context_line_count: u32,
+        cx: &mut ModelContext<Self>,
+    ) -> mpsc::Receiver<Range<Anchor>> {
+        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<O>(
+        &mut self,
+        buffer: Model<Buffer>,
+        ranges: impl IntoIterator<Item = ExcerptRange<O>>,
+        cx: &mut ModelContext<Self>,
+    ) -> Vec<ExcerptId>
+    where
+        O: text::ToOffset,
+    {
+        self.insert_excerpts_after(ExcerptId::max(), buffer, ranges, cx)
+    }
+
+    pub fn push_excerpts_with_context_lines<O>(
+        &mut self,
+        buffer: Model<Buffer>,
+        ranges: Vec<Range<O>>,
+        context_line_count: u32,
+        cx: &mut ModelContext<Self>,
+    ) -> Vec<Range<Anchor>>
+    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<O>(
+        &mut self,
+        prev_excerpt_id: ExcerptId,
+        buffer: Model<Buffer>,
+        ranges: impl IntoIterator<Item = ExcerptRange<O>>,
+        cx: &mut ModelContext<Self>,
+    ) -> Vec<ExcerptId>
+    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<O>(
+        &mut self,
+        prev_excerpt_id: ExcerptId,
+        buffer: Model<Buffer>,
+        ranges: impl IntoIterator<Item = (ExcerptId, ExcerptRange<O>)>,
+        cx: &mut ModelContext<Self>,
+    ) 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::<Option<&Locator>>();
+        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>) {
+        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<Buffer>,
+        cx: &AppContext,
+    ) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
+        let mut excerpts = Vec::new();
+        let snapshot = self.read(cx);
+        let buffers = self.buffers.borrow();
+        let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>();
+        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<ExcerptId> {
+        self.snapshot
+            .borrow()
+            .excerpts
+            .iter()
+            .map(|entry| entry.id)
+            .collect()
+    }
+
+    pub fn excerpt_containing(
+        &self,
+        position: impl ToOffset,
+        cx: &AppContext,
+    ) -> Option<(ExcerptId, Model<Buffer>, Range<text::Anchor>)> {
+        let snapshot = self.read(cx);
+        let position = position.to_offset(&snapshot);
+
+        let mut cursor = snapshot.excerpts.cursor::<usize>();
+        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<T: ToOffset>(
+        &self,
+        point: T,
+        cx: &AppContext,
+    ) -> Option<(Model<Buffer>, usize, ExcerptId)> {
+        let snapshot = self.read(cx);
+        let offset = point.to_offset(&snapshot);
+        let mut cursor = snapshot.excerpts.cursor::<usize>();
+        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<T: ToOffset>(
+        &self,
+        range: Range<T>,
+        cx: &AppContext,
+    ) -> Vec<(Model<Buffer>, Range<usize>, 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::<usize>();
+        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<Item = ExcerptId>,
+        cx: &mut ModelContext<Self>,
+    ) {
+        self.sync(cx);
+        let ids = excerpt_ids.into_iter().collect::<Vec<_>>();
+        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<Item = Anchor>,
+        cx: &mut ModelContext<Self>,
+    ) -> impl 'static + Future<Output = Result<()>> {
+        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<T: ToOffset>(
+        &self,
+        position: T,
+        cx: &AppContext,
+    ) -> Option<(Model<Buffer>, 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<Buffer>,
+        event: &language2::Event,
+        cx: &mut ModelContext<Self>,
+    ) {
+        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<Model<Buffer>> {
+        self.buffers
+            .borrow()
+            .values()
+            .map(|state| state.buffer.clone())
+            .collect()
+    }
+
+    pub fn buffer(&self, buffer_id: u64) -> Option<Model<Buffer>> {
+        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<Arc<Language>> {
+        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<Buffer>)) {
+        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::<usize>(
+                            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<Self> {
+        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<const COUNT: usize>(
+        excerpts: [(&str, Vec<Range<Point>>); COUNT],
+        cx: &mut gpui2::AppContext,
+    ) -> Model<Self> {
+        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<Buffer>, cx: &mut gpui2::AppContext) -> Model<Self> {
+        cx.build_model(|cx| Self::singleton(buffer, cx))
+    }
+
+    pub fn build_random(rng: &mut impl rand::Rng, cx: &mut gpui2::AppContext) -> Model<Self> {
+        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<Self>,
+    ) {
+        use util::RandomCharIter;
+
+        let snapshot = self.read(cx);
+        let mut edits: Vec<(Range<usize>, Arc<str>)> = 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<Self>,
+    ) {
+        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::<String>();
+                    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::<Vec<_>>();
+                log::info!(
+                    "Inserting excerpts from buffer {} and ranges {:?}: {:?}",
+                    buffer_handle.read(cx).remote_id(),
+                    ranges.iter().map(|r| &r.context).collect::<Vec<_>>(),
+                    ranges
+                        .iter()
+                        .map(|r| &buffer_text[r.context.clone()])
+                        .collect::<Vec<_>>()
+                );
+
+                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::<Vec<_>>();
+                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<Self>,
+    ) {
+        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<Self>) {
+        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<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
+        let mut offset = position.to_offset(self);
+        let mut cursor = self.excerpts.cursor::<usize>();
+        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<T: ToOffset>(&self, position: T) -> impl Iterator<Item = char> + '_ {
+        let offset = position.to_offset(self);
+        self.text_for_range(offset..self.len())
+            .flat_map(|chunk| chunk.chars())
+    }
+
+    pub fn text_for_range<T: ToOffset>(&self, range: Range<T>) -> impl Iterator<Item = &str> + '_ {
+        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<T>(&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<T: ToOffset>(&self, start: T) -> (Range<usize>, Option<CharKind>) {
+        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::<usize>();
+        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::<Point>();
+        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::<OffsetUtf16>();
+        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<PointUtf16>, bias: Bias) -> PointUtf16 {
+        if let Some((_, _, buffer)) = self.as_singleton() {
+            return buffer.clip_point_utf16(point, bias);
+        }
+
+        let mut cursor = self.excerpts.cursor::<PointUtf16>();
+        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<T: ToOffset>(&self, range: Range<T>) -> MultiBufferBytes {
+        let range = range.start.to_offset(self)..range.end.to_offset(self);
+        let mut excerpts = self.excerpts.cursor::<usize>();
+        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<T: ToOffset>(
+        &self,
+        range: Range<T>,
+    ) -> ReversedMultiBufferBytes {
+        let range = range.start.to_offset(self)..range.end.to_offset(self);
+        let mut excerpts = self.excerpts.cursor::<usize>();
+        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<T: ToOffset>(&self, range: Range<T>, 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<T: ToOffset>(
+        &self,
+        point: T,
+    ) -> Option<(&BufferSnapshot, usize)> {
+        let offset = point.to_offset(&self);
+        let mut cursor = self.excerpts.cursor::<usize>();
+        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<Item = u32>,
+        cx: &AppContext,
+    ) -> BTreeMap<u32, IndentSize> {
+        let mut result = BTreeMap::new();
+
+        let mut rows_for_excerpt = Vec::new();
+        let mut cursor = self.excerpts.cursor::<Point>();
+        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<u32> {
+        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<Point>)> {
+        let mut cursor = self.excerpts.cursor::<Point>();
+        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<D, O>(&self, range: Range<O>) -> 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::<usize>();
+        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<D>(&self, anchor: &Anchor) -> D
+    where
+        D: TextDimension + Ord + Sub<D, Output = D>,
+    {
+        let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
+        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::<D>(&excerpt.buffer);
+                let excerpt_buffer_end = excerpt.range.context.end.summary::<D>(&excerpt.buffer);
+                let buffer_position = cmp::min(
+                    excerpt_buffer_end,
+                    anchor.text_anchor.summary::<D>(&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<D>
+    where
+        D: TextDimension + Ord + Sub<D, Output = D>,
+        I: 'a + IntoIterator<Item = &'a Anchor>,
+    {
+        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::<ExcerptSummary>();
+        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::<D>(&excerpt.buffer);
+                    let excerpt_buffer_end =
+                        excerpt.range.context.end.summary::<D>(&excerpt.buffer);
+                    summaries.extend(
+                        excerpt
+                            .buffer
+                            .summaries_for_anchors::<D, _>(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<Item = &'a Anchor>,
+    {
+        let mut anchors = anchors.into_iter().enumerate().peekable();
+        let mut cursor = self.excerpts.cursor::<Option<&Locator>>();
+        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<T: ToOffset>(&self, position: T) -> Anchor {
+        self.anchor_at(position, Bias::Left)
+    }
+
+    pub fn anchor_after<T: ToOffset>(&self, position: T) -> Anchor {
+        self.anchor_at(position, Bias::Right)
+    }
+
+    pub fn anchor_at<T: ToOffset>(&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<ExcerptId>)>();
+        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::<Option<&Locator>>();
+        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<Item = (ExcerptId, &BufferSnapshot, ExcerptRange<text::Anchor>)> {
+        self.excerpts
+            .iter()
+            .map(|excerpt| (excerpt.id, &excerpt.buffer, excerpt.range.clone()))
+    }
+
+    pub fn excerpt_boundaries_in_range<R, T>(
+        &self,
+        range: R,
+    ) -> impl Iterator<Item = ExcerptBoundary> + '_
+    where
+        R: RangeBounds<T>,
+        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<T: ToOffset>(
+        &self,
+        range: Range<T>,
+    ) -> Option<(Range<usize>, Range<usize>)> {
+        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<usize>, Range<usize>)> = 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<T>,
+    ) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + '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<T>,
+    ) -> Option<impl Iterator<Item = (Range<usize>, Range<usize>)> + '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<dyn File>> {
+        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<Language>> {
+        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<LanguageScope> {
+        self.point_to_buffer_offset(point)
+            .and_then(|(buffer, offset)| buffer.language_scope_at(offset))
+    }
+
+    pub fn language_indent_size_at<T: ToOffset>(
+        &self,
+        position: T,
+        cx: &AppContext,
+    ) -> Option<IndentSize> {
+        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<Item = DiagnosticEntry<O>> + '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<T>,
+        reversed: bool,
+    ) -> impl Iterator<Item = DiagnosticEntry<O>> + '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<u32>,
+    ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
+        let mut cursor = self.excerpts.cursor::<Point>();
+
+        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<u32>,
+    ) -> impl 'a + Iterator<Item = DiffHunk<u32>> {
+        let mut cursor = self.excerpts.cursor::<Point>();
+
+        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<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
+        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<Outline<Anchor>> {
+        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<T: ToOffset>(
+        &self,
+        offset: T,
+        theme: Option<&SyntaxTheme>,
+    ) -> Option<(u64, Vec<OutlineItem<Anchor>>)> {
+        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::<ExcerptId>();
+            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<u64> {
+        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::<Option<&Locator>>();
+        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<T>,
+    ) -> Option<(&'a Excerpt, usize)> {
+        let range = range.start.to_offset(self)..range.end.to_offset(self);
+
+        let mut cursor = self.excerpts.cursor::<usize>();
+        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<Anchor>,
+    ) -> impl 'a + Iterator<Item = (ReplicaId, bool, CursorShape, Selection<Anchor>)> {
+        let mut cursor = self.excerpts.cursor::<ExcerptSummary>();
+        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<usize> {
+        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<TransactionId> {
+        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<u64, TransactionId>,
+    ) -> 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<MultiBuffer>,
+    ) where
+        T: IntoIterator<Item = (&'a Model<Buffer>, &'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<Transaction> {
+        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<TransactionId> {
+        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<TransactionId> {
+        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<text::Anchor>,
+        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::<TextSummary, _>(range.context.to_offset(&buffer)),
+            buffer_id,
+            buffer,
+            range,
+            has_trailing_newline,
+        }
+    }
+
+    fn chunks_in_range(&self, range: Range<usize>, 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<usize>) -> 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<usize>) -> 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<usize> 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<ExcerptId> {
+    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<u32>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        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<Self::Item> {
+        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<Self::Item> {
+        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<usize> {
+        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<usize> {
+        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<Self::Item> {
+        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<Self::Item> {
+        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<T>(
+    buffer: &BufferSnapshot,
+    ranges: &[Range<T>],
+    context_line_count: u32,
+) -> (Vec<ExcerptRange<Point>>, Vec<usize>)
+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::<Vec<_>>(),
+            (0..buffer.read(cx).row_count())
+                .map(Some)
+                .collect::<Vec<_>>()
+        );
+
+        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::<Vec<_>>(),
+            (0..buffer.read(cx).row_count())
+                .map(Some)
+                .collect::<Vec<_>>()
+        );
+    }
+
+    #[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::<Event>::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::<Vec<_>>(),
+            [Some(1), Some(2), Some(3), Some(4), Some(3)]
+        );
+        assert_eq!(
+            snapshot.buffer_rows(2).collect::<Vec<_>>(),
+            [Some(3), Some(4), Some(3)]
+        );
+        assert_eq!(snapshot.buffer_rows(4).collect::<Vec<_>>(), [Some(3)]);
+        assert_eq!(snapshot.buffer_rows(5).collect::<Vec<_>>(), []);
+
+        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<Point>,
+            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::<String>(),
+                        boundary.starts_new_buffer,
+                    )
+                })
+                .collect::<Vec<_>>()
+        }
+    }
+
+    #[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::<usize>(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<_>>(),
+            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::<Vec<_>>().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<_>>(),
+            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::<Vec<_>>(), &[Some(0)]);
+        assert_eq!(snapshot.buffer_rows(1).collect::<Vec<_>>(), &[]);
+    }
+
+    #[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::<usize>(&snapshot_1.anchor_before(2)),
+            0
+        );
+        assert_eq!(
+            snapshot_2.summaries_for_anchors::<usize, _>(&[
+                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::<usize, _>(&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::<Vec<_>>(),
+            &[(0, true), (1, true), (2, true), (3, true)]
+        );
+        assert_eq!(
+            snapshot_3.summaries_for_anchors::<usize, _>(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<Model<Buffer>> = Vec::new();
+        let multibuffer = cx.build_model(|_| MultiBuffer::new(0));
+        let mut excerpt_ids = Vec::<ExcerptId>::new();
+        let mut expected_excerpts = Vec::<(Model<Buffer>, Range<text::Anchor>)>::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::<String>(),
+                        );
+                    }
+                    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::<String>();
+                        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::<Vec<_>>(),
+                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::<Vec<_>>(),
+                    &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::<PointUtf16, _>(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::<String>();
+                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::<String>()
+                    })
+                    .collect::<Vec<_>>()
+                    .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::<TextSummary, _>(start_ix..end_ix),
+                    expected_summary,
+                    "incorrect summary for range {:?}",
+                    start_ix..end_ix
+                );
+            }
+
+            // Anchor resolution
+            let summaries = snapshot.summaries_for_anchors::<usize, _>(&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::<usize>(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::<String>(),
+                    expected_text[..end_ix].chars().rev().collect::<String>(),
+                );
+            }
+
+            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::<Vec<_>>(),
+                    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");
+        });
+    }
+}

crates/prettier/src/prettier_server.js 🔗

@@ -55,8 +55,11 @@ async function handleBuffer(prettier) {
         }
         // allow concurrent request handling by not `await`ing the message handling promise (async function)
         handleMessage(message, prettier).catch(e => {
-            sendResponse({ id: message.id, ...makeError(`error during message handling: ${e}`) });
-        });
+            const errorMessage = message;
+            if ((errorMessage.params || {}).text !== undefined) {
+                errorMessage.params.text = "..snip..";
+            }
+            sendResponse({ id: message.id, ...makeError(`error during message '${JSON.stringify(errorMessage)}' handling: ${e}`) }); });
     }
 }
 
@@ -172,7 +175,7 @@ async function handleMessage(message, prettier) {
         sendResponse({ id, result: null });
     } else if (method === 'initialize') {
         sendResponse({
-            id,
+            id: id || 0,
             result: {
                 "capabilities": {}
             }

crates/project/src/project.rs 🔗

@@ -162,12 +162,20 @@ pub struct Project {
     copilot_log_subscription: Option<lsp::Subscription>,
     current_lsp_settings: HashMap<Arc<str>, LspSettings>,
     node: Option<Arc<dyn NodeRuntime>>,
+    #[cfg(not(any(test, feature = "test-support")))]
+    default_prettier: Option<DefaultPrettier>,
     prettier_instances: HashMap<
         (Option<WorktreeId>, PathBuf),
         Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>,
     >,
 }
 
+#[cfg(not(any(test, feature = "test-support")))]
+struct DefaultPrettier {
+    installation_process: Option<Shared<Task<()>>>,
+    installed_plugins: HashSet<&'static str>,
+}
+
 struct DelayedDebounced {
     task: Option<Task<()>>,
     cancel_channel: Option<oneshot::Sender<()>>,
@@ -677,6 +685,8 @@ impl Project {
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
                 node: Some(node),
+                #[cfg(not(any(test, feature = "test-support")))]
+                default_prettier: None,
                 prettier_instances: HashMap::default(),
             }
         })
@@ -776,6 +786,8 @@ impl Project {
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
                 node: None,
+                #[cfg(not(any(test, feature = "test-support")))]
+                default_prettier: None,
                 prettier_instances: HashMap::default(),
             };
             for worktree in worktrees {
@@ -8497,7 +8509,7 @@ impl Project {
 
     #[cfg(any(test, feature = "test-support"))]
     fn install_default_formatters(
-        &self,
+        &mut self,
         _worktree: Option<WorktreeId>,
         _new_language: &Language,
         _language_settings: &LanguageSettings,
@@ -8508,7 +8520,7 @@ impl Project {
 
     #[cfg(not(any(test, feature = "test-support")))]
     fn install_default_formatters(
-        &self,
+        &mut self,
         worktree: Option<WorktreeId>,
         new_language: &Language,
         language_settings: &LanguageSettings,
@@ -8537,51 +8549,108 @@ impl Project {
             return Task::ready(Ok(()));
         };
 
+        let mut plugins_to_install = prettier_plugins;
+        let (mut install_success_tx, mut install_success_rx) =
+            futures::channel::mpsc::channel::<HashSet<&'static str>>(1);
+        let new_installation_process = cx
+            .spawn(|this, mut cx| async move {
+                if let Some(installed_plugins) = install_success_rx.next().await {
+                    this.update(&mut cx, |this, _| {
+                        let default_prettier =
+                            this.default_prettier
+                                .get_or_insert_with(|| DefaultPrettier {
+                                    installation_process: None,
+                                    installed_plugins: HashSet::default(),
+                                });
+                        if !installed_plugins.is_empty() {
+                            log::info!("Installed new prettier plugins: {installed_plugins:?}");
+                            default_prettier.installed_plugins.extend(installed_plugins);
+                        }
+                    })
+                }
+            })
+            .shared();
+        let previous_installation_process =
+            if let Some(default_prettier) = &mut self.default_prettier {
+                plugins_to_install
+                    .retain(|plugin| !default_prettier.installed_plugins.contains(plugin));
+                if plugins_to_install.is_empty() {
+                    return Task::ready(Ok(()));
+                }
+                std::mem::replace(
+                    &mut default_prettier.installation_process,
+                    Some(new_installation_process.clone()),
+                )
+            } else {
+                None
+            };
+
         let default_prettier_dir = util::paths::DEFAULT_PRETTIER_DIR.as_path();
         let already_running_prettier = self
             .prettier_instances
             .get(&(worktree, default_prettier_dir.to_path_buf()))
             .cloned();
-
         let fs = Arc::clone(&self.fs);
-        cx.background()
-            .spawn(async move {
-                let prettier_wrapper_path = default_prettier_dir.join(prettier::PRETTIER_SERVER_FILE);
-                // method creates parent directory if it doesn't exist
-                fs.save(&prettier_wrapper_path, &text::Rope::from(prettier::PRETTIER_SERVER_JS), text::LineEnding::Unix).await
-                .with_context(|| format!("writing {} file at {prettier_wrapper_path:?}", prettier::PRETTIER_SERVER_FILE))?;
-
-                let packages_to_versions = future::try_join_all(
-                    prettier_plugins
-                        .iter()
-                        .chain(Some(&"prettier"))
-                        .map(|package_name| async {
-                            let returned_package_name = package_name.to_string();
-                            let latest_version = node.npm_package_latest_version(package_name)
-                                .await
-                                .with_context(|| {
-                                    format!("fetching latest npm version for package {returned_package_name}")
-                                })?;
-                            anyhow::Ok((returned_package_name, latest_version))
-                        }),
-                )
-                .await
-                .context("fetching latest npm versions")?;
+        cx.spawn(|this, mut cx| async move {
+            if let Some(previous_installation_process) = previous_installation_process {
+                previous_installation_process.await;
+            }
+            let mut everything_was_installed = false;
+            this.update(&mut cx, |this, _| {
+                match &mut this.default_prettier {
+                    Some(default_prettier) => {
+                        plugins_to_install
+                            .retain(|plugin| !default_prettier.installed_plugins.contains(plugin));
+                        everything_was_installed = plugins_to_install.is_empty();
+                    },
+                    None => this.default_prettier = Some(DefaultPrettier { installation_process: Some(new_installation_process), installed_plugins: HashSet::default() }),
+                }
+            });
+            if everything_was_installed {
+                return Ok(());
+            }
 
-                log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
-                let borrowed_packages = packages_to_versions.iter().map(|(package, version)| {
-                    (package.as_str(), version.as_str())
-                }).collect::<Vec<_>>();
-                node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?;
+            cx.background()
+                .spawn(async move {
+                    let prettier_wrapper_path = default_prettier_dir.join(prettier::PRETTIER_SERVER_FILE);
+                    // method creates parent directory if it doesn't exist
+                    fs.save(&prettier_wrapper_path, &text::Rope::from(prettier::PRETTIER_SERVER_JS), text::LineEnding::Unix).await
+                    .with_context(|| format!("writing {} file at {prettier_wrapper_path:?}", prettier::PRETTIER_SERVER_FILE))?;
 
-                if !prettier_plugins.is_empty() {
-                    if let Some(prettier) = already_running_prettier {
-                        prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?;
+                    let packages_to_versions = future::try_join_all(
+                        plugins_to_install
+                            .iter()
+                            .chain(Some(&"prettier"))
+                            .map(|package_name| async {
+                                let returned_package_name = package_name.to_string();
+                                let latest_version = node.npm_package_latest_version(package_name)
+                                    .await
+                                    .with_context(|| {
+                                        format!("fetching latest npm version for package {returned_package_name}")
+                                    })?;
+                                anyhow::Ok((returned_package_name, latest_version))
+                            }),
+                    )
+                    .await
+                    .context("fetching latest npm versions")?;
+
+                    log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
+                    let borrowed_packages = packages_to_versions.iter().map(|(package, version)| {
+                        (package.as_str(), version.as_str())
+                    }).collect::<Vec<_>>();
+                    node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?;
+                    let installed_packages = !plugins_to_install.is_empty();
+                    install_success_tx.try_send(plugins_to_install).ok();
+
+                    if !installed_packages {
+                        if let Some(prettier) = already_running_prettier {
+                            prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?;
+                        }
                     }
-                }
 
-                anyhow::Ok(())
-            })
+                    anyhow::Ok(())
+                }).await
+        })
     }
 }
 

crates/project/src/worktree.rs 🔗

@@ -2662,12 +2662,12 @@ impl language::File for File {
 
 impl language::LocalFile for File {
     fn abs_path(&self, cx: &AppContext) -> PathBuf {
-        self.worktree
-            .read(cx)
-            .as_local()
-            .unwrap()
-            .abs_path
-            .join(&self.path)
+        let worktree_path = &self.worktree.read(cx).as_local().unwrap().abs_path;
+        if self.path.as_ref() == Path::new("") {
+            worktree_path.to_path_buf()
+        } else {
+            worktree_path.join(&self.path)
+        }
     }
 
     fn load(&self, cx: &AppContext) -> Task<Result<String>> {

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<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> 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))),

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<Self, StatefulInteraction<Self>, FocusEnabled<Self>>;
 
     fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> 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();
 

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<Self, StatefulInteraction<Self>>;
 
     fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> 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| {

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| {
@@ -71,7 +71,6 @@ fn main() {
         theme_settings.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(
@@ -82,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);

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"] }

crates/theme2/src/colors.rs 🔗

@@ -0,0 +1,144 @@
+use gpui2::Hsla;
+use refineable::Refineable;
+
+use crate::SyntaxTheme;
+
+#[derive(Clone)]
+pub struct SystemColors {
+    pub transparent: Hsla,
+    pub mac_os_traffic_light_red: Hsla,
+    pub mac_os_traffic_light_yellow: Hsla,
+    pub mac_os_traffic_light_green: Hsla,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct PlayerColor {
+    pub cursor: Hsla,
+    pub background: Hsla,
+    pub selection: Hsla,
+}
+
+#[derive(Clone)]
+pub struct PlayerColors(pub Vec<PlayerColor>);
+
+#[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, Default)]
+#[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,
+}
+
+#[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)]
+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);
+    }
+}

crates/theme2/src/default.rs → crates/theme2/src/default_colors.rs 🔗

@@ -1,10 +1,276 @@
-use gpui2::Rgba;
-use indexmap::IndexMap;
+use gpui2::{hsla, Rgba};
 
-use crate::scale::{ColorScaleName, ColorScaleSet, ColorScales};
+use crate::{
+    colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors},
+    scale::{ColorScaleSet, ColorScales},
+    syntax::SyntaxTheme,
+};
+
+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 SyntaxTheme {
+    pub fn default_light() -> Self {
+        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 {
+        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(),
+                ),
+            ],
+        }
+    }
+}
+
+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 +298,46 @@ impl From<DefaultColorScaleSet> 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(),
+        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: ColorScaleName::Gray,
+        scale: "Gray",
         light: [
             "#fcfcfcff",
             "#f9f9f9ff",
@@ -131,11 +395,12 @@ fn gray() -> DefaultColorScaleSet {
             "#ffffffed",
         ],
     }
+    .into()
 }
 
-fn mauve() -> DefaultColorScaleSet {
+fn mauve() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Mauve,
+        scale: "Mauve",
         light: [
             "#fdfcfdff",
             "#faf9fbff",
@@ -193,11 +458,12 @@ fn mauve() -> DefaultColorScaleSet {
             "#fdfdffef",
         ],
     }
+    .into()
 }
 
-fn slate() -> DefaultColorScaleSet {
+fn slate() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Slate,
+        scale: "Slate",
         light: [
             "#fcfcfdff",
             "#f9f9fbff",
@@ -255,11 +521,12 @@ fn slate() -> DefaultColorScaleSet {
             "#fcfdffef",
         ],
     }
+    .into()
 }
 
-fn sage() -> DefaultColorScaleSet {
+fn sage() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Sage,
+        scale: "Sage",
         light: [
             "#fbfdfcff",
             "#f7f9f8ff",
@@ -317,11 +584,12 @@ fn sage() -> DefaultColorScaleSet {
             "#fdfffeed",
         ],
     }
+    .into()
 }
 
-fn olive() -> DefaultColorScaleSet {
+fn olive() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Olive,
+        scale: "Olive",
         light: [
             "#fcfdfcff",
             "#f8faf8ff",
@@ -379,11 +647,12 @@ fn olive() -> DefaultColorScaleSet {
             "#fdfffded",
         ],
     }
+    .into()
 }
 
-fn sand() -> DefaultColorScaleSet {
+fn sand() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Sand,
+        scale: "Sand",
         light: [
             "#fdfdfcff",
             "#f9f9f8ff",
@@ -441,11 +710,12 @@ fn sand() -> DefaultColorScaleSet {
             "#fffffded",
         ],
     }
+    .into()
 }
 
-fn gold() -> DefaultColorScaleSet {
+fn gold() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Gold,
+        scale: "Gold",
         light: [
             "#fdfdfcff",
             "#faf9f2ff",
@@ -503,11 +773,12 @@ fn gold() -> DefaultColorScaleSet {
             "#fef7ede7",
         ],
     }
+    .into()
 }
 
-fn bronze() -> DefaultColorScaleSet {
+fn bronze() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Bronze,
+        scale: "Bronze",
         light: [
             "#fdfcfcff",
             "#fdf7f5ff",
@@ -565,11 +836,12 @@ fn bronze() -> DefaultColorScaleSet {
             "#fff1e9ec",
         ],
     }
+    .into()
 }
 
-fn brown() -> DefaultColorScaleSet {
+fn brown() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Brown,
+        scale: "Brown",
         light: [
             "#fefdfcff",
             "#fcf9f6ff",
@@ -627,11 +899,12 @@ fn brown() -> DefaultColorScaleSet {
             "#feecd4f2",
         ],
     }
+    .into()
 }
 
-fn yellow() -> DefaultColorScaleSet {
+fn yellow() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Yellow,
+        scale: "Yellow",
         light: [
             "#fdfdf9ff",
             "#fefce9ff",
@@ -689,11 +962,12 @@ fn yellow() -> DefaultColorScaleSet {
             "#fef6baf6",
         ],
     }
+    .into()
 }
 
-fn amber() -> DefaultColorScaleSet {
+fn amber() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Amber,
+        scale: "Amber",
         light: [
             "#fefdfbff",
             "#fefbe9ff",
@@ -751,11 +1025,12 @@ fn amber() -> DefaultColorScaleSet {
             "#ffe7b3ff",
         ],
     }
+    .into()
 }
 
-fn orange() -> DefaultColorScaleSet {
+fn orange() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Orange,
+        scale: "Orange",
         light: [
             "#fefcfbff",
             "#fff7edff",
@@ -813,11 +1088,12 @@ fn orange() -> DefaultColorScaleSet {
             "#ffe0c2ff",
         ],
     }
+    .into()
 }
 
-fn tomato() -> DefaultColorScaleSet {
+fn tomato() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Tomato,
+        scale: "Tomato",
         light: [
             "#fffcfcff",
             "#fff8f7ff",
@@ -875,11 +1151,12 @@ fn tomato() -> DefaultColorScaleSet {
             "#ffd6cefb",
         ],
     }
+    .into()
 }
 
-fn red() -> DefaultColorScaleSet {
+fn red() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Red,
+        scale: "Red",
         light: [
             "#fffcfcff",
             "#fff7f7ff",
@@ -937,11 +1214,12 @@ fn red() -> DefaultColorScaleSet {
             "#ffd1d9ff",
         ],
     }
+    .into()
 }
 
-fn ruby() -> DefaultColorScaleSet {
+fn ruby() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Ruby,
+        scale: "Ruby",
         light: [
             "#fffcfdff",
             "#fff7f8ff",
@@ -999,11 +1277,12 @@ fn ruby() -> DefaultColorScaleSet {
             "#ffd3e2fe",
         ],
     }
+    .into()
 }
 
-fn crimson() -> DefaultColorScaleSet {
+fn crimson() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Crimson,
+        scale: "Crimson",
         light: [
             "#fffcfdff",
             "#fef7f9ff",
@@ -1061,11 +1340,12 @@ fn crimson() -> DefaultColorScaleSet {
             "#ffd5eafd",
         ],
     }
+    .into()
 }
 
-fn pink() -> DefaultColorScaleSet {
+fn pink() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Pink,
+        scale: "Pink",
         light: [
             "#fffcfeff",
             "#fef7fbff",
@@ -1123,11 +1403,12 @@ fn pink() -> DefaultColorScaleSet {
             "#ffd3ecfd",
         ],
     }
+    .into()
 }
 
-fn plum() -> DefaultColorScaleSet {
+fn plum() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Plum,
+        scale: "Plum",
         light: [
             "#fefcffff",
             "#fdf7fdff",
@@ -1185,11 +1466,12 @@ fn plum() -> DefaultColorScaleSet {
             "#feddfef4",
         ],
     }
+    .into()
 }
 
-fn purple() -> DefaultColorScaleSet {
+fn purple() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Purple,
+        scale: "Purple",
         light: [
             "#fefcfeff",
             "#fbf7feff",
@@ -1247,11 +1529,12 @@ fn purple() -> DefaultColorScaleSet {
             "#f1ddfffa",
         ],
     }
+    .into()
 }
 
-fn violet() -> DefaultColorScaleSet {
+fn violet() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Violet,
+        scale: "Violet",
         light: [
             "#fdfcfeff",
             "#faf8ffff",
@@ -1309,11 +1592,12 @@ fn violet() -> DefaultColorScaleSet {
             "#e3defffe",
         ],
     }
+    .into()
 }
 
-fn iris() -> DefaultColorScaleSet {
+fn iris() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Iris,
+        scale: "Iris",
         light: [
             "#fdfdffff",
             "#f8f8ffff",
@@ -1371,11 +1655,12 @@ fn iris() -> DefaultColorScaleSet {
             "#e1e0fffe",
         ],
     }
+    .into()
 }
 
-fn indigo() -> DefaultColorScaleSet {
+fn indigo() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Indigo,
+        scale: "Indigo",
         light: [
             "#fdfdfeff",
             "#f7f9ffff",
@@ -1433,11 +1718,12 @@ fn indigo() -> DefaultColorScaleSet {
             "#d6e1ffff",
         ],
     }
+    .into()
 }
 
-fn blue() -> DefaultColorScaleSet {
+fn blue() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Blue,
+        scale: "Blue",
         light: [
             "#fbfdffff",
             "#f4faffff",
@@ -1495,11 +1781,12 @@ fn blue() -> DefaultColorScaleSet {
             "#c2e6ffff",
         ],
     }
+    .into()
 }
 
-fn cyan() -> DefaultColorScaleSet {
+fn cyan() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Cyan,
+        scale: "Cyan",
         light: [
             "#fafdfeff",
             "#f2fafbff",
@@ -1557,11 +1844,12 @@ fn cyan() -> DefaultColorScaleSet {
             "#bbf3fef7",
         ],
     }
+    .into()
 }
 
-fn teal() -> DefaultColorScaleSet {
+fn teal() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Teal,
+        scale: "Teal",
         light: [
             "#fafefdff",
             "#f3fbf9ff",
@@ -1619,11 +1907,12 @@ fn teal() -> DefaultColorScaleSet {
             "#b8ffebef",
         ],
     }
+    .into()
 }
 
-fn jade() -> DefaultColorScaleSet {
+fn jade() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Jade,
+        scale: "Jade",
         light: [
             "#fbfefdff",
             "#f4fbf7ff",
@@ -1681,11 +1970,12 @@ fn jade() -> DefaultColorScaleSet {
             "#b8ffe1ef",
         ],
     }
+    .into()
 }
 
-fn green() -> DefaultColorScaleSet {
+fn green() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Green,
+        scale: "Green",
         light: [
             "#fbfefcff",
             "#f4fbf6ff",
@@ -1743,11 +2033,12 @@ fn green() -> DefaultColorScaleSet {
             "#bbffd7f0",
         ],
     }
+    .into()
 }
 
-fn grass() -> DefaultColorScaleSet {
+fn grass() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Grass,
+        scale: "Grass",
         light: [
             "#fbfefbff",
             "#f5fbf5ff",
@@ -1805,11 +2096,12 @@ fn grass() -> DefaultColorScaleSet {
             "#ceffceef",
         ],
     }
+    .into()
 }
 
-fn lime() -> DefaultColorScaleSet {
+fn lime() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Lime,
+        scale: "Lime",
         light: [
             "#fcfdfaff",
             "#f8faf3ff",
@@ -1867,11 +2159,12 @@ fn lime() -> DefaultColorScaleSet {
             "#e9febff7",
         ],
     }
+    .into()
 }
 
-fn mint() -> DefaultColorScaleSet {
+fn mint() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Mint,
+        scale: "Mint",
         light: [
             "#f9fefdff",
             "#f2fbf9ff",
@@ -1929,11 +2222,12 @@ fn mint() -> DefaultColorScaleSet {
             "#cbfee9f5",
         ],
     }
+    .into()
 }
 
-fn sky() -> DefaultColorScaleSet {
+fn sky() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Sky,
+        scale: "Sky",
         light: [
             "#f9feffff",
             "#f1fafdff",
@@ -1991,11 +2285,12 @@ fn sky() -> DefaultColorScaleSet {
             "#c2f3ffff",
         ],
     }
+    .into()
 }
 
-fn black() -> DefaultColorScaleSet {
+fn black() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::Black,
+        scale: "Black",
         light: [
             "#0000000d",
             "#0000001a",
@@ -2053,11 +2348,12 @@ fn black() -> DefaultColorScaleSet {
             "#000000f2",
         ],
     }
+    .into()
 }
 
-fn white() -> DefaultColorScaleSet {
+fn white() -> ColorScaleSet {
     DefaultColorScaleSet {
-        scale: ColorScaleName::White,
+        scale: "White",
         light: [
             "#ffffff0d",
             "#ffffff1a",
@@ -2115,4 +2411,5 @@ fn white() -> DefaultColorScaleSet {
             "#fffffff2",
         ],
     }
+    .into()
 }

crates/theme2/src/default_theme.rs 🔗

@@ -0,0 +1,58 @@
+use crate::{
+    colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles},
+    default_color_scales, Appearance, SyntaxTheme, ThemeFamily, ThemeVariant,
+};
+
+fn zed_pro_daylight() -> ThemeVariant {
+    ThemeVariant {
+        id: "zed_pro_daylight".to_string(),
+        name: "Zed Pro Daylight".into(),
+        appearance: Appearance::Light,
+        styles: ThemeStyles {
+            system: SystemColors::default(),
+            colors: ThemeColors::default_light(),
+            status: StatusColors::default(),
+            git: GitStatusColors::default(),
+            player: PlayerColors::default(),
+            syntax: SyntaxTheme::default_light(),
+        },
+    }
+}
+
+pub(crate) fn zed_pro_moonlight() -> ThemeVariant {
+    ThemeVariant {
+        id: "zed_pro_moonlight".to_string(),
+        name: "Zed Pro Moonlight".into(),
+        appearance: Appearance::Dark,
+        styles: ThemeStyles {
+            system: SystemColors::default(),
+            colors: ThemeColors::default_dark(),
+            status: StatusColors::default(),
+            git: GitStatusColors::default(),
+            player: PlayerColors::default(),
+            syntax: SyntaxTheme::default_dark(),
+        },
+    }
+}
+
+pub fn zed_pro_family() -> ThemeFamily {
+    ThemeFamily {
+        id: "zed_pro".to_string(),
+        name: "Zed Pro".into(),
+        author: "Zed Team".into(),
+        themes: vec![zed_pro_daylight(), zed_pro_moonlight()],
+        scales: default_color_scales(),
+    }
+}
+
+impl Default for ThemeFamily {
+    fn default() -> Self {
+        zed_pro_family()
+    }
+}
+
+impl Default for ThemeVariant {
+    fn default() -> Self {
+        zed_pro_daylight()
+    }
+}

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<SharedString, Arc<Theme>>,
+    themes: HashMap<SharedString, Arc<ThemeVariant>>,
 }
 
 impl ThemeRegistry {
-    fn insert_themes(&mut self, themes: impl IntoIterator<Item = Theme>) {
+    fn insert_theme_families(&mut self, families: impl IntoIterator<Item = ThemeFamily>) {
+        for family in families.into_iter() {
+            self.insert_themes(family.themes);
+        }
+    }
+
+    fn insert_themes(&mut self, themes: impl IntoIterator<Item = ThemeVariant>) {
         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<Item = ThemeMetadata> + '_ {
-        self.themes.values().map(|theme| theme.metadata.clone())
+    pub fn list(&self, _staff: bool) -> impl Iterator<Item = SharedString> + '_ {
+        self.themes.values().map(|theme| theme.name.clone())
     }
 
-    pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
+    pub fn get(&self, name: &str) -> Result<Arc<ThemeVariant>> {
         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
     }

crates/theme2/src/scale.rs 🔗

@@ -1,98 +1,95 @@
-use gpui2::{AppContext, Hsla};
-use indexmap::IndexMap;
-
-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,
-}
+use gpui2::{AppContext, Hsla, SharedString};
 
-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<ColorScaleName, ColorScaleSet>;
+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<Self::Item>;
+
+    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<SharedString>,
         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),
         }

crates/theme2/src/settings.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{Theme, ThemeRegistry};
+use crate::{ThemeRegistry, ThemeVariant};
 use anyhow::Result;
 use gpui2::{px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels};
 use schemars::{
@@ -17,10 +17,11 @@ 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,
-    pub active_theme: Arc<Theme>,
+    pub active_theme: Arc<ThemeVariant>,
 }
 
 #[derive(Default)]
@@ -28,6 +29,8 @@ pub struct AdjustedBufferFontSize(Option<Pixels>);
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 pub struct ThemeSettingsContent {
+    #[serde(default)]
+    pub ui_font_size: Option<f32>,
     #[serde(default)]
     pub buffer_font_family: Option<String>,
     #[serde(default)]
@@ -115,6 +118,7 @@ impl settings2::Settings for ThemeSettings {
         let themes = cx.default_global::<Arc<ThemeRegistry>>();
 
         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,7 +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(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() {
@@ -140,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),

crates/theme2/src/syntax.rs 🔗

@@ -0,0 +1,37 @@
+use gpui2::{HighlightStyle, Hsla};
+
+#[derive(Clone, Default)]
+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<Item = (&'static str, Hsla)>) -> 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()
+    }
+}

crates/theme2/src/theme2.rs 🔗

@@ -1,17 +1,21 @@
-mod default;
+mod colors;
+mod default_colors;
+mod default_theme;
 mod registry;
 mod scale;
 mod settings;
-mod themes;
+mod syntax;
 
-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 gpui2::{AppContext, HighlightStyle, Hsla, SharedString};
+use gpui2::{AppContext, Hsla, SharedString};
 use settings2::Settings;
-use std::sync::Arc;
 
 #[derive(Debug, Clone, PartialEq)]
 pub enum Appearance {
@@ -24,132 +28,51 @@ pub fn init(cx: &mut AppContext) {
     ThemeSettings::register(cx);
 }
 
-pub fn active_theme<'a>(cx: &'a AppContext) -> &'a Arc<Theme> {
-    &ThemeSettings::get_global(cx).active_theme
+pub trait ActiveTheme {
+    fn theme(&self) -> &ThemeVariant;
 }
 
-pub fn theme(cx: &AppContext) -> Arc<Theme> {
-    active_theme(cx).clone()
+impl ActiveTheme for AppContext {
+    fn theme(&self) -> &ThemeVariant {
+        &ThemeSettings::get_global(self).active_theme
+    }
 }
 
-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],
+pub struct ThemeFamily {
+    #[allow(dead_code)]
+    pub(crate) id: String,
+    pub name: SharedString,
+    pub author: SharedString,
+    pub themes: Vec<ThemeVariant>,
+    pub scales: ColorScales,
 }
 
-#[derive(Clone)]
-pub struct SyntaxTheme {
-    pub highlights: Vec<(String, HighlightStyle)>,
+impl ThemeFamily {}
+
+pub struct ThemeVariant {
+    #[allow(dead_code)]
+    pub(crate) id: String,
+    pub name: SharedString,
+    pub appearance: Appearance,
+    pub styles: ThemeStyles,
 }
 
-impl SyntaxTheme {
-    // TOOD: Get this working with `#[cfg(test)]`. Why isn't it?
-    pub fn new_test(colors: impl IntoIterator<Item = (&'static str, Hsla)>) -> Self {
-        SyntaxTheme {
-            highlights: colors
-                .into_iter()
-                .map(|(key, color)| {
-                    (
-                        key.to_owned(),
-                        HighlightStyle {
-                            color: Some(color),
-                            ..Default::default()
-                        },
-                    )
-                })
-                .collect(),
-        }
+impl ThemeVariant {
+    /// Returns the [`ThemeColors`] for the theme.
+    #[inline(always)]
+    pub fn colors(&self) -> &ThemeColors {
+        &self.styles.colors
     }
 
-    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()
+    /// Returns the [`SyntaxTheme`] for the theme.
+    #[inline(always)]
+    pub fn syntax(&self) -> &SyntaxTheme {
+        &self.styles.syntax
     }
 
-    pub fn color(&self, name: &str) -> Hsla {
-        self.get(name).color.unwrap_or_default()
+    /// 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)
     }
 }
-
-#[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<SyntaxTheme>,
-}

crates/theme2/src/themes/andromeda.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_cave_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_cave_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_dune_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_dune_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_estuary_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_estuary_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_forest_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_forest_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_heath_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_heath_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_lakeside_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_lakeside_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_plateau_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_plateau_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_savanna_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_savanna_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_seaside_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_seaside_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_sulphurpool_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/atelier_sulphurpool_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/ayu_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/ayu_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/ayu_mirage.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/gruvbox_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/gruvbox_dark_hard.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/gruvbox_dark_soft.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/gruvbox_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/gruvbox_light_hard.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/gruvbox_light_soft.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/mod.rs 🔗

@@ -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::*;

crates/theme2/src/themes/one_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/one_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/rose_pine.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/rose_pine_dawn.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/rose_pine_moon.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/sandcastle.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/solarized_dark.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/solarized_light.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme2/src/themes/summercamp.rs 🔗

@@ -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(),
-            },
-        ],
-    }
-}

crates/theme_converter/Cargo.toml 🔗

@@ -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" }

crates/theme_converter/src/main.rs 🔗

@@ -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::<Vec<_>>()
-            .join("\n"),
-        use_statements = theme_modules
-            .iter()
-            .map(|module| format!("pub use {module}::*;"))
-            .collect::<Vec<_>>()
-            .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<Cow<[u8]>> {
-        Self::get(path)
-            .map(|f| f.data)
-            .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
-    }
-
-    fn list(&self, path: &str) -> Result<Vec<SharedString>> {
-        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::<Hsla>(0xff00ff),
-                selection: rgb::<Hsla>(0xff00ff),
-            }
-        }
-    }
-}
-
-impl From<PlayerThemeColors> for PlayerTheme {
-    fn from(value: PlayerThemeColors) -> Self {
-        Self {
-            cursor: value.cursor,
-            selection: value.selection,
-        }
-    }
-}
-
-fn convert_theme(json_theme: JsonTheme, legacy_theme: LegacyTheme) -> Result<theme2::Theme> {
-    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::<Hsla>(0xEC695E),
-        mac_os_traffic_light_yellow: rgb::<Hsla>(0xF4BF4F),
-        mac_os_traffic_light_green: rgb::<Hsla>(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<String, JsonSyntaxStyle>,
-}
-
-#[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<PlayerColors>,
-    #[serde(deserialize_with = "deserialize_syntax_colors")]
-    pub syntax: HashMap<String, Hsla>,
-}
-
-#[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<u8>,
-}
-
-fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
-where
-    D: Deserializer<'de>,
-{
-    struct PlayerArrayVisitor;
-
-    impl<'de> Visitor<'de> for PlayerArrayVisitor {
-        type Value = Vec<PlayerColors>;
-
-        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
-            formatter.write_str("an object with integer keys")
-        }
-
-        fn visit_map<A: serde::de::MapAccess<'de>>(
-            self,
-            mut map: A,
-        ) -> Result<Self::Value, A::Error> {
-            let mut players = Vec::with_capacity(8);
-            while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
-                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<HashMap<String, Hsla>, D::Error>
-where
-    D: serde::Deserializer<'de>,
-{
-    #[derive(Deserialize)]
-    struct ColorWrapper {
-        color: Hsla,
-    }
-
-    struct SyntaxVisitor;
-
-    impl<'de> Visitor<'de> for SyntaxVisitor {
-        type Value = HashMap<String, Hsla>;
-
-        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<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, 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)
-}

crates/theme_converter/src/theme_printer.rs 🔗

@@ -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<T>);
-
-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()
-    }
-}

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" }

crates/ui2/src/components/breadcrumb.rs 🔗

@@ -19,24 +19,22 @@ impl Breadcrumb {
     }
 
     fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
-        let theme = theme(cx);
-
-        div().child(" › ").text_color(theme.text_muted)
+        div()
+            .child(" › ")
+            .text_color(cx.theme().colors().text_muted)
     }
 
     fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> 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"),
                             },
                         ]),
                     ],

crates/ui2/src/components/buffer.rs 🔗

@@ -155,18 +155,16 @@ impl Buffer {
     }
 
     fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Component<V> {
-        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<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> 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)),
                 )
         }
     }

crates/ui2/src/components/buffer_search.rs 🔗

@@ -30,9 +30,7 @@ impl Render for BufferSearch {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        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::<Self>::new("replace", Icon::Replace)
                     .when(self.is_replace_open, |this| this.color(IconColor::Accent))

crates/ui2/src/components/collab_panel.rs 🔗

@@ -15,27 +15,29 @@ impl CollabPanel {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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..."),
                     ),
             )

crates/ui2/src/components/context_menu.rs 🔗

@@ -44,13 +44,11 @@ impl ContextMenu {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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

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};
@@ -66,8 +66,6 @@ impl<V: 'static> IconButton<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let theme = theme(cx);
-
         let icon_color = match (self.state, self.color) {
             (InteractionState::Disabled, _) => IconColor::Disabled,
             _ => self.color,
@@ -75,14 +73,14 @@ impl<V: 'static> IconButton<V> {
 
         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,
             ),
         };
 
@@ -90,8 +88,8 @@ impl<V: 'static> IconButton<V> {
             .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))

crates/ui2/src/components/keybinding.rs 🔗

@@ -60,15 +60,13 @@ impl Key {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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())
     }
 }

crates/ui2/src/components/list.rs 🔗

@@ -89,8 +89,6 @@ impl ListHeader {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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<V: 'static>(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         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<V: 'static> ListDetailsEntry<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let theme = theme(cx);
-
-        div().h_px().w_full().bg(theme.border)
+        div().h_px().w_full().bg(cx.theme().colors().border)
     }
 }
 

crates/ui2/src/components/modal.rs 🔗

@@ -39,22 +39,20 @@ impl<V: 'static> Modal<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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<V: 'static> Modal<V> {
                     this.child(
                         h_stack()
                             .border_t()
-                            .border_color(theme.border)
+                            .border_color(cx.theme().colors().border)
                             .p_1()
                             .justify_end()
                             .children(self.secondary_action)

crates/ui2/src/components/multi_buffer.rs 🔗

@@ -12,8 +12,6 @@ impl MultiBuffer {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> 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),
                 ]))
         }
     }

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<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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()))
     }
 }

crates/ui2/src/components/notifications_panel.rs 🔗

@@ -12,15 +12,13 @@ impl NotificationsPanel {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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")

crates/ui2/src/components/palette.rs 🔗

@@ -43,22 +43,20 @@ impl Palette {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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)
                             })),
                     ),

crates/ui2/src/components/panel.rs 🔗

@@ -93,8 +93,6 @@ impl<V: 'static> Panel<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let theme = theme(cx);
-
         let current_size = self.width.unwrap_or(self.initial_width);
 
         v_stack()
@@ -111,8 +109,8 @@ impl<V: 'static> Panel<V> {
             .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)
     }
 }

crates/ui2/src/components/panes.rs 🔗

@@ -90,8 +90,6 @@ impl<V: 'static> PaneGroup<V> {
     }
 
     fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let theme = theme(cx);
-
         if !self.panes.is_empty() {
             let el = div()
                 .flex()
@@ -115,7 +113,7 @@ impl<V: 'static> PaneGroup<V> {
                 .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 {

crates/ui2/src/components/player_stack.rs 🔗

@@ -14,9 +14,7 @@ impl PlayerStack {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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)
                     })

crates/ui2/src/components/project_panel.rs 🔗

@@ -14,15 +14,13 @@ impl ProjectPanel {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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")

crates/ui2/src/components/status_bar.rs 🔗

@@ -86,8 +86,6 @@ impl StatusBar {
         view: &mut Workspace,
         cx: &mut ViewContext<Workspace>,
     ) -> impl Component<Workspace> {
-        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))
     }

crates/ui2/src/components/tab.rs 🔗

@@ -87,7 +87,6 @@ impl Tab {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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,
             ),
         };
 

crates/ui2/src/components/tab_bar.rs 🔗

@@ -24,15 +24,13 @@ impl TabBar {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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()

crates/ui2/src/components/terminal.rs 🔗

@@ -12,8 +12,6 @@ impl Terminal {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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)),
             )
     }
 }

crates/ui2/src/components/title_bar.rs 🔗

@@ -89,7 +89,6 @@ impl Render for TitleBar {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        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()

crates/ui2/src/components/toast.rs 🔗

@@ -37,8 +37,6 @@ impl<V: 'static> Toast<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let theme = theme(cx);
-
         let mut div = div();
 
         if self.origin == ToastOrigin::Bottom {
@@ -56,7 +54,7 @@ impl<V: 'static> Toast<V> {
             .rounded_lg()
             .shadow_md()
             .overflow_hidden()
-            .bg(theme.elevated_surface)
+            .bg(cx.theme().colors().elevated_surface)
             .children(self.children)
     }
 }

crates/ui2/src/components/toolbar.rs 🔗

@@ -55,10 +55,8 @@ impl<V: 'static> Toolbar<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-            let theme = theme(cx);
-
             Story::container(cx)
                 .child(Story::title_for::<_, Toolbar<Self>>(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"),
                                     },
                                 ]),
                             ],

crates/ui2/src/components/traffic_lights.rs 🔗

@@ -22,13 +22,13 @@ impl TrafficLight {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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)

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>) {
         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,22 +193,6 @@ impl Render for Workspace {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
-        let theme = theme(cx);
-
-        // HACK: This should happen inside of `debug_toggle_user_settings`, but
-        // we don't have `cx.global::<FakeSettings>()` 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",
@@ -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)
@@ -323,7 +321,7 @@ impl Render for Workspace {
                 v_stack()
                     .z_index(9)
                     .absolute()
-                    .bottom_10()
+                    .top_20()
                     .left_1_4()
                     .w_40()
                     .gap_2()

crates/ui2/src/elements/avatar.rs 🔗

@@ -22,8 +22,6 @@ impl Avatar {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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())
     }
 }
 

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 {
@@ -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,
         }
     }
 }
@@ -157,7 +151,7 @@ impl<V: 'static> Button<V> {
             .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)))
@@ -204,7 +198,7 @@ impl<V: 'static> ButtonGroup<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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));

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<V: 'static> {
@@ -27,13 +28,11 @@ impl<V: 'static> Details<V> {
     }
 
     fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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))

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::*;
@@ -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(),
@@ -174,8 +175,8 @@ impl IconElement {
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
         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()

crates/ui2/src/elements/input.rs 🔗

@@ -57,18 +57,16 @@ impl Input {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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))

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::*;
@@ -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,
         }
     }
 }
@@ -88,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.))
             })
@@ -126,9 +124,7 @@ impl HighlightedLabel {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        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();
 

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<V: 'static>(&self, cx: &mut ViewContext<V>) -> Hsla {
-        let theme = theme(cx);
-        theme.players[self.index].cursor
+        cx.theme().styles.player.0[self.index].cursor
     }
 
     pub fn selection_color<V: 'static>(&self, cx: &mut ViewContext<V>) -> Hsla {
-        let theme = theme(cx);
-        theme.players[self.index].selection
+        cx.theme().styles.player.0[self.index].selection
     }
 
     pub fn avatar_src(&self) -> &str {

crates/ui2/src/elements/tool_divider.rs 🔗

@@ -9,8 +9,6 @@ impl ToolDivider {
     }
 
     fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-        let theme = theme(cx);
-
-        div().w_px().h_3().bg(theme.border)
+        div().w_px().h_3().bg(cx.theme().colors().border)
     }
 }

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::theme;
+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]
@@ -54,15 +45,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,
         }
     }
 }

crates/ui2/src/settings.rs 🔗

@@ -58,7 +58,6 @@ pub struct FakeSettings {
     pub list_disclosure_style: SettingValue<DisclosureControlStyle>,
     pub list_indent_depth: SettingValue<AbsoluteLength>,
     pub titlebar: TitlebarSettings,
-    pub ui_scale: SettingValue<f32>,
 }
 
 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.),
         }
     }
 }

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>) -> 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<EditorPane>) -> 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<BufferRow> {
+pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
     let show_line_number = true;
 
     vec![
@@ -686,15 +684,15 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                 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<BufferRow> {
                 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<BufferRow> {
             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<BufferRow> {
                 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<BufferRow> {
             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<BufferRow> {
 }
 
 pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext<EditorPane>) -> 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<EditorPa
         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_with_status_example(&theme),
+        hello_world_rust_buffer_with_status_example(cx),
     )
 }
 
-pub fn hello_world_rust_buffer_with_status_example(theme: &Theme) -> 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<BufferRow> {
+pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
     let show_line_number = true;
 
     vec![
@@ -825,15 +821,15 @@ pub fn hello_world_rust_with_status_buffer_rows(theme: &Theme) -> Vec<BufferRow>
                 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<BufferRow>
                 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<BufferRow>
             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<BufferRow>
                 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<BufferRow>
             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<BufferRow>
             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<BufferRow>
             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<BufferRow>
     ]
 }
 
-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<BufferRow> {
+pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
     let show_line_number = false;
 
     vec![
@@ -969,31 +965,31 @@ pub fn terminal_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
                 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<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "λ ".to_string(),
-                    color: theme.syntax.color("string"),
+                    color: cx.theme().syntax_color("string"),
                 }],
             }),
             cursors: None,

crates/ui2/src/story.rs 🔗

@@ -6,8 +6,6 @@ pub struct Story {}
 
 impl Story {
     pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
-        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<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
-        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<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
-        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())
     }
 }

crates/zed/Cargo.toml 🔗

@@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
 description = "The fast, collaborative code editor."
 edition = "2021"
 name = "zed"
-version = "0.111.0"
+version = "0.112.0"
 publish = false
 
 [lib]