Merge branch 'zed2' into zed2-workspace

Mikayla created

Change summary

Cargo.lock                                            |   42 
Cargo.toml                                            |    1 
crates/Cargo.toml                                     |   38 
crates/ai/Cargo.toml                                  |    3 
crates/ai/src/ai.rs                                   |    6 
crates/ai/src/auth.rs                                 |   15 
crates/ai/src/completion.rs                           |  215 -
crates/ai/src/embedding.rs                            |  322 -
crates/ai/src/models.rs                               |   70 
crates/ai/src/prompts/base.rs                         |   56 
crates/ai/src/prompts/file_context.rs                 |   16 
crates/ai/src/prompts/generate.rs                     |    8 
crates/ai/src/prompts/mod.rs                          |    0 
crates/ai/src/prompts/preamble.rs                     |    2 
crates/ai/src/prompts/repository_context.rs           |    2 
crates/ai/src/providers/mod.rs                        |    1 
crates/ai/src/providers/open_ai/completion.rs         |  298 +
crates/ai/src/providers/open_ai/embedding.rs          |  306 +
crates/ai/src/providers/open_ai/mod.rs                |    9 
crates/ai/src/providers/open_ai/model.rs              |   57 
crates/ai/src/providers/open_ai/new.rs                |   11 
crates/ai/src/test.rs                                 |  191 +
crates/ai2/Cargo.toml                                 |   38 
crates/ai2/src/ai2.rs                                 |    8 
crates/ai2/src/auth.rs                                |   17 
crates/ai2/src/completion.rs                          |   23 
crates/ai2/src/embedding.rs                           |  123 
crates/ai2/src/models.rs                              |   16 
crates/ai2/src/prompts/base.rs                        |  330 ++
crates/ai2/src/prompts/file_context.rs                |  164 +
crates/ai2/src/prompts/generate.rs                    |   99 
crates/ai2/src/prompts/mod.rs                         |    5 
crates/ai2/src/prompts/preamble.rs                    |   52 
crates/ai2/src/prompts/repository_context.rs          |   98 
crates/ai2/src/providers/mod.rs                       |    1 
crates/ai2/src/providers/open_ai/completion.rs        |  306 +
crates/ai2/src/providers/open_ai/embedding.rs         |  313 +
crates/ai2/src/providers/open_ai/mod.rs               |    9 
crates/ai2/src/providers/open_ai/model.rs             |   57 
crates/ai2/src/providers/open_ai/new.rs               |   11 
crates/ai2/src/test.rs                                |  193 +
crates/assistant/Cargo.toml                           |    1 
crates/assistant/src/assistant.rs                     |    2 
crates/assistant/src/assistant_panel.rs               |  280 
crates/assistant/src/codegen.rs                       |   80 
crates/assistant/src/prompts.rs                       |   13 
crates/call2/Cargo.toml                               |    4 
crates/call2/src/call2.rs                             |   46 
crates/call2/src/participant.rs                       |    4 
crates/call2/src/room.rs                              |   46 
crates/client2/src/client2.rs                         |   42 
crates/client2/src/telemetry.rs                       |   19 
crates/client2/src/test.rs                            |    6 
crates/client2/src/user.rs                            |   10 
crates/copilot2/src/copilot2.rs                       |   32 
crates/editor/src/editor.rs                           |    5 
crates/gpui2/src/app.rs                               |   54 
crates/gpui2/src/app/async_context.rs                 |   42 
crates/gpui2/src/app/entity_map.rs                    |  212 
crates/gpui2/src/app/model_context.rs                 |   42 
crates/gpui2/src/app/test_context.rs                  |   16 
crates/gpui2/src/element.rs                           |    2 
crates/gpui2/src/gpui2.rs                             |   69 
crates/gpui2/src/interactive.rs                       |   69 
crates/gpui2/src/view.rs                              |  124 
crates/gpui2/src/window.rs                            |  113 
crates/language2/src/buffer_tests.rs                  |   73 
crates/menu2/Cargo.toml                               |   12 
crates/menu2/src/menu2.rs                             |   25 
crates/prettier2/Cargo.toml                           |    4 
crates/prettier2/src/prettier2.rs                     |    6 
crates/project2/Cargo.toml                            |    4 
crates/project2/src/lsp_command.rs                    |  158 
crates/project2/src/project2.rs                       |  244 
crates/project2/src/terminals.rs                      |   12 
crates/project2/src/worktree.rs                       |   28 
crates/semantic_index/Cargo.toml                      |    1 
crates/semantic_index/src/embedding_queue.rs          |   15 
crates/semantic_index/src/parsing.rs                  |   33 
crates/semantic_index/src/semantic_index.rs           |   55 
crates/semantic_index/src/semantic_index_tests.rs     |   93 
crates/storybook2/src/stories.rs                      |    2 
crates/storybook2/src/stories/colors.rs               |   38 
crates/storybook2/src/stories/focus.rs                |  163 
crates/storybook2/src/stories/kitchen_sink.rs         |   27 
crates/storybook2/src/stories/scroll.rs               |   92 
crates/storybook2/src/stories/text.rs                 |   31 
crates/storybook2/src/stories/z_index.rs              |    9 
crates/storybook2/src/story_selector.rs               |  106 
crates/storybook2/src/storybook2.rs                   |   38 
crates/storybook2/src/themes.rs                       |   30 
crates/storybook2/src/themes/rose_pine.rs             | 1686 ----------
crates/theme2/src/default.rs                          | 2118 +++++++++++++
crates/theme2/src/registry.rs                         |   49 
crates/theme2/src/scale.rs                            |  164 +
crates/theme2/src/theme2.rs                           |   14 
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                       |   72 
crates/theme2/src/themes/one_dark.rs                  |   62 
crates/theme2/src/themes/one_light.rs                 |  131 
crates/theme2/src/themes/rose_pine.rs                 |  322 -
crates/theme2/src/themes/rose_pine_dawn.rs            |  132 
crates/theme2/src/themes/rose_pine_moon.rs            |  132 
crates/theme2/src/themes/sandcastle.rs                |   52 
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                     |    1 
crates/theme_converter/src/main.rs                    |  249 -
crates/theme_converter/src/theme_printer.rs           |  174 +
crates/ui2/src/components/assistant_panel.rs          |   15 
crates/ui2/src/components/breadcrumb.rs               |   18 
crates/ui2/src/components/buffer.rs                   |   13 
crates/ui2/src/components/buffer_search.rs            |   16 
crates/ui2/src/components/chat_panel.rs               |    8 
crates/ui2/src/components/collab_panel.rs             |   11 
crates/ui2/src/components/command_palette.rs          |    9 
crates/ui2/src/components/context_menu.rs             |   11 
crates/ui2/src/components/copilot.rs                  |    9 
crates/ui2/src/components/editor_pane.rs              |   18 
crates/ui2/src/components/facepile.rs                 |   11 
crates/ui2/src/components/keybinding.rs               |   14 
crates/ui2/src/components/language_selector.rs        |   11 
crates/ui2/src/components/multi_buffer.rs             |   11 
crates/ui2/src/components/notifications_panel.rs      |   11 
crates/ui2/src/components/palette.rs                  |  101 
crates/ui2/src/components/panel.rs                    |   13 
crates/ui2/src/components/panes.rs                    |   13 
crates/ui2/src/components/project_panel.rs            |   11 
crates/ui2/src/components/recent_projects.rs          |   11 
crates/ui2/src/components/tab.rs                      |   31 
crates/ui2/src/components/tab_bar.rs                  |   11 
crates/ui2/src/components/terminal.rs                 |   12 
crates/ui2/src/components/theme_selector.rs           |    9 
crates/ui2/src/components/title_bar.rs                |   39 
crates/ui2/src/components/toast.rs                    |   11 
crates/ui2/src/components/toolbar.rs                  |   11 
crates/ui2/src/components/traffic_lights.rs           |    9 
crates/ui2/src/components/workspace.rs                |   56 
crates/ui2/src/elements/avatar.rs                     |   11 
crates/ui2/src/elements/button.rs                     |   17 
crates/ui2/src/elements/details.rs                    |   13 
crates/ui2/src/elements/icon.rs                       |   29 
crates/ui2/src/elements/input.rs                      |   11 
crates/ui2/src/elements/label.rs                      |   25 
crates/ui2/src/lib.rs                                 |    2 
crates/ui2/src/prelude.rs                             |    3 
crates/ui2/src/static_data.rs                         |    8 
crates/ui2/src/theme.rs                               |  225 -
crates/workspace2/src/item.rs                         |   54 
crates/zed/examples/semantic_index_eval.rs            |    4 
crates/zed/src/languages/elixir.rs                    |    4 
crates/zed2/Cargo.toml                                |    1 
crates/zed2/src/main.rs                               |    2 
crates/zed2/src/zed2.rs                               |    4 
script/zed-2-progress-report.py                       |   27 
185 files changed, 12,131 insertions(+), 4,771 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -108,6 +108,33 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "ai2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "bincode",
+ "futures 0.3.28",
+ "gpui2",
+ "isahc",
+ "language2",
+ "lazy_static",
+ "log",
+ "matrixmultiply",
+ "ordered-float 2.10.0",
+ "parking_lot 0.11.2",
+ "parse_duration",
+ "postage",
+ "rand 0.8.5",
+ "regex",
+ "rusqlite",
+ "serde",
+ "serde_json",
+ "tiktoken-rs",
+ "util",
+]
+
 [[package]]
 name = "alacritty_config"
 version = "0.1.2-dev"
@@ -1138,7 +1165,7 @@ dependencies = [
  "audio2",
  "client2",
  "collections",
- "fs",
+ "fs2",
  "futures 0.3.28",
  "gpui2",
  "language2",
@@ -4795,6 +4822,13 @@ dependencies = [
  "gpui",
 ]
 
+[[package]]
+name = "menu2"
+version = "0.1.0"
+dependencies = [
+ "gpui2",
+]
+
 [[package]]
 name = "metal"
 version = "0.21.0"
@@ -6000,7 +6034,7 @@ dependencies = [
  "anyhow",
  "client2",
  "collections",
- "fs",
+ "fs2",
  "futures 0.3.28",
  "gpui2",
  "language2",
@@ -6167,7 +6201,7 @@ dependencies = [
  "ctor",
  "db2",
  "env_logger 0.9.3",
- "fs",
+ "fs2",
  "fsevent",
  "futures 0.3.28",
  "fuzzy2",
@@ -8740,6 +8774,7 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "clap 4.4.4",
+ "convert_case 0.6.0",
  "gpui2",
  "log",
  "rust-embed",
@@ -10932,6 +10967,7 @@ dependencies = [
 name = "zed2"
 version = "0.109.0"
 dependencies = [
+ "ai2",
  "anyhow",
  "async-compression",
  "async-recursion 0.3.2",

Cargo.toml 🔗

@@ -59,6 +59,7 @@ members = [
     "crates/lsp2",
     "crates/media",
     "crates/menu",
+    "crates/menu2",
     "crates/multi_buffer",
     "crates/node_runtime",
     "crates/notifications",

crates/Cargo.toml 🔗

@@ -0,0 +1,38 @@
+[package]
+name = "ai"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/ai.rs"
+doctest = false
+
+[features]
+test-support = []
+
+[dependencies]
+gpui = { path = "../gpui" }
+util = { path = "../util" }
+language = { path = "../language" }
+async-trait.workspace = true
+anyhow.workspace = true
+futures.workspace = true
+lazy_static.workspace = true
+ordered-float.workspace = true
+parking_lot.workspace = true
+isahc.workspace = true
+regex.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+postage.workspace = true
+rand.workspace = true
+log.workspace = true
+parse_duration = "2.1.1"
+tiktoken-rs = "0.5.0"
+matrixmultiply = "0.3.7"
+rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
+bincode = "1.3.3"
+
+[dev-dependencies]
+gpui = { path = "../gpui", features = ["test-support"] }

crates/ai/Cargo.toml 🔗

@@ -8,6 +8,9 @@ publish = false
 path = "src/ai.rs"
 doctest = false
 
+[features]
+test-support = []
+
 [dependencies]
 gpui = { path = "../gpui" }
 util = { path = "../util" }

crates/ai/src/ai.rs 🔗

@@ -1,4 +1,8 @@
+pub mod auth;
 pub mod completion;
 pub mod embedding;
 pub mod models;
-pub mod templates;
+pub mod prompts;
+pub mod providers;
+#[cfg(any(test, feature = "test-support"))]
+pub mod test;

crates/ai/src/auth.rs 🔗

@@ -0,0 +1,15 @@
+use gpui::AppContext;
+
+#[derive(Clone, Debug)]
+pub enum ProviderCredential {
+    Credentials { api_key: String },
+    NoCredentials,
+    NotNeeded,
+}
+
+pub trait CredentialProvider: Send + Sync {
+    fn has_credentials(&self) -> bool;
+    fn retrieve_credentials(&self, cx: &AppContext) -> ProviderCredential;
+    fn save_credentials(&self, cx: &AppContext, credential: ProviderCredential);
+    fn delete_credentials(&self, cx: &AppContext);
+}

crates/ai/src/completion.rs 🔗

@@ -1,214 +1,23 @@
-use anyhow::{anyhow, Result};
-use futures::{
-    future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt,
-    Stream, StreamExt,
-};
-use gpui::executor::Background;
-use isahc::{http::StatusCode, Request, RequestExt};
-use serde::{Deserialize, Serialize};
-use std::{
-    fmt::{self, Display},
-    io,
-    sync::Arc,
-};
+use anyhow::Result;
+use futures::{future::BoxFuture, stream::BoxStream};
 
-pub const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";
+use crate::{auth::CredentialProvider, models::LanguageModel};
 
-#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
-#[serde(rename_all = "lowercase")]
-pub enum Role {
-    User,
-    Assistant,
-    System,
+pub trait CompletionRequest: Send + Sync {
+    fn data(&self) -> serde_json::Result<String>;
 }
 
-impl Role {
-    pub fn cycle(&mut self) {
-        *self = match self {
-            Role::User => Role::Assistant,
-            Role::Assistant => Role::System,
-            Role::System => Role::User,
-        }
-    }
-}
-
-impl Display for Role {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            Role::User => write!(f, "User"),
-            Role::Assistant => write!(f, "Assistant"),
-            Role::System => write!(f, "System"),
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
-pub struct RequestMessage {
-    pub role: Role,
-    pub content: String,
-}
-
-#[derive(Debug, Default, Serialize)]
-pub struct OpenAIRequest {
-    pub model: String,
-    pub messages: Vec<RequestMessage>,
-    pub stream: bool,
-    pub stop: Vec<String>,
-    pub temperature: f32,
-}
-
-#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
-pub struct ResponseMessage {
-    pub role: Option<Role>,
-    pub content: Option<String>,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct OpenAIUsage {
-    pub prompt_tokens: u32,
-    pub completion_tokens: u32,
-    pub total_tokens: u32,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct ChatChoiceDelta {
-    pub index: u32,
-    pub delta: ResponseMessage,
-    pub finish_reason: Option<String>,
-}
-
-#[derive(Deserialize, Debug)]
-pub struct OpenAIResponseStreamEvent {
-    pub id: Option<String>,
-    pub object: String,
-    pub created: u32,
-    pub model: String,
-    pub choices: Vec<ChatChoiceDelta>,
-    pub usage: Option<OpenAIUsage>,
-}
-
-pub async fn stream_completion(
-    api_key: String,
-    executor: Arc<Background>,
-    mut request: OpenAIRequest,
-) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
-    request.stream = true;
-
-    let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
-
-    let json_data = serde_json::to_string(&request)?;
-    let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
-        .header("Content-Type", "application/json")
-        .header("Authorization", format!("Bearer {}", api_key))
-        .body(json_data)?
-        .send_async()
-        .await?;
-
-    let status = response.status();
-    if status == StatusCode::OK {
-        executor
-            .spawn(async move {
-                let mut lines = BufReader::new(response.body_mut()).lines();
-
-                fn parse_line(
-                    line: Result<String, io::Error>,
-                ) -> Result<Option<OpenAIResponseStreamEvent>> {
-                    if let Some(data) = line?.strip_prefix("data: ") {
-                        let event = serde_json::from_str(&data)?;
-                        Ok(Some(event))
-                    } else {
-                        Ok(None)
-                    }
-                }
-
-                while let Some(line) = lines.next().await {
-                    if let Some(event) = parse_line(line).transpose() {
-                        let done = event.as_ref().map_or(false, |event| {
-                            event
-                                .choices
-                                .last()
-                                .map_or(false, |choice| choice.finish_reason.is_some())
-                        });
-                        if tx.unbounded_send(event).is_err() {
-                            break;
-                        }
-
-                        if done {
-                            break;
-                        }
-                    }
-                }
-
-                anyhow::Ok(())
-            })
-            .detach();
-
-        Ok(rx)
-    } else {
-        let mut body = String::new();
-        response.body_mut().read_to_string(&mut body).await?;
-
-        #[derive(Deserialize)]
-        struct OpenAIResponse {
-            error: OpenAIError,
-        }
-
-        #[derive(Deserialize)]
-        struct OpenAIError {
-            message: String,
-        }
-
-        match serde_json::from_str::<OpenAIResponse>(&body) {
-            Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
-                "Failed to connect to OpenAI API: {}",
-                response.error.message,
-            )),
-
-            _ => Err(anyhow!(
-                "Failed to connect to OpenAI API: {} {}",
-                response.status(),
-                body,
-            )),
-        }
-    }
-}
-
-pub trait CompletionProvider {
+pub trait CompletionProvider: CredentialProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel>;
     fn complete(
         &self,
-        prompt: OpenAIRequest,
+        prompt: Box<dyn CompletionRequest>,
     ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
+    fn box_clone(&self) -> Box<dyn CompletionProvider>;
 }
 
-pub struct OpenAICompletionProvider {
-    api_key: String,
-    executor: Arc<Background>,
-}
-
-impl OpenAICompletionProvider {
-    pub fn new(api_key: String, executor: Arc<Background>) -> Self {
-        Self { api_key, executor }
-    }
-}
-
-impl CompletionProvider for OpenAICompletionProvider {
-    fn complete(
-        &self,
-        prompt: OpenAIRequest,
-    ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
-        let request = stream_completion(self.api_key.clone(), self.executor.clone(), prompt);
-        async move {
-            let response = request.await?;
-            let stream = response
-                .filter_map(|response| async move {
-                    match response {
-                        Ok(mut response) => Some(Ok(response.choices.pop()?.delta.content?)),
-                        Err(error) => Some(Err(error)),
-                    }
-                })
-                .boxed();
-            Ok(stream)
-        }
-        .boxed()
+impl Clone for Box<dyn CompletionProvider> {
+    fn clone(&self) -> Box<dyn CompletionProvider> {
+        self.box_clone()
     }
 }

crates/ai/src/embedding.rs 🔗

@@ -1,32 +1,13 @@
-use anyhow::{anyhow, Result};
+use std::time::Instant;
+
+use anyhow::Result;
 use async_trait::async_trait;
-use futures::AsyncReadExt;
-use gpui::executor::Background;
-use gpui::{serde_json, AppContext};
-use isahc::http::StatusCode;
-use isahc::prelude::Configurable;
-use isahc::{AsyncBody, Response};
-use lazy_static::lazy_static;
 use ordered_float::OrderedFloat;
-use parking_lot::Mutex;
-use parse_duration::parse;
-use postage::watch;
 use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef};
 use rusqlite::ToSql;
-use serde::{Deserialize, Serialize};
-use std::env;
-use std::ops::Add;
-use std::sync::Arc;
-use std::time::{Duration, Instant};
-use tiktoken_rs::{cl100k_base, CoreBPE};
-use util::http::{HttpClient, Request};
-use util::ResultExt;
-
-use crate::completion::OPENAI_API_URL;
 
-lazy_static! {
-    static ref OPENAI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
-}
+use crate::auth::CredentialProvider;
+use crate::models::LanguageModel;
 
 #[derive(Debug, PartialEq, Clone)]
 pub struct Embedding(pub Vec<f32>);
@@ -87,301 +68,14 @@ impl Embedding {
     }
 }
 
-#[derive(Clone)]
-pub struct OpenAIEmbeddings {
-    pub client: Arc<dyn HttpClient>,
-    pub executor: Arc<Background>,
-    rate_limit_count_rx: watch::Receiver<Option<Instant>>,
-    rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
-}
-
-#[derive(Serialize)]
-struct OpenAIEmbeddingRequest<'a> {
-    model: &'static str,
-    input: Vec<&'a str>,
-}
-
-#[derive(Deserialize)]
-struct OpenAIEmbeddingResponse {
-    data: Vec<OpenAIEmbedding>,
-    usage: OpenAIEmbeddingUsage,
-}
-
-#[derive(Debug, Deserialize)]
-struct OpenAIEmbedding {
-    embedding: Vec<f32>,
-    index: usize,
-    object: String,
-}
-
-#[derive(Deserialize)]
-struct OpenAIEmbeddingUsage {
-    prompt_tokens: usize,
-    total_tokens: usize,
-}
-
 #[async_trait]
-pub trait EmbeddingProvider: Sync + Send {
-    fn retrieve_credentials(&self, cx: &AppContext) -> Option<String>;
-    async fn embed_batch(
-        &self,
-        spans: Vec<String>,
-        api_key: Option<String>,
-    ) -> Result<Vec<Embedding>>;
+pub trait EmbeddingProvider: CredentialProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel>;
+    async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>>;
     fn max_tokens_per_batch(&self) -> usize;
-    fn truncate(&self, span: &str) -> (String, usize);
     fn rate_limit_expiration(&self) -> Option<Instant>;
 }
 
-pub struct DummyEmbeddings {}
-
-#[async_trait]
-impl EmbeddingProvider for DummyEmbeddings {
-    fn retrieve_credentials(&self, _cx: &AppContext) -> Option<String> {
-        Some("Dummy API KEY".to_string())
-    }
-    fn rate_limit_expiration(&self) -> Option<Instant> {
-        None
-    }
-    async fn embed_batch(
-        &self,
-        spans: Vec<String>,
-        _api_key: Option<String>,
-    ) -> Result<Vec<Embedding>> {
-        // 1024 is the OpenAI Embeddings size for ada models.
-        // the model we will likely be starting with.
-        let dummy_vec = Embedding::from(vec![0.32 as f32; 1536]);
-        return Ok(vec![dummy_vec; spans.len()]);
-    }
-
-    fn max_tokens_per_batch(&self) -> usize {
-        OPENAI_INPUT_LIMIT
-    }
-
-    fn truncate(&self, span: &str) -> (String, usize) {
-        let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span);
-        let token_count = tokens.len();
-        let output = if token_count > OPENAI_INPUT_LIMIT {
-            tokens.truncate(OPENAI_INPUT_LIMIT);
-            let new_input = OPENAI_BPE_TOKENIZER.decode(tokens.clone());
-            new_input.ok().unwrap_or_else(|| span.to_string())
-        } else {
-            span.to_string()
-        };
-
-        (output, tokens.len())
-    }
-}
-
-const OPENAI_INPUT_LIMIT: usize = 8190;
-
-impl OpenAIEmbeddings {
-    pub fn new(client: Arc<dyn HttpClient>, executor: Arc<Background>) -> Self {
-        let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
-        let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
-
-        OpenAIEmbeddings {
-            client,
-            executor,
-            rate_limit_count_rx,
-            rate_limit_count_tx,
-        }
-    }
-
-    fn resolve_rate_limit(&self) {
-        let reset_time = *self.rate_limit_count_tx.lock().borrow();
-
-        if let Some(reset_time) = reset_time {
-            if Instant::now() >= reset_time {
-                *self.rate_limit_count_tx.lock().borrow_mut() = None
-            }
-        }
-
-        log::trace!(
-            "resolving reset time: {:?}",
-            *self.rate_limit_count_tx.lock().borrow()
-        );
-    }
-
-    fn update_reset_time(&self, reset_time: Instant) {
-        let original_time = *self.rate_limit_count_tx.lock().borrow();
-
-        let updated_time = if let Some(original_time) = original_time {
-            if reset_time < original_time {
-                Some(reset_time)
-            } else {
-                Some(original_time)
-            }
-        } else {
-            Some(reset_time)
-        };
-
-        log::trace!("updating rate limit time: {:?}", updated_time);
-
-        *self.rate_limit_count_tx.lock().borrow_mut() = updated_time;
-    }
-    async fn send_request(
-        &self,
-        api_key: &str,
-        spans: Vec<&str>,
-        request_timeout: u64,
-    ) -> Result<Response<AsyncBody>> {
-        let request = Request::post("https://api.openai.com/v1/embeddings")
-            .redirect_policy(isahc::config::RedirectPolicy::Follow)
-            .timeout(Duration::from_secs(request_timeout))
-            .header("Content-Type", "application/json")
-            .header("Authorization", format!("Bearer {}", api_key))
-            .body(
-                serde_json::to_string(&OpenAIEmbeddingRequest {
-                    input: spans.clone(),
-                    model: "text-embedding-ada-002",
-                })
-                .unwrap()
-                .into(),
-            )?;
-
-        Ok(self.client.send(request).await?)
-    }
-}
-
-#[async_trait]
-impl EmbeddingProvider for OpenAIEmbeddings {
-    fn retrieve_credentials(&self, cx: &AppContext) -> Option<String> {
-        if let Ok(api_key) = env::var("OPENAI_API_KEY") {
-            Some(api_key)
-        } else if let Some((_, api_key)) = cx
-            .platform()
-            .read_credentials(OPENAI_API_URL)
-            .log_err()
-            .flatten()
-        {
-            String::from_utf8(api_key).log_err()
-        } else {
-            None
-        }
-    }
-
-    fn max_tokens_per_batch(&self) -> usize {
-        50000
-    }
-
-    fn rate_limit_expiration(&self) -> Option<Instant> {
-        *self.rate_limit_count_rx.borrow()
-    }
-    fn truncate(&self, span: &str) -> (String, usize) {
-        let mut tokens = OPENAI_BPE_TOKENIZER.encode_with_special_tokens(span);
-        let output = if tokens.len() > OPENAI_INPUT_LIMIT {
-            tokens.truncate(OPENAI_INPUT_LIMIT);
-            OPENAI_BPE_TOKENIZER
-                .decode(tokens.clone())
-                .ok()
-                .unwrap_or_else(|| span.to_string())
-        } else {
-            span.to_string()
-        };
-
-        (output, tokens.len())
-    }
-
-    async fn embed_batch(
-        &self,
-        spans: Vec<String>,
-        api_key: Option<String>,
-    ) -> Result<Vec<Embedding>> {
-        const BACKOFF_SECONDS: [usize; 4] = [3, 5, 15, 45];
-        const MAX_RETRIES: usize = 4;
-
-        let Some(api_key) = api_key else {
-            return Err(anyhow!("no open ai key provided"));
-        };
-
-        let mut request_number = 0;
-        let mut rate_limiting = false;
-        let mut request_timeout: u64 = 15;
-        let mut response: Response<AsyncBody>;
-        while request_number < MAX_RETRIES {
-            response = self
-                .send_request(
-                    &api_key,
-                    spans.iter().map(|x| &**x).collect(),
-                    request_timeout,
-                )
-                .await?;
-
-            request_number += 1;
-
-            match response.status() {
-                StatusCode::REQUEST_TIMEOUT => {
-                    request_timeout += 5;
-                }
-                StatusCode::OK => {
-                    let mut body = String::new();
-                    response.body_mut().read_to_string(&mut body).await?;
-                    let response: OpenAIEmbeddingResponse = serde_json::from_str(&body)?;
-
-                    log::trace!(
-                        "openai embedding completed. tokens: {:?}",
-                        response.usage.total_tokens
-                    );
-
-                    // If we complete a request successfully that was previously rate_limited
-                    // resolve the rate limit
-                    if rate_limiting {
-                        self.resolve_rate_limit()
-                    }
-
-                    return Ok(response
-                        .data
-                        .into_iter()
-                        .map(|embedding| Embedding::from(embedding.embedding))
-                        .collect());
-                }
-                StatusCode::TOO_MANY_REQUESTS => {
-                    rate_limiting = true;
-                    let mut body = String::new();
-                    response.body_mut().read_to_string(&mut body).await?;
-
-                    let delay_duration = {
-                        let delay = Duration::from_secs(BACKOFF_SECONDS[request_number - 1] as u64);
-                        if let Some(time_to_reset) =
-                            response.headers().get("x-ratelimit-reset-tokens")
-                        {
-                            if let Ok(time_str) = time_to_reset.to_str() {
-                                parse(time_str).unwrap_or(delay)
-                            } else {
-                                delay
-                            }
-                        } else {
-                            delay
-                        }
-                    };
-
-                    // If we've previously rate limited, increment the duration but not the count
-                    let reset_time = Instant::now().add(delay_duration);
-                    self.update_reset_time(reset_time);
-
-                    log::trace!(
-                        "openai rate limiting: waiting {:?} until lifted",
-                        &delay_duration
-                    );
-
-                    self.executor.timer(delay_duration).await;
-                }
-                _ => {
-                    let mut body = String::new();
-                    response.body_mut().read_to_string(&mut body).await?;
-                    return Err(anyhow!(
-                        "open ai bad request: {:?} {:?}",
-                        &response.status(),
-                        body
-                    ));
-                }
-            }
-        }
-        Err(anyhow!("openai max retries"))
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;

crates/ai/src/models.rs 🔗

@@ -1,66 +1,16 @@
-use anyhow::anyhow;
-use tiktoken_rs::CoreBPE;
-use util::ResultExt;
+pub enum TruncationDirection {
+    Start,
+    End,
+}
 
 pub trait LanguageModel {
     fn name(&self) -> String;
     fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
-    fn truncate(&self, content: &str, length: usize) -> anyhow::Result<String>;
-    fn truncate_start(&self, content: &str, length: usize) -> anyhow::Result<String>;
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String>;
     fn capacity(&self) -> anyhow::Result<usize>;
 }
-
-pub struct OpenAILanguageModel {
-    name: String,
-    bpe: Option<CoreBPE>,
-}
-
-impl OpenAILanguageModel {
-    pub fn load(model_name: &str) -> Self {
-        let bpe = tiktoken_rs::get_bpe_from_model(model_name).log_err();
-        OpenAILanguageModel {
-            name: model_name.to_string(),
-            bpe,
-        }
-    }
-}
-
-impl LanguageModel for OpenAILanguageModel {
-    fn name(&self) -> String {
-        self.name.clone()
-    }
-    fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
-        if let Some(bpe) = &self.bpe {
-            anyhow::Ok(bpe.encode_with_special_tokens(content).len())
-        } else {
-            Err(anyhow!("bpe for open ai model was not retrieved"))
-        }
-    }
-    fn truncate(&self, content: &str, length: usize) -> anyhow::Result<String> {
-        if let Some(bpe) = &self.bpe {
-            let tokens = bpe.encode_with_special_tokens(content);
-            if tokens.len() > length {
-                bpe.decode(tokens[..length].to_vec())
-            } else {
-                bpe.decode(tokens)
-            }
-        } else {
-            Err(anyhow!("bpe for open ai model was not retrieved"))
-        }
-    }
-    fn truncate_start(&self, content: &str, length: usize) -> anyhow::Result<String> {
-        if let Some(bpe) = &self.bpe {
-            let tokens = bpe.encode_with_special_tokens(content);
-            if tokens.len() > length {
-                bpe.decode(tokens[length..].to_vec())
-            } else {
-                bpe.decode(tokens)
-            }
-        } else {
-            Err(anyhow!("bpe for open ai model was not retrieved"))
-        }
-    }
-    fn capacity(&self) -> anyhow::Result<usize> {
-        anyhow::Ok(tiktoken_rs::model::get_context_size(&self.name))
-    }
-}

crates/ai/src/templates/base.rs → crates/ai/src/prompts/base.rs 🔗

@@ -6,7 +6,7 @@ use language::BufferSnapshot;
 use util::ResultExt;
 
 use crate::models::LanguageModel;
-use crate::templates::repository_context::PromptCodeSnippet;
+use crate::prompts::repository_context::PromptCodeSnippet;
 
 pub(crate) enum PromptFileType {
     Text,
@@ -125,6 +125,9 @@ impl PromptChain {
 
 #[cfg(test)]
 pub(crate) mod tests {
+    use crate::models::TruncationDirection;
+    use crate::test::FakeLanguageModel;
+
     use super::*;
 
     #[test]
@@ -141,7 +144,11 @@ pub(crate) mod tests {
                 let mut token_count = args.model.count_tokens(&content)?;
                 if let Some(max_token_length) = max_token_length {
                     if token_count > max_token_length {
-                        content = args.model.truncate(&content, max_token_length)?;
+                        content = args.model.truncate(
+                            &content,
+                            max_token_length,
+                            TruncationDirection::End,
+                        )?;
                         token_count = max_token_length;
                     }
                 }
@@ -162,7 +169,11 @@ pub(crate) mod tests {
                 let mut token_count = args.model.count_tokens(&content)?;
                 if let Some(max_token_length) = max_token_length {
                     if token_count > max_token_length {
-                        content = args.model.truncate(&content, max_token_length)?;
+                        content = args.model.truncate(
+                            &content,
+                            max_token_length,
+                            TruncationDirection::End,
+                        )?;
                         token_count = max_token_length;
                     }
                 }
@@ -171,38 +182,7 @@ pub(crate) mod tests {
             }
         }
 
-        #[derive(Clone)]
-        struct DummyLanguageModel {
-            capacity: usize,
-        }
-
-        impl LanguageModel for DummyLanguageModel {
-            fn name(&self) -> String {
-                "dummy".to_string()
-            }
-            fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
-                anyhow::Ok(content.chars().collect::<Vec<char>>().len())
-            }
-            fn truncate(&self, content: &str, length: usize) -> anyhow::Result<String> {
-                anyhow::Ok(
-                    content.chars().collect::<Vec<char>>()[..length]
-                        .into_iter()
-                        .collect::<String>(),
-                )
-            }
-            fn truncate_start(&self, content: &str, length: usize) -> anyhow::Result<String> {
-                anyhow::Ok(
-                    content.chars().collect::<Vec<char>>()[length..]
-                        .into_iter()
-                        .collect::<String>(),
-                )
-            }
-            fn capacity(&self) -> anyhow::Result<usize> {
-                anyhow::Ok(self.capacity)
-            }
-        }
-
-        let model: Arc<dyn LanguageModel> = Arc::new(DummyLanguageModel { capacity: 100 });
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity: 100 });
         let args = PromptArguments {
             model: model.clone(),
             language_name: None,
@@ -238,7 +218,7 @@ pub(crate) mod tests {
 
         // Testing with Truncation Off
         // Should ignore capacity and return all prompts
-        let model: Arc<dyn LanguageModel> = Arc::new(DummyLanguageModel { capacity: 20 });
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity: 20 });
         let args = PromptArguments {
             model: model.clone(),
             language_name: None,
@@ -275,7 +255,7 @@ pub(crate) mod tests {
         // Testing with Truncation Off
         // Should ignore capacity and return all prompts
         let capacity = 20;
-        let model: Arc<dyn LanguageModel> = Arc::new(DummyLanguageModel { capacity });
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity });
         let args = PromptArguments {
             model: model.clone(),
             language_name: None,
@@ -311,7 +291,7 @@ pub(crate) mod tests {
         // Change Ordering of Prompts Based on Priority
         let capacity = 120;
         let reserved_tokens = 10;
-        let model: Arc<dyn LanguageModel> = Arc::new(DummyLanguageModel { capacity });
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity });
         let args = PromptArguments {
             model: model.clone(),
             language_name: None,

crates/ai/src/templates/file_context.rs → crates/ai/src/prompts/file_context.rs 🔗

@@ -3,8 +3,9 @@ use language::BufferSnapshot;
 use language::ToOffset;
 
 use crate::models::LanguageModel;
-use crate::templates::base::PromptArguments;
-use crate::templates::base::PromptTemplate;
+use crate::models::TruncationDirection;
+use crate::prompts::base::PromptArguments;
+use crate::prompts::base::PromptTemplate;
 use std::fmt::Write;
 use std::ops::Range;
 use std::sync::Arc;
@@ -70,8 +71,9 @@ fn retrieve_context(
                     };
 
                 let truncated_start_window =
-                    model.truncate_start(&start_window, start_goal_tokens)?;
-                let truncated_end_window = model.truncate(&end_window, end_goal_tokens)?;
+                    model.truncate(&start_window, start_goal_tokens, TruncationDirection::Start)?;
+                let truncated_end_window =
+                    model.truncate(&end_window, end_goal_tokens, TruncationDirection::End)?;
                 writeln!(
                     prompt,
                     "{truncated_start_window}{selected_window}{truncated_end_window}"
@@ -89,7 +91,7 @@ fn retrieve_context(
             if let Some(max_token_count) = max_token_count {
                 if model.count_tokens(&prompt)? > max_token_count {
                     truncated = true;
-                    prompt = model.truncate(&prompt, max_token_count)?;
+                    prompt = model.truncate(&prompt, max_token_count, TruncationDirection::End)?;
                 }
             }
         }
@@ -148,7 +150,9 @@ impl PromptTemplate for FileContext {
 
             // Really dumb truncation strategy
             if let Some(max_tokens) = max_token_length {
-                prompt = args.model.truncate(&prompt, max_tokens)?;
+                prompt = args
+                    .model
+                    .truncate(&prompt, max_tokens, TruncationDirection::End)?;
             }
 
             let token_count = args.model.count_tokens(&prompt)?;

crates/ai/src/templates/generate.rs → crates/ai/src/prompts/generate.rs 🔗

@@ -1,4 +1,4 @@
-use crate::templates::base::{PromptArguments, PromptFileType, PromptTemplate};
+use crate::prompts::base::{PromptArguments, PromptFileType, PromptTemplate};
 use anyhow::anyhow;
 use std::fmt::Write;
 
@@ -85,7 +85,11 @@ impl PromptTemplate for GenerateInlineContent {
 
         // Really dumb truncation strategy
         if let Some(max_tokens) = max_token_length {
-            prompt = args.model.truncate(&prompt, max_tokens)?;
+            prompt = args.model.truncate(
+                &prompt,
+                max_tokens,
+                crate::models::TruncationDirection::End,
+            )?;
         }
 
         let token_count = args.model.count_tokens(&prompt)?;

crates/ai/src/templates/preamble.rs → crates/ai/src/prompts/preamble.rs 🔗

@@ -1,4 +1,4 @@
-use crate::templates::base::{PromptArguments, PromptFileType, PromptTemplate};
+use crate::prompts::base::{PromptArguments, PromptFileType, PromptTemplate};
 use std::fmt::Write;
 
 pub struct EngineerPreamble {}

crates/ai/src/templates/repository_context.rs → crates/ai/src/prompts/repository_context.rs 🔗

@@ -1,4 +1,4 @@
-use crate::templates::base::{PromptArguments, PromptTemplate};
+use crate::prompts::base::{PromptArguments, PromptTemplate};
 use std::fmt::Write;
 use std::{ops::Range, path::PathBuf};
 

crates/ai/src/providers/open_ai/completion.rs 🔗

@@ -0,0 +1,298 @@
+use anyhow::{anyhow, Result};
+use futures::{
+    future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt,
+    Stream, StreamExt,
+};
+use gpui::{executor::Background, AppContext};
+use isahc::{http::StatusCode, Request, RequestExt};
+use parking_lot::RwLock;
+use serde::{Deserialize, Serialize};
+use std::{
+    env,
+    fmt::{self, Display},
+    io,
+    sync::Arc,
+};
+use util::ResultExt;
+
+use crate::{
+    auth::{CredentialProvider, ProviderCredential},
+    completion::{CompletionProvider, CompletionRequest},
+    models::LanguageModel,
+};
+
+use crate::providers::open_ai::{OpenAILanguageModel, OPENAI_API_URL};
+
+#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
+#[serde(rename_all = "lowercase")]
+pub enum Role {
+    User,
+    Assistant,
+    System,
+}
+
+impl Role {
+    pub fn cycle(&mut self) {
+        *self = match self {
+            Role::User => Role::Assistant,
+            Role::Assistant => Role::System,
+            Role::System => Role::User,
+        }
+    }
+}
+
+impl Display for Role {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Role::User => write!(f, "User"),
+            Role::Assistant => write!(f, "Assistant"),
+            Role::System => write!(f, "System"),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct RequestMessage {
+    pub role: Role,
+    pub content: String,
+}
+
+#[derive(Debug, Default, Serialize)]
+pub struct OpenAIRequest {
+    pub model: String,
+    pub messages: Vec<RequestMessage>,
+    pub stream: bool,
+    pub stop: Vec<String>,
+    pub temperature: f32,
+}
+
+impl CompletionRequest for OpenAIRequest {
+    fn data(&self) -> serde_json::Result<String> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct ResponseMessage {
+    pub role: Option<Role>,
+    pub content: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OpenAIUsage {
+    pub prompt_tokens: u32,
+    pub completion_tokens: u32,
+    pub total_tokens: u32,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct ChatChoiceDelta {
+    pub index: u32,
+    pub delta: ResponseMessage,
+    pub finish_reason: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OpenAIResponseStreamEvent {
+    pub id: Option<String>,
+    pub object: String,
+    pub created: u32,
+    pub model: String,
+    pub choices: Vec<ChatChoiceDelta>,
+    pub usage: Option<OpenAIUsage>,
+}
+
+pub async fn stream_completion(
+    credential: ProviderCredential,
+    executor: Arc<Background>,
+    request: Box<dyn CompletionRequest>,
+) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
+    let api_key = match credential {
+        ProviderCredential::Credentials { api_key } => api_key,
+        _ => {
+            return Err(anyhow!("no credentials provider for completion"));
+        }
+    };
+
+    let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
+
+    let json_data = request.data()?;
+    let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
+        .header("Content-Type", "application/json")
+        .header("Authorization", format!("Bearer {}", api_key))
+        .body(json_data)?
+        .send_async()
+        .await?;
+
+    let status = response.status();
+    if status == StatusCode::OK {
+        executor
+            .spawn(async move {
+                let mut lines = BufReader::new(response.body_mut()).lines();
+
+                fn parse_line(
+                    line: Result<String, io::Error>,
+                ) -> Result<Option<OpenAIResponseStreamEvent>> {
+                    if let Some(data) = line?.strip_prefix("data: ") {
+                        let event = serde_json::from_str(&data)?;
+                        Ok(Some(event))
+                    } else {
+                        Ok(None)
+                    }
+                }
+
+                while let Some(line) = lines.next().await {
+                    if let Some(event) = parse_line(line).transpose() {
+                        let done = event.as_ref().map_or(false, |event| {
+                            event
+                                .choices
+                                .last()
+                                .map_or(false, |choice| choice.finish_reason.is_some())
+                        });
+                        if tx.unbounded_send(event).is_err() {
+                            break;
+                        }
+
+                        if done {
+                            break;
+                        }
+                    }
+                }
+
+                anyhow::Ok(())
+            })
+            .detach();
+
+        Ok(rx)
+    } else {
+        let mut body = String::new();
+        response.body_mut().read_to_string(&mut body).await?;
+
+        #[derive(Deserialize)]
+        struct OpenAIResponse {
+            error: OpenAIError,
+        }
+
+        #[derive(Deserialize)]
+        struct OpenAIError {
+            message: String,
+        }
+
+        match serde_json::from_str::<OpenAIResponse>(&body) {
+            Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
+                "Failed to connect to OpenAI API: {}",
+                response.error.message,
+            )),
+
+            _ => Err(anyhow!(
+                "Failed to connect to OpenAI API: {} {}",
+                response.status(),
+                body,
+            )),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct OpenAICompletionProvider {
+    model: OpenAILanguageModel,
+    credential: Arc<RwLock<ProviderCredential>>,
+    executor: Arc<Background>,
+}
+
+impl OpenAICompletionProvider {
+    pub fn new(model_name: &str, executor: Arc<Background>) -> Self {
+        let model = OpenAILanguageModel::load(model_name);
+        let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
+        Self {
+            model,
+            credential,
+            executor,
+        }
+    }
+}
+
+impl CredentialProvider for OpenAICompletionProvider {
+    fn has_credentials(&self) -> bool {
+        match *self.credential.read() {
+            ProviderCredential::Credentials { .. } => true,
+            _ => false,
+        }
+    }
+    fn retrieve_credentials(&self, cx: &AppContext) -> ProviderCredential {
+        let mut credential = self.credential.write();
+        match *credential {
+            ProviderCredential::Credentials { .. } => {
+                return credential.clone();
+            }
+            _ => {
+                if let Ok(api_key) = env::var("OPENAI_API_KEY") {
+                    *credential = ProviderCredential::Credentials { api_key };
+                } else if let Some((_, api_key)) = cx
+                    .platform()
+                    .read_credentials(OPENAI_API_URL)
+                    .log_err()
+                    .flatten()
+                {
+                    if let Some(api_key) = String::from_utf8(api_key).log_err() {
+                        *credential = ProviderCredential::Credentials { api_key };
+                    }
+                } else {
+                };
+            }
+        }
+
+        credential.clone()
+    }
+
+    fn save_credentials(&self, cx: &AppContext, credential: ProviderCredential) {
+        match credential.clone() {
+            ProviderCredential::Credentials { api_key } => {
+                cx.platform()
+                    .write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
+                    .log_err();
+            }
+            _ => {}
+        }
+
+        *self.credential.write() = credential;
+    }
+    fn delete_credentials(&self, cx: &AppContext) {
+        cx.platform().delete_credentials(OPENAI_API_URL).log_err();
+        *self.credential.write() = ProviderCredential::NoCredentials;
+    }
+}
+
+impl CompletionProvider for OpenAICompletionProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
+        model
+    }
+    fn complete(
+        &self,
+        prompt: Box<dyn CompletionRequest>,
+    ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
+        // Currently the CompletionRequest for OpenAI, includes a 'model' parameter
+        // This means that the model is determined by the CompletionRequest and not the CompletionProvider,
+        // which is currently model based, due to the langauge model.
+        // At some point in the future we should rectify this.
+        let credential = self.credential.read().clone();
+        let request = stream_completion(credential, self.executor.clone(), prompt);
+        async move {
+            let response = request.await?;
+            let stream = response
+                .filter_map(|response| async move {
+                    match response {
+                        Ok(mut response) => Some(Ok(response.choices.pop()?.delta.content?)),
+                        Err(error) => Some(Err(error)),
+                    }
+                })
+                .boxed();
+            Ok(stream)
+        }
+        .boxed()
+    }
+    fn box_clone(&self) -> Box<dyn CompletionProvider> {
+        Box::new((*self).clone())
+    }
+}

crates/ai/src/providers/open_ai/embedding.rs 🔗

@@ -0,0 +1,306 @@
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use futures::AsyncReadExt;
+use gpui::executor::Background;
+use gpui::{serde_json, AppContext};
+use isahc::http::StatusCode;
+use isahc::prelude::Configurable;
+use isahc::{AsyncBody, Response};
+use lazy_static::lazy_static;
+use parking_lot::{Mutex, RwLock};
+use parse_duration::parse;
+use postage::watch;
+use serde::{Deserialize, Serialize};
+use std::env;
+use std::ops::Add;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+use tiktoken_rs::{cl100k_base, CoreBPE};
+use util::http::{HttpClient, Request};
+use util::ResultExt;
+
+use crate::auth::{CredentialProvider, ProviderCredential};
+use crate::embedding::{Embedding, EmbeddingProvider};
+use crate::models::LanguageModel;
+use crate::providers::open_ai::OpenAILanguageModel;
+
+use crate::providers::open_ai::OPENAI_API_URL;
+
+lazy_static! {
+    static ref OPENAI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
+}
+
+#[derive(Clone)]
+pub struct OpenAIEmbeddingProvider {
+    model: OpenAILanguageModel,
+    credential: Arc<RwLock<ProviderCredential>>,
+    pub client: Arc<dyn HttpClient>,
+    pub executor: Arc<Background>,
+    rate_limit_count_rx: watch::Receiver<Option<Instant>>,
+    rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
+}
+
+#[derive(Serialize)]
+struct OpenAIEmbeddingRequest<'a> {
+    model: &'static str,
+    input: Vec<&'a str>,
+}
+
+#[derive(Deserialize)]
+struct OpenAIEmbeddingResponse {
+    data: Vec<OpenAIEmbedding>,
+    usage: OpenAIEmbeddingUsage,
+}
+
+#[derive(Debug, Deserialize)]
+struct OpenAIEmbedding {
+    embedding: Vec<f32>,
+    index: usize,
+    object: String,
+}
+
+#[derive(Deserialize)]
+struct OpenAIEmbeddingUsage {
+    prompt_tokens: usize,
+    total_tokens: usize,
+}
+
+impl OpenAIEmbeddingProvider {
+    pub fn new(client: Arc<dyn HttpClient>, executor: Arc<Background>) -> Self {
+        let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
+        let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
+
+        let model = OpenAILanguageModel::load("text-embedding-ada-002");
+        let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
+
+        OpenAIEmbeddingProvider {
+            model,
+            credential,
+            client,
+            executor,
+            rate_limit_count_rx,
+            rate_limit_count_tx,
+        }
+    }
+
+    fn get_api_key(&self) -> Result<String> {
+        match self.credential.read().clone() {
+            ProviderCredential::Credentials { api_key } => Ok(api_key),
+            _ => Err(anyhow!("api credentials not provided")),
+        }
+    }
+
+    fn resolve_rate_limit(&self) {
+        let reset_time = *self.rate_limit_count_tx.lock().borrow();
+
+        if let Some(reset_time) = reset_time {
+            if Instant::now() >= reset_time {
+                *self.rate_limit_count_tx.lock().borrow_mut() = None
+            }
+        }
+
+        log::trace!(
+            "resolving reset time: {:?}",
+            *self.rate_limit_count_tx.lock().borrow()
+        );
+    }
+
+    fn update_reset_time(&self, reset_time: Instant) {
+        let original_time = *self.rate_limit_count_tx.lock().borrow();
+
+        let updated_time = if let Some(original_time) = original_time {
+            if reset_time < original_time {
+                Some(reset_time)
+            } else {
+                Some(original_time)
+            }
+        } else {
+            Some(reset_time)
+        };
+
+        log::trace!("updating rate limit time: {:?}", updated_time);
+
+        *self.rate_limit_count_tx.lock().borrow_mut() = updated_time;
+    }
+    async fn send_request(
+        &self,
+        api_key: &str,
+        spans: Vec<&str>,
+        request_timeout: u64,
+    ) -> Result<Response<AsyncBody>> {
+        let request = Request::post("https://api.openai.com/v1/embeddings")
+            .redirect_policy(isahc::config::RedirectPolicy::Follow)
+            .timeout(Duration::from_secs(request_timeout))
+            .header("Content-Type", "application/json")
+            .header("Authorization", format!("Bearer {}", api_key))
+            .body(
+                serde_json::to_string(&OpenAIEmbeddingRequest {
+                    input: spans.clone(),
+                    model: "text-embedding-ada-002",
+                })
+                .unwrap()
+                .into(),
+            )?;
+
+        Ok(self.client.send(request).await?)
+    }
+}
+
+impl CredentialProvider for OpenAIEmbeddingProvider {
+    fn has_credentials(&self) -> bool {
+        match *self.credential.read() {
+            ProviderCredential::Credentials { .. } => true,
+            _ => false,
+        }
+    }
+    fn retrieve_credentials(&self, cx: &AppContext) -> ProviderCredential {
+        let mut credential = self.credential.write();
+        match *credential {
+            ProviderCredential::Credentials { .. } => {
+                return credential.clone();
+            }
+            _ => {
+                if let Ok(api_key) = env::var("OPENAI_API_KEY") {
+                    *credential = ProviderCredential::Credentials { api_key };
+                } else if let Some((_, api_key)) = cx
+                    .platform()
+                    .read_credentials(OPENAI_API_URL)
+                    .log_err()
+                    .flatten()
+                {
+                    if let Some(api_key) = String::from_utf8(api_key).log_err() {
+                        *credential = ProviderCredential::Credentials { api_key };
+                    }
+                } else {
+                };
+            }
+        }
+
+        credential.clone()
+    }
+
+    fn save_credentials(&self, cx: &AppContext, credential: ProviderCredential) {
+        match credential.clone() {
+            ProviderCredential::Credentials { api_key } => {
+                cx.platform()
+                    .write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
+                    .log_err();
+            }
+            _ => {}
+        }
+
+        *self.credential.write() = credential;
+    }
+    fn delete_credentials(&self, cx: &AppContext) {
+        cx.platform().delete_credentials(OPENAI_API_URL).log_err();
+        *self.credential.write() = ProviderCredential::NoCredentials;
+    }
+}
+
+#[async_trait]
+impl EmbeddingProvider for OpenAIEmbeddingProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
+        model
+    }
+
+    fn max_tokens_per_batch(&self) -> usize {
+        50000
+    }
+
+    fn rate_limit_expiration(&self) -> Option<Instant> {
+        *self.rate_limit_count_rx.borrow()
+    }
+
+    async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>> {
+        const BACKOFF_SECONDS: [usize; 4] = [3, 5, 15, 45];
+        const MAX_RETRIES: usize = 4;
+
+        let api_key = self.get_api_key()?;
+
+        let mut request_number = 0;
+        let mut rate_limiting = false;
+        let mut request_timeout: u64 = 15;
+        let mut response: Response<AsyncBody>;
+        while request_number < MAX_RETRIES {
+            response = self
+                .send_request(
+                    &api_key,
+                    spans.iter().map(|x| &**x).collect(),
+                    request_timeout,
+                )
+                .await?;
+
+            request_number += 1;
+
+            match response.status() {
+                StatusCode::REQUEST_TIMEOUT => {
+                    request_timeout += 5;
+                }
+                StatusCode::OK => {
+                    let mut body = String::new();
+                    response.body_mut().read_to_string(&mut body).await?;
+                    let response: OpenAIEmbeddingResponse = serde_json::from_str(&body)?;
+
+                    log::trace!(
+                        "openai embedding completed. tokens: {:?}",
+                        response.usage.total_tokens
+                    );
+
+                    // If we complete a request successfully that was previously rate_limited
+                    // resolve the rate limit
+                    if rate_limiting {
+                        self.resolve_rate_limit()
+                    }
+
+                    return Ok(response
+                        .data
+                        .into_iter()
+                        .map(|embedding| Embedding::from(embedding.embedding))
+                        .collect());
+                }
+                StatusCode::TOO_MANY_REQUESTS => {
+                    rate_limiting = true;
+                    let mut body = String::new();
+                    response.body_mut().read_to_string(&mut body).await?;
+
+                    let delay_duration = {
+                        let delay = Duration::from_secs(BACKOFF_SECONDS[request_number - 1] as u64);
+                        if let Some(time_to_reset) =
+                            response.headers().get("x-ratelimit-reset-tokens")
+                        {
+                            if let Ok(time_str) = time_to_reset.to_str() {
+                                parse(time_str).unwrap_or(delay)
+                            } else {
+                                delay
+                            }
+                        } else {
+                            delay
+                        }
+                    };
+
+                    // If we've previously rate limited, increment the duration but not the count
+                    let reset_time = Instant::now().add(delay_duration);
+                    self.update_reset_time(reset_time);
+
+                    log::trace!(
+                        "openai rate limiting: waiting {:?} until lifted",
+                        &delay_duration
+                    );
+
+                    self.executor.timer(delay_duration).await;
+                }
+                _ => {
+                    let mut body = String::new();
+                    response.body_mut().read_to_string(&mut body).await?;
+                    return Err(anyhow!(
+                        "open ai bad request: {:?} {:?}",
+                        &response.status(),
+                        body
+                    ));
+                }
+            }
+        }
+        Err(anyhow!("openai max retries"))
+    }
+}

crates/ai/src/providers/open_ai/mod.rs 🔗

@@ -0,0 +1,9 @@
+pub mod completion;
+pub mod embedding;
+pub mod model;
+
+pub use completion::*;
+pub use embedding::*;
+pub use model::OpenAILanguageModel;
+
+pub const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";

crates/ai/src/providers/open_ai/model.rs 🔗

@@ -0,0 +1,57 @@
+use anyhow::anyhow;
+use tiktoken_rs::CoreBPE;
+use util::ResultExt;
+
+use crate::models::{LanguageModel, TruncationDirection};
+
+#[derive(Clone)]
+pub struct OpenAILanguageModel {
+    name: String,
+    bpe: Option<CoreBPE>,
+}
+
+impl OpenAILanguageModel {
+    pub fn load(model_name: &str) -> Self {
+        let bpe = tiktoken_rs::get_bpe_from_model(model_name).log_err();
+        OpenAILanguageModel {
+            name: model_name.to_string(),
+            bpe,
+        }
+    }
+}
+
+impl LanguageModel for OpenAILanguageModel {
+    fn name(&self) -> String {
+        self.name.clone()
+    }
+    fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
+        if let Some(bpe) = &self.bpe {
+            anyhow::Ok(bpe.encode_with_special_tokens(content).len())
+        } else {
+            Err(anyhow!("bpe for open ai model was not retrieved"))
+        }
+    }
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String> {
+        if let Some(bpe) = &self.bpe {
+            let tokens = bpe.encode_with_special_tokens(content);
+            if tokens.len() > length {
+                match direction {
+                    TruncationDirection::End => bpe.decode(tokens[..length].to_vec()),
+                    TruncationDirection::Start => bpe.decode(tokens[length..].to_vec()),
+                }
+            } else {
+                bpe.decode(tokens)
+            }
+        } else {
+            Err(anyhow!("bpe for open ai model was not retrieved"))
+        }
+    }
+    fn capacity(&self) -> anyhow::Result<usize> {
+        anyhow::Ok(tiktoken_rs::model::get_context_size(&self.name))
+    }
+}

crates/ai/src/providers/open_ai/new.rs 🔗

@@ -0,0 +1,11 @@
+pub trait LanguageModel {
+    fn name(&self) -> String;
+    fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String>;
+    fn capacity(&self) -> anyhow::Result<usize>;
+}

crates/ai/src/test.rs 🔗

@@ -0,0 +1,191 @@
+use std::{
+    sync::atomic::{self, AtomicUsize, Ordering},
+    time::Instant,
+};
+
+use async_trait::async_trait;
+use futures::{channel::mpsc, future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
+use gpui::AppContext;
+use parking_lot::Mutex;
+
+use crate::{
+    auth::{CredentialProvider, ProviderCredential},
+    completion::{CompletionProvider, CompletionRequest},
+    embedding::{Embedding, EmbeddingProvider},
+    models::{LanguageModel, TruncationDirection},
+};
+
+#[derive(Clone)]
+pub struct FakeLanguageModel {
+    pub capacity: usize,
+}
+
+impl LanguageModel for FakeLanguageModel {
+    fn name(&self) -> String {
+        "dummy".to_string()
+    }
+    fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
+        anyhow::Ok(content.chars().collect::<Vec<char>>().len())
+    }
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String> {
+        println!("TRYING TO TRUNCATE: {:?}", length.clone());
+
+        if length > self.count_tokens(content)? {
+            println!("NOT TRUNCATING");
+            return anyhow::Ok(content.to_string());
+        }
+
+        anyhow::Ok(match direction {
+            TruncationDirection::End => content.chars().collect::<Vec<char>>()[..length]
+                .into_iter()
+                .collect::<String>(),
+            TruncationDirection::Start => content.chars().collect::<Vec<char>>()[length..]
+                .into_iter()
+                .collect::<String>(),
+        })
+    }
+    fn capacity(&self) -> anyhow::Result<usize> {
+        anyhow::Ok(self.capacity)
+    }
+}
+
+pub struct FakeEmbeddingProvider {
+    pub embedding_count: AtomicUsize,
+}
+
+impl Clone for FakeEmbeddingProvider {
+    fn clone(&self) -> Self {
+        FakeEmbeddingProvider {
+            embedding_count: AtomicUsize::new(self.embedding_count.load(Ordering::SeqCst)),
+        }
+    }
+}
+
+impl Default for FakeEmbeddingProvider {
+    fn default() -> Self {
+        FakeEmbeddingProvider {
+            embedding_count: AtomicUsize::default(),
+        }
+    }
+}
+
+impl FakeEmbeddingProvider {
+    pub fn embedding_count(&self) -> usize {
+        self.embedding_count.load(atomic::Ordering::SeqCst)
+    }
+
+    pub fn embed_sync(&self, span: &str) -> Embedding {
+        let mut result = vec![1.0; 26];
+        for letter in span.chars() {
+            let letter = letter.to_ascii_lowercase();
+            if letter as u32 >= 'a' as u32 {
+                let ix = (letter as u32) - ('a' as u32);
+                if ix < 26 {
+                    result[ix as usize] += 1.0;
+                }
+            }
+        }
+
+        let norm = result.iter().map(|x| x * x).sum::<f32>().sqrt();
+        for x in &mut result {
+            *x /= norm;
+        }
+
+        result.into()
+    }
+}
+
+impl CredentialProvider for FakeEmbeddingProvider {
+    fn has_credentials(&self) -> bool {
+        true
+    }
+    fn retrieve_credentials(&self, _cx: &AppContext) -> ProviderCredential {
+        ProviderCredential::NotNeeded
+    }
+    fn save_credentials(&self, _cx: &AppContext, _credential: ProviderCredential) {}
+    fn delete_credentials(&self, _cx: &AppContext) {}
+}
+
+#[async_trait]
+impl EmbeddingProvider for FakeEmbeddingProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        Box::new(FakeLanguageModel { capacity: 1000 })
+    }
+    fn max_tokens_per_batch(&self) -> usize {
+        1000
+    }
+
+    fn rate_limit_expiration(&self) -> Option<Instant> {
+        None
+    }
+
+    async fn embed_batch(&self, spans: Vec<String>) -> anyhow::Result<Vec<Embedding>> {
+        self.embedding_count
+            .fetch_add(spans.len(), atomic::Ordering::SeqCst);
+
+        anyhow::Ok(spans.iter().map(|span| self.embed_sync(span)).collect())
+    }
+}
+
+pub struct FakeCompletionProvider {
+    last_completion_tx: Mutex<Option<mpsc::Sender<String>>>,
+}
+
+impl Clone for FakeCompletionProvider {
+    fn clone(&self) -> Self {
+        Self {
+            last_completion_tx: Mutex::new(None),
+        }
+    }
+}
+
+impl FakeCompletionProvider {
+    pub fn new() -> Self {
+        Self {
+            last_completion_tx: Mutex::new(None),
+        }
+    }
+
+    pub fn send_completion(&self, completion: impl Into<String>) {
+        let mut tx = self.last_completion_tx.lock();
+        tx.as_mut().unwrap().try_send(completion.into()).unwrap();
+    }
+
+    pub fn finish_completion(&self) {
+        self.last_completion_tx.lock().take().unwrap();
+    }
+}
+
+impl CredentialProvider for FakeCompletionProvider {
+    fn has_credentials(&self) -> bool {
+        true
+    }
+    fn retrieve_credentials(&self, _cx: &AppContext) -> ProviderCredential {
+        ProviderCredential::NotNeeded
+    }
+    fn save_credentials(&self, _cx: &AppContext, _credential: ProviderCredential) {}
+    fn delete_credentials(&self, _cx: &AppContext) {}
+}
+
+impl CompletionProvider for FakeCompletionProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        let model: Box<dyn LanguageModel> = Box::new(FakeLanguageModel { capacity: 8190 });
+        model
+    }
+    fn complete(
+        &self,
+        _prompt: Box<dyn CompletionRequest>,
+    ) -> BoxFuture<'static, anyhow::Result<BoxStream<'static, anyhow::Result<String>>>> {
+        let (tx, rx) = mpsc::channel(1);
+        *self.last_completion_tx.lock() = Some(tx);
+        async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed()
+    }
+    fn box_clone(&self) -> Box<dyn CompletionProvider> {
+        Box::new((*self).clone())
+    }
+}

crates/ai2/Cargo.toml 🔗

@@ -0,0 +1,38 @@
+[package]
+name = "ai2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/ai2.rs"
+doctest = false
+
+[features]
+test-support = []
+
+[dependencies]
+gpui2 = { path = "../gpui2" }
+util = { path = "../util" }
+language2 = { path = "../language2" }
+async-trait.workspace = true
+anyhow.workspace = true
+futures.workspace = true
+lazy_static.workspace = true
+ordered-float.workspace = true
+parking_lot.workspace = true
+isahc.workspace = true
+regex.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+postage.workspace = true
+rand.workspace = true
+log.workspace = true
+parse_duration = "2.1.1"
+tiktoken-rs = "0.5.0"
+matrixmultiply = "0.3.7"
+rusqlite = { version = "0.29.0", features = ["blob", "array", "modern_sqlite"] }
+bincode = "1.3.3"
+
+[dev-dependencies]
+gpui2 = { path = "../gpui2", features = ["test-support"] }

crates/ai2/src/ai2.rs 🔗

@@ -0,0 +1,8 @@
+pub mod auth;
+pub mod completion;
+pub mod embedding;
+pub mod models;
+pub mod prompts;
+pub mod providers;
+#[cfg(any(test, feature = "test-support"))]
+pub mod test;

crates/ai2/src/auth.rs 🔗

@@ -0,0 +1,17 @@
+use async_trait::async_trait;
+use gpui2::AppContext;
+
+#[derive(Clone, Debug)]
+pub enum ProviderCredential {
+    Credentials { api_key: String },
+    NoCredentials,
+    NotNeeded,
+}
+
+#[async_trait]
+pub trait CredentialProvider: Send + Sync {
+    fn has_credentials(&self) -> bool;
+    async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential;
+    async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential);
+    async fn delete_credentials(&self, cx: &mut AppContext);
+}

crates/ai2/src/completion.rs 🔗

@@ -0,0 +1,23 @@
+use anyhow::Result;
+use futures::{future::BoxFuture, stream::BoxStream};
+
+use crate::{auth::CredentialProvider, models::LanguageModel};
+
+pub trait CompletionRequest: Send + Sync {
+    fn data(&self) -> serde_json::Result<String>;
+}
+
+pub trait CompletionProvider: CredentialProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel>;
+    fn complete(
+        &self,
+        prompt: Box<dyn CompletionRequest>,
+    ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>>;
+    fn box_clone(&self) -> Box<dyn CompletionProvider>;
+}
+
+impl Clone for Box<dyn CompletionProvider> {
+    fn clone(&self) -> Box<dyn CompletionProvider> {
+        self.box_clone()
+    }
+}

crates/ai2/src/embedding.rs 🔗

@@ -0,0 +1,123 @@
+use std::time::Instant;
+
+use anyhow::Result;
+use async_trait::async_trait;
+use ordered_float::OrderedFloat;
+use rusqlite::types::{FromSql, FromSqlResult, ToSqlOutput, ValueRef};
+use rusqlite::ToSql;
+
+use crate::auth::CredentialProvider;
+use crate::models::LanguageModel;
+
+#[derive(Debug, PartialEq, Clone)]
+pub struct Embedding(pub Vec<f32>);
+
+// This is needed for semantic index functionality
+// Unfortunately it has to live wherever the "Embedding" struct is created.
+// Keeping this in here though, introduces a 'rusqlite' dependency into AI
+// which is less than ideal
+impl FromSql for Embedding {
+    fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+        let bytes = value.as_blob()?;
+        let embedding: Result<Vec<f32>, Box<bincode::ErrorKind>> = bincode::deserialize(bytes);
+        if embedding.is_err() {
+            return Err(rusqlite::types::FromSqlError::Other(embedding.unwrap_err()));
+        }
+        Ok(Embedding(embedding.unwrap()))
+    }
+}
+
+impl ToSql for Embedding {
+    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
+        let bytes = bincode::serialize(&self.0)
+            .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
+        Ok(ToSqlOutput::Owned(rusqlite::types::Value::Blob(bytes)))
+    }
+}
+impl From<Vec<f32>> for Embedding {
+    fn from(value: Vec<f32>) -> Self {
+        Embedding(value)
+    }
+}
+
+impl Embedding {
+    pub fn similarity(&self, other: &Self) -> OrderedFloat<f32> {
+        let len = self.0.len();
+        assert_eq!(len, other.0.len());
+
+        let mut result = 0.0;
+        unsafe {
+            matrixmultiply::sgemm(
+                1,
+                len,
+                1,
+                1.0,
+                self.0.as_ptr(),
+                len as isize,
+                1,
+                other.0.as_ptr(),
+                1,
+                len as isize,
+                0.0,
+                &mut result as *mut f32,
+                1,
+                1,
+            );
+        }
+        OrderedFloat(result)
+    }
+}
+
+#[async_trait]
+pub trait EmbeddingProvider: CredentialProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel>;
+    async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>>;
+    fn max_tokens_per_batch(&self) -> usize;
+    fn rate_limit_expiration(&self) -> Option<Instant>;
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use rand::prelude::*;
+
+    #[gpui2::test]
+    fn test_similarity(mut rng: StdRng) {
+        assert_eq!(
+            Embedding::from(vec![1., 0., 0., 0., 0.])
+                .similarity(&Embedding::from(vec![0., 1., 0., 0., 0.])),
+            0.
+        );
+        assert_eq!(
+            Embedding::from(vec![2., 0., 0., 0., 0.])
+                .similarity(&Embedding::from(vec![3., 1., 0., 0., 0.])),
+            6.
+        );
+
+        for _ in 0..100 {
+            let size = 1536;
+            let mut a = vec![0.; size];
+            let mut b = vec![0.; size];
+            for (a, b) in a.iter_mut().zip(b.iter_mut()) {
+                *a = rng.gen();
+                *b = rng.gen();
+            }
+            let a = Embedding::from(a);
+            let b = Embedding::from(b);
+
+            assert_eq!(
+                round_to_decimals(a.similarity(&b), 1),
+                round_to_decimals(reference_dot(&a.0, &b.0), 1)
+            );
+        }
+
+        fn round_to_decimals(n: OrderedFloat<f32>, decimal_places: i32) -> f32 {
+            let factor = (10.0 as f32).powi(decimal_places);
+            (n * factor).round() / factor
+        }
+
+        fn reference_dot(a: &[f32], b: &[f32]) -> OrderedFloat<f32> {
+            OrderedFloat(a.iter().zip(b.iter()).map(|(a, b)| a * b).sum())
+        }
+    }
+}

crates/ai2/src/models.rs 🔗

@@ -0,0 +1,16 @@
+pub enum TruncationDirection {
+    Start,
+    End,
+}
+
+pub trait LanguageModel {
+    fn name(&self) -> String;
+    fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String>;
+    fn capacity(&self) -> anyhow::Result<usize>;
+}

crates/ai2/src/prompts/base.rs 🔗

@@ -0,0 +1,330 @@
+use std::cmp::Reverse;
+use std::ops::Range;
+use std::sync::Arc;
+
+use language2::BufferSnapshot;
+use util::ResultExt;
+
+use crate::models::LanguageModel;
+use crate::prompts::repository_context::PromptCodeSnippet;
+
+pub(crate) enum PromptFileType {
+    Text,
+    Code,
+}
+
+// TODO: Set this up to manage for defaults well
+pub struct PromptArguments {
+    pub model: Arc<dyn LanguageModel>,
+    pub user_prompt: Option<String>,
+    pub language_name: Option<String>,
+    pub project_name: Option<String>,
+    pub snippets: Vec<PromptCodeSnippet>,
+    pub reserved_tokens: usize,
+    pub buffer: Option<BufferSnapshot>,
+    pub selected_range: Option<Range<usize>>,
+}
+
+impl PromptArguments {
+    pub(crate) fn get_file_type(&self) -> PromptFileType {
+        if self
+            .language_name
+            .as_ref()
+            .and_then(|name| Some(!["Markdown", "Plain Text"].contains(&name.as_str())))
+            .unwrap_or(true)
+        {
+            PromptFileType::Code
+        } else {
+            PromptFileType::Text
+        }
+    }
+}
+
+pub trait PromptTemplate {
+    fn generate(
+        &self,
+        args: &PromptArguments,
+        max_token_length: Option<usize>,
+    ) -> anyhow::Result<(String, usize)>;
+}
+
+#[repr(i8)]
+#[derive(PartialEq, Eq, Ord)]
+pub enum PromptPriority {
+    Mandatory,                // Ignores truncation
+    Ordered { order: usize }, // Truncates based on priority
+}
+
+impl PartialOrd for PromptPriority {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        match (self, other) {
+            (Self::Mandatory, Self::Mandatory) => Some(std::cmp::Ordering::Equal),
+            (Self::Mandatory, Self::Ordered { .. }) => Some(std::cmp::Ordering::Greater),
+            (Self::Ordered { .. }, Self::Mandatory) => Some(std::cmp::Ordering::Less),
+            (Self::Ordered { order: a }, Self::Ordered { order: b }) => b.partial_cmp(a),
+        }
+    }
+}
+
+pub struct PromptChain {
+    args: PromptArguments,
+    templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)>,
+}
+
+impl PromptChain {
+    pub fn new(
+        args: PromptArguments,
+        templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)>,
+    ) -> Self {
+        PromptChain { args, templates }
+    }
+
+    pub fn generate(&self, truncate: bool) -> anyhow::Result<(String, usize)> {
+        // Argsort based on Prompt Priority
+        let seperator = "\n";
+        let seperator_tokens = self.args.model.count_tokens(seperator)?;
+        let mut sorted_indices = (0..self.templates.len()).collect::<Vec<_>>();
+        sorted_indices.sort_by_key(|&i| Reverse(&self.templates[i].0));
+
+        // If Truncate
+        let mut tokens_outstanding = if truncate {
+            Some(self.args.model.capacity()? - self.args.reserved_tokens)
+        } else {
+            None
+        };
+
+        let mut prompts = vec!["".to_string(); sorted_indices.len()];
+        for idx in sorted_indices {
+            let (_, template) = &self.templates[idx];
+
+            if let Some((template_prompt, prompt_token_count)) =
+                template.generate(&self.args, tokens_outstanding).log_err()
+            {
+                if template_prompt != "" {
+                    prompts[idx] = template_prompt;
+
+                    if let Some(remaining_tokens) = tokens_outstanding {
+                        let new_tokens = prompt_token_count + seperator_tokens;
+                        tokens_outstanding = if remaining_tokens > new_tokens {
+                            Some(remaining_tokens - new_tokens)
+                        } else {
+                            Some(0)
+                        };
+                    }
+                }
+            }
+        }
+
+        prompts.retain(|x| x != "");
+
+        let full_prompt = prompts.join(seperator);
+        let total_token_count = self.args.model.count_tokens(&full_prompt)?;
+        anyhow::Ok((prompts.join(seperator), total_token_count))
+    }
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+    use crate::models::TruncationDirection;
+    use crate::test::FakeLanguageModel;
+
+    use super::*;
+
+    #[test]
+    pub fn test_prompt_chain() {
+        struct TestPromptTemplate {}
+        impl PromptTemplate for TestPromptTemplate {
+            fn generate(
+                &self,
+                args: &PromptArguments,
+                max_token_length: Option<usize>,
+            ) -> anyhow::Result<(String, usize)> {
+                let mut content = "This is a test prompt template".to_string();
+
+                let mut token_count = args.model.count_tokens(&content)?;
+                if let Some(max_token_length) = max_token_length {
+                    if token_count > max_token_length {
+                        content = args.model.truncate(
+                            &content,
+                            max_token_length,
+                            TruncationDirection::End,
+                        )?;
+                        token_count = max_token_length;
+                    }
+                }
+
+                anyhow::Ok((content, token_count))
+            }
+        }
+
+        struct TestLowPriorityTemplate {}
+        impl PromptTemplate for TestLowPriorityTemplate {
+            fn generate(
+                &self,
+                args: &PromptArguments,
+                max_token_length: Option<usize>,
+            ) -> anyhow::Result<(String, usize)> {
+                let mut content = "This is a low priority test prompt template".to_string();
+
+                let mut token_count = args.model.count_tokens(&content)?;
+                if let Some(max_token_length) = max_token_length {
+                    if token_count > max_token_length {
+                        content = args.model.truncate(
+                            &content,
+                            max_token_length,
+                            TruncationDirection::End,
+                        )?;
+                        token_count = max_token_length;
+                    }
+                }
+
+                anyhow::Ok((content, token_count))
+            }
+        }
+
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity: 100 });
+        let args = PromptArguments {
+            model: model.clone(),
+            language_name: None,
+            project_name: None,
+            snippets: Vec::new(),
+            reserved_tokens: 0,
+            buffer: None,
+            selected_range: None,
+            user_prompt: None,
+        };
+
+        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
+            (
+                PromptPriority::Ordered { order: 0 },
+                Box::new(TestPromptTemplate {}),
+            ),
+            (
+                PromptPriority::Ordered { order: 1 },
+                Box::new(TestLowPriorityTemplate {}),
+            ),
+        ];
+        let chain = PromptChain::new(args, templates);
+
+        let (prompt, token_count) = chain.generate(false).unwrap();
+
+        assert_eq!(
+            prompt,
+            "This is a test prompt template\nThis is a low priority test prompt template"
+                .to_string()
+        );
+
+        assert_eq!(model.count_tokens(&prompt).unwrap(), token_count);
+
+        // Testing with Truncation Off
+        // Should ignore capacity and return all prompts
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity: 20 });
+        let args = PromptArguments {
+            model: model.clone(),
+            language_name: None,
+            project_name: None,
+            snippets: Vec::new(),
+            reserved_tokens: 0,
+            buffer: None,
+            selected_range: None,
+            user_prompt: None,
+        };
+
+        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
+            (
+                PromptPriority::Ordered { order: 0 },
+                Box::new(TestPromptTemplate {}),
+            ),
+            (
+                PromptPriority::Ordered { order: 1 },
+                Box::new(TestLowPriorityTemplate {}),
+            ),
+        ];
+        let chain = PromptChain::new(args, templates);
+
+        let (prompt, token_count) = chain.generate(false).unwrap();
+
+        assert_eq!(
+            prompt,
+            "This is a test prompt template\nThis is a low priority test prompt template"
+                .to_string()
+        );
+
+        assert_eq!(model.count_tokens(&prompt).unwrap(), token_count);
+
+        // Testing with Truncation Off
+        // Should ignore capacity and return all prompts
+        let capacity = 20;
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity });
+        let args = PromptArguments {
+            model: model.clone(),
+            language_name: None,
+            project_name: None,
+            snippets: Vec::new(),
+            reserved_tokens: 0,
+            buffer: None,
+            selected_range: None,
+            user_prompt: None,
+        };
+
+        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
+            (
+                PromptPriority::Ordered { order: 0 },
+                Box::new(TestPromptTemplate {}),
+            ),
+            (
+                PromptPriority::Ordered { order: 1 },
+                Box::new(TestLowPriorityTemplate {}),
+            ),
+            (
+                PromptPriority::Ordered { order: 2 },
+                Box::new(TestLowPriorityTemplate {}),
+            ),
+        ];
+        let chain = PromptChain::new(args, templates);
+
+        let (prompt, token_count) = chain.generate(true).unwrap();
+
+        assert_eq!(prompt, "This is a test promp".to_string());
+        assert_eq!(token_count, capacity);
+
+        // Change Ordering of Prompts Based on Priority
+        let capacity = 120;
+        let reserved_tokens = 10;
+        let model: Arc<dyn LanguageModel> = Arc::new(FakeLanguageModel { capacity });
+        let args = PromptArguments {
+            model: model.clone(),
+            language_name: None,
+            project_name: None,
+            snippets: Vec::new(),
+            reserved_tokens,
+            buffer: None,
+            selected_range: None,
+            user_prompt: None,
+        };
+        let templates: Vec<(PromptPriority, Box<dyn PromptTemplate>)> = vec![
+            (
+                PromptPriority::Mandatory,
+                Box::new(TestLowPriorityTemplate {}),
+            ),
+            (
+                PromptPriority::Ordered { order: 0 },
+                Box::new(TestPromptTemplate {}),
+            ),
+            (
+                PromptPriority::Ordered { order: 1 },
+                Box::new(TestLowPriorityTemplate {}),
+            ),
+        ];
+        let chain = PromptChain::new(args, templates);
+
+        let (prompt, token_count) = chain.generate(true).unwrap();
+
+        assert_eq!(
+            prompt,
+            "This is a low priority test prompt template\nThis is a test prompt template\nThis is a low priority test prompt "
+                .to_string()
+        );
+        assert_eq!(token_count, capacity - reserved_tokens);
+    }
+}

crates/ai2/src/prompts/file_context.rs 🔗

@@ -0,0 +1,164 @@
+use anyhow::anyhow;
+use language2::BufferSnapshot;
+use language2::ToOffset;
+
+use crate::models::LanguageModel;
+use crate::models::TruncationDirection;
+use crate::prompts::base::PromptArguments;
+use crate::prompts::base::PromptTemplate;
+use std::fmt::Write;
+use std::ops::Range;
+use std::sync::Arc;
+
+fn retrieve_context(
+    buffer: &BufferSnapshot,
+    selected_range: &Option<Range<usize>>,
+    model: Arc<dyn LanguageModel>,
+    max_token_count: Option<usize>,
+) -> anyhow::Result<(String, usize, bool)> {
+    let mut prompt = String::new();
+    let mut truncated = false;
+    if let Some(selected_range) = selected_range {
+        let start = selected_range.start.to_offset(buffer);
+        let end = selected_range.end.to_offset(buffer);
+
+        let start_window = buffer.text_for_range(0..start).collect::<String>();
+
+        let mut selected_window = String::new();
+        if start == end {
+            write!(selected_window, "<|START|>").unwrap();
+        } else {
+            write!(selected_window, "<|START|").unwrap();
+        }
+
+        write!(
+            selected_window,
+            "{}",
+            buffer.text_for_range(start..end).collect::<String>()
+        )
+        .unwrap();
+
+        if start != end {
+            write!(selected_window, "|END|>").unwrap();
+        }
+
+        let end_window = buffer.text_for_range(end..buffer.len()).collect::<String>();
+
+        if let Some(max_token_count) = max_token_count {
+            let selected_tokens = model.count_tokens(&selected_window)?;
+            if selected_tokens > max_token_count {
+                return Err(anyhow!(
+                    "selected range is greater than model context window, truncation not possible"
+                ));
+            };
+
+            let mut remaining_tokens = max_token_count - selected_tokens;
+            let start_window_tokens = model.count_tokens(&start_window)?;
+            let end_window_tokens = model.count_tokens(&end_window)?;
+            let outside_tokens = start_window_tokens + end_window_tokens;
+            if outside_tokens > remaining_tokens {
+                let (start_goal_tokens, end_goal_tokens) =
+                    if start_window_tokens < end_window_tokens {
+                        let start_goal_tokens = (remaining_tokens / 2).min(start_window_tokens);
+                        remaining_tokens -= start_goal_tokens;
+                        let end_goal_tokens = remaining_tokens.min(end_window_tokens);
+                        (start_goal_tokens, end_goal_tokens)
+                    } else {
+                        let end_goal_tokens = (remaining_tokens / 2).min(end_window_tokens);
+                        remaining_tokens -= end_goal_tokens;
+                        let start_goal_tokens = remaining_tokens.min(start_window_tokens);
+                        (start_goal_tokens, end_goal_tokens)
+                    };
+
+                let truncated_start_window =
+                    model.truncate(&start_window, start_goal_tokens, TruncationDirection::Start)?;
+                let truncated_end_window =
+                    model.truncate(&end_window, end_goal_tokens, TruncationDirection::End)?;
+                writeln!(
+                    prompt,
+                    "{truncated_start_window}{selected_window}{truncated_end_window}"
+                )
+                .unwrap();
+                truncated = true;
+            } else {
+                writeln!(prompt, "{start_window}{selected_window}{end_window}").unwrap();
+            }
+        } else {
+            // If we dont have a selected range, include entire file.
+            writeln!(prompt, "{}", &buffer.text()).unwrap();
+
+            // Dumb truncation strategy
+            if let Some(max_token_count) = max_token_count {
+                if model.count_tokens(&prompt)? > max_token_count {
+                    truncated = true;
+                    prompt = model.truncate(&prompt, max_token_count, TruncationDirection::End)?;
+                }
+            }
+        }
+    }
+
+    let token_count = model.count_tokens(&prompt)?;
+    anyhow::Ok((prompt, token_count, truncated))
+}
+
+pub struct FileContext {}
+
+impl PromptTemplate for FileContext {
+    fn generate(
+        &self,
+        args: &PromptArguments,
+        max_token_length: Option<usize>,
+    ) -> anyhow::Result<(String, usize)> {
+        if let Some(buffer) = &args.buffer {
+            let mut prompt = String::new();
+            // Add Initial Preamble
+            // TODO: Do we want to add the path in here?
+            writeln!(
+                prompt,
+                "The file you are currently working on has the following content:"
+            )
+            .unwrap();
+
+            let language_name = args
+                .language_name
+                .clone()
+                .unwrap_or("".to_string())
+                .to_lowercase();
+
+            let (context, _, truncated) = retrieve_context(
+                buffer,
+                &args.selected_range,
+                args.model.clone(),
+                max_token_length,
+            )?;
+            writeln!(prompt, "```{language_name}\n{context}\n```").unwrap();
+
+            if truncated {
+                writeln!(prompt, "Note the content has been truncated and only represents a portion of the file.").unwrap();
+            }
+
+            if let Some(selected_range) = &args.selected_range {
+                let start = selected_range.start.to_offset(buffer);
+                let end = selected_range.end.to_offset(buffer);
+
+                if start == end {
+                    writeln!(prompt, "In particular, the user's cursor is currently on the '<|START|>' span in the above content, with no text selected.").unwrap();
+                } else {
+                    writeln!(prompt, "In particular, the user has selected a section of the text between the '<|START|' and '|END|>' spans.").unwrap();
+                }
+            }
+
+            // Really dumb truncation strategy
+            if let Some(max_tokens) = max_token_length {
+                prompt = args
+                    .model
+                    .truncate(&prompt, max_tokens, TruncationDirection::End)?;
+            }
+
+            let token_count = args.model.count_tokens(&prompt)?;
+            anyhow::Ok((prompt, token_count))
+        } else {
+            Err(anyhow!("no buffer provided to retrieve file context from"))
+        }
+    }
+}

crates/ai2/src/prompts/generate.rs 🔗

@@ -0,0 +1,99 @@
+use crate::prompts::base::{PromptArguments, PromptFileType, PromptTemplate};
+use anyhow::anyhow;
+use std::fmt::Write;
+
+pub fn capitalize(s: &str) -> String {
+    let mut c = s.chars();
+    match c.next() {
+        None => String::new(),
+        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
+    }
+}
+
+pub struct GenerateInlineContent {}
+
+impl PromptTemplate for GenerateInlineContent {
+    fn generate(
+        &self,
+        args: &PromptArguments,
+        max_token_length: Option<usize>,
+    ) -> anyhow::Result<(String, usize)> {
+        let Some(user_prompt) = &args.user_prompt else {
+            return Err(anyhow!("user prompt not provided"));
+        };
+
+        let file_type = args.get_file_type();
+        let content_type = match &file_type {
+            PromptFileType::Code => "code",
+            PromptFileType::Text => "text",
+        };
+
+        let mut prompt = String::new();
+
+        if let Some(selected_range) = &args.selected_range {
+            if selected_range.start == selected_range.end {
+                writeln!(
+                    prompt,
+                    "Assume the cursor is located where the `<|START|>` span is."
+                )
+                .unwrap();
+                writeln!(
+                    prompt,
+                    "{} can't be replaced, so assume your answer will be inserted at the cursor.",
+                    capitalize(content_type)
+                )
+                .unwrap();
+                writeln!(
+                    prompt,
+                    "Generate {content_type} based on the users prompt: {user_prompt}",
+                )
+                .unwrap();
+            } else {
+                writeln!(prompt, "Modify the user's selected {content_type} based upon the users prompt: '{user_prompt}'").unwrap();
+                writeln!(prompt, "You must reply with only the adjusted {content_type} (within the '<|START|' and '|END|>' spans) not the entire file.").unwrap();
+                writeln!(prompt, "Double check that you only return code and not the '<|START|' and '|END|'> spans").unwrap();
+            }
+        } else {
+            writeln!(
+                prompt,
+                "Generate {content_type} based on the users prompt: {user_prompt}"
+            )
+            .unwrap();
+        }
+
+        if let Some(language_name) = &args.language_name {
+            writeln!(
+                prompt,
+                "Your answer MUST always and only be valid {}.",
+                language_name
+            )
+            .unwrap();
+        }
+        writeln!(prompt, "Never make remarks about the output.").unwrap();
+        writeln!(
+            prompt,
+            "Do not return anything else, except the generated {content_type}."
+        )
+        .unwrap();
+
+        match file_type {
+            PromptFileType::Code => {
+                // writeln!(prompt, "Always wrap your code in a Markdown block.").unwrap();
+            }
+            _ => {}
+        }
+
+        // Really dumb truncation strategy
+        if let Some(max_tokens) = max_token_length {
+            prompt = args.model.truncate(
+                &prompt,
+                max_tokens,
+                crate::models::TruncationDirection::End,
+            )?;
+        }
+
+        let token_count = args.model.count_tokens(&prompt)?;
+
+        anyhow::Ok((prompt, token_count))
+    }
+}

crates/ai2/src/prompts/preamble.rs 🔗

@@ -0,0 +1,52 @@
+use crate::prompts::base::{PromptArguments, PromptFileType, PromptTemplate};
+use std::fmt::Write;
+
+pub struct EngineerPreamble {}
+
+impl PromptTemplate for EngineerPreamble {
+    fn generate(
+        &self,
+        args: &PromptArguments,
+        max_token_length: Option<usize>,
+    ) -> anyhow::Result<(String, usize)> {
+        let mut prompts = Vec::new();
+
+        match args.get_file_type() {
+            PromptFileType::Code => {
+                prompts.push(format!(
+                    "You are an expert {}engineer.",
+                    args.language_name.clone().unwrap_or("".to_string()) + " "
+                ));
+            }
+            PromptFileType::Text => {
+                prompts.push("You are an expert engineer.".to_string());
+            }
+        }
+
+        if let Some(project_name) = args.project_name.clone() {
+            prompts.push(format!(
+                "You are currently working inside the '{project_name}' project in code editor Zed."
+            ));
+        }
+
+        if let Some(mut remaining_tokens) = max_token_length {
+            let mut prompt = String::new();
+            let mut total_count = 0;
+            for prompt_piece in prompts {
+                let prompt_token_count =
+                    args.model.count_tokens(&prompt_piece)? + args.model.count_tokens("\n")?;
+                if remaining_tokens > prompt_token_count {
+                    writeln!(prompt, "{prompt_piece}").unwrap();
+                    remaining_tokens -= prompt_token_count;
+                    total_count += prompt_token_count;
+                }
+            }
+
+            anyhow::Ok((prompt, total_count))
+        } else {
+            let prompt = prompts.join("\n");
+            let token_count = args.model.count_tokens(&prompt)?;
+            anyhow::Ok((prompt, token_count))
+        }
+    }
+}

crates/ai2/src/prompts/repository_context.rs 🔗

@@ -0,0 +1,98 @@
+use crate::prompts::base::{PromptArguments, PromptTemplate};
+use std::fmt::Write;
+use std::{ops::Range, path::PathBuf};
+
+use gpui2::{AsyncAppContext, Model};
+use language2::{Anchor, Buffer};
+
+#[derive(Clone)]
+pub struct PromptCodeSnippet {
+    path: Option<PathBuf>,
+    language_name: Option<String>,
+    content: String,
+}
+
+impl PromptCodeSnippet {
+    pub fn new(
+        buffer: Model<Buffer>,
+        range: Range<Anchor>,
+        cx: &mut AsyncAppContext,
+    ) -> anyhow::Result<Self> {
+        let (content, language_name, file_path) = buffer.update(cx, |buffer, _| {
+            let snapshot = buffer.snapshot();
+            let content = snapshot.text_for_range(range.clone()).collect::<String>();
+
+            let language_name = buffer
+                .language()
+                .and_then(|language| Some(language.name().to_string().to_lowercase()));
+
+            let file_path = buffer
+                .file()
+                .and_then(|file| Some(file.path().to_path_buf()));
+
+            (content, language_name, file_path)
+        })?;
+
+        anyhow::Ok(PromptCodeSnippet {
+            path: file_path,
+            language_name,
+            content,
+        })
+    }
+}
+
+impl ToString for PromptCodeSnippet {
+    fn to_string(&self) -> String {
+        let path = self
+            .path
+            .as_ref()
+            .and_then(|path| Some(path.to_string_lossy().to_string()))
+            .unwrap_or("".to_string());
+        let language_name = self.language_name.clone().unwrap_or("".to_string());
+        let content = self.content.clone();
+
+        format!("The below code snippet may be relevant from file: {path}\n```{language_name}\n{content}\n```")
+    }
+}
+
+pub struct RepositoryContext {}
+
+impl PromptTemplate for RepositoryContext {
+    fn generate(
+        &self,
+        args: &PromptArguments,
+        max_token_length: Option<usize>,
+    ) -> anyhow::Result<(String, usize)> {
+        const MAXIMUM_SNIPPET_TOKEN_COUNT: usize = 500;
+        let template = "You are working inside a large repository, here are a few code snippets that may be useful.";
+        let mut prompt = String::new();
+
+        let mut remaining_tokens = max_token_length.clone();
+        let seperator_token_length = args.model.count_tokens("\n")?;
+        for snippet in &args.snippets {
+            let mut snippet_prompt = template.to_string();
+            let content = snippet.to_string();
+            writeln!(snippet_prompt, "{content}").unwrap();
+
+            let token_count = args.model.count_tokens(&snippet_prompt)?;
+            if token_count <= MAXIMUM_SNIPPET_TOKEN_COUNT {
+                if let Some(tokens_left) = remaining_tokens {
+                    if tokens_left >= token_count {
+                        writeln!(prompt, "{snippet_prompt}").unwrap();
+                        remaining_tokens = if tokens_left >= (token_count + seperator_token_length)
+                        {
+                            Some(tokens_left - token_count - seperator_token_length)
+                        } else {
+                            Some(0)
+                        };
+                    }
+                } else {
+                    writeln!(prompt, "{snippet_prompt}").unwrap();
+                }
+            }
+        }
+
+        let total_token_count = args.model.count_tokens(&prompt)?;
+        anyhow::Ok((prompt, total_token_count))
+    }
+}

crates/ai2/src/providers/open_ai/completion.rs 🔗

@@ -0,0 +1,306 @@
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use futures::{
+    future::BoxFuture, io::BufReader, stream::BoxStream, AsyncBufReadExt, AsyncReadExt, FutureExt,
+    Stream, StreamExt,
+};
+use gpui2::{AppContext, Executor};
+use isahc::{http::StatusCode, Request, RequestExt};
+use parking_lot::RwLock;
+use serde::{Deserialize, Serialize};
+use std::{
+    env,
+    fmt::{self, Display},
+    io,
+    sync::Arc,
+};
+use util::ResultExt;
+
+use crate::{
+    auth::{CredentialProvider, ProviderCredential},
+    completion::{CompletionProvider, CompletionRequest},
+    models::LanguageModel,
+};
+
+use crate::providers::open_ai::{OpenAILanguageModel, OPENAI_API_URL};
+
+#[derive(Clone, Copy, Serialize, Deserialize, Debug, Eq, PartialEq)]
+#[serde(rename_all = "lowercase")]
+pub enum Role {
+    User,
+    Assistant,
+    System,
+}
+
+impl Role {
+    pub fn cycle(&mut self) {
+        *self = match self {
+            Role::User => Role::Assistant,
+            Role::Assistant => Role::System,
+            Role::System => Role::User,
+        }
+    }
+}
+
+impl Display for Role {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Role::User => write!(f, "User"),
+            Role::Assistant => write!(f, "Assistant"),
+            Role::System => write!(f, "System"),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct RequestMessage {
+    pub role: Role,
+    pub content: String,
+}
+
+#[derive(Debug, Default, Serialize)]
+pub struct OpenAIRequest {
+    pub model: String,
+    pub messages: Vec<RequestMessage>,
+    pub stream: bool,
+    pub stop: Vec<String>,
+    pub temperature: f32,
+}
+
+impl CompletionRequest for OpenAIRequest {
+    fn data(&self) -> serde_json::Result<String> {
+        serde_json::to_string(self)
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
+pub struct ResponseMessage {
+    pub role: Option<Role>,
+    pub content: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OpenAIUsage {
+    pub prompt_tokens: u32,
+    pub completion_tokens: u32,
+    pub total_tokens: u32,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct ChatChoiceDelta {
+    pub index: u32,
+    pub delta: ResponseMessage,
+    pub finish_reason: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct OpenAIResponseStreamEvent {
+    pub id: Option<String>,
+    pub object: String,
+    pub created: u32,
+    pub model: String,
+    pub choices: Vec<ChatChoiceDelta>,
+    pub usage: Option<OpenAIUsage>,
+}
+
+pub async fn stream_completion(
+    credential: ProviderCredential,
+    executor: Arc<Executor>,
+    request: Box<dyn CompletionRequest>,
+) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
+    let api_key = match credential {
+        ProviderCredential::Credentials { api_key } => api_key,
+        _ => {
+            return Err(anyhow!("no credentials provider for completion"));
+        }
+    };
+
+    let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAIResponseStreamEvent>>();
+
+    let json_data = request.data()?;
+    let mut response = Request::post(format!("{OPENAI_API_URL}/chat/completions"))
+        .header("Content-Type", "application/json")
+        .header("Authorization", format!("Bearer {}", api_key))
+        .body(json_data)?
+        .send_async()
+        .await?;
+
+    let status = response.status();
+    if status == StatusCode::OK {
+        executor
+            .spawn(async move {
+                let mut lines = BufReader::new(response.body_mut()).lines();
+
+                fn parse_line(
+                    line: Result<String, io::Error>,
+                ) -> Result<Option<OpenAIResponseStreamEvent>> {
+                    if let Some(data) = line?.strip_prefix("data: ") {
+                        let event = serde_json::from_str(&data)?;
+                        Ok(Some(event))
+                    } else {
+                        Ok(None)
+                    }
+                }
+
+                while let Some(line) = lines.next().await {
+                    if let Some(event) = parse_line(line).transpose() {
+                        let done = event.as_ref().map_or(false, |event| {
+                            event
+                                .choices
+                                .last()
+                                .map_or(false, |choice| choice.finish_reason.is_some())
+                        });
+                        if tx.unbounded_send(event).is_err() {
+                            break;
+                        }
+
+                        if done {
+                            break;
+                        }
+                    }
+                }
+
+                anyhow::Ok(())
+            })
+            .detach();
+
+        Ok(rx)
+    } else {
+        let mut body = String::new();
+        response.body_mut().read_to_string(&mut body).await?;
+
+        #[derive(Deserialize)]
+        struct OpenAIResponse {
+            error: OpenAIError,
+        }
+
+        #[derive(Deserialize)]
+        struct OpenAIError {
+            message: String,
+        }
+
+        match serde_json::from_str::<OpenAIResponse>(&body) {
+            Ok(response) if !response.error.message.is_empty() => Err(anyhow!(
+                "Failed to connect to OpenAI API: {}",
+                response.error.message,
+            )),
+
+            _ => Err(anyhow!(
+                "Failed to connect to OpenAI API: {} {}",
+                response.status(),
+                body,
+            )),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct OpenAICompletionProvider {
+    model: OpenAILanguageModel,
+    credential: Arc<RwLock<ProviderCredential>>,
+    executor: Arc<Executor>,
+}
+
+impl OpenAICompletionProvider {
+    pub fn new(model_name: &str, executor: Arc<Executor>) -> Self {
+        let model = OpenAILanguageModel::load(model_name);
+        let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
+        Self {
+            model,
+            credential,
+            executor,
+        }
+    }
+}
+
+#[async_trait]
+impl CredentialProvider for OpenAICompletionProvider {
+    fn has_credentials(&self) -> bool {
+        match *self.credential.read() {
+            ProviderCredential::Credentials { .. } => true,
+            _ => false,
+        }
+    }
+    async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
+        let existing_credential = self.credential.read().clone();
+
+        let retrieved_credential = cx
+            .run_on_main(move |cx| match existing_credential {
+                ProviderCredential::Credentials { .. } => {
+                    return existing_credential.clone();
+                }
+                _ => {
+                    if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
+                        return ProviderCredential::Credentials { api_key };
+                    }
+
+                    if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err()
+                    {
+                        if let Some(api_key) = String::from_utf8(api_key).log_err() {
+                            return ProviderCredential::Credentials { api_key };
+                        } else {
+                            return ProviderCredential::NoCredentials;
+                        }
+                    } else {
+                        return ProviderCredential::NoCredentials;
+                    }
+                }
+            })
+            .await;
+
+        *self.credential.write() = retrieved_credential.clone();
+        retrieved_credential
+    }
+
+    async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
+        *self.credential.write() = credential.clone();
+        let credential = credential.clone();
+        cx.run_on_main(move |cx| match credential {
+            ProviderCredential::Credentials { api_key } => {
+                cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
+                    .log_err();
+            }
+            _ => {}
+        })
+        .await;
+    }
+    async fn delete_credentials(&self, cx: &mut AppContext) {
+        cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err())
+            .await;
+        *self.credential.write() = ProviderCredential::NoCredentials;
+    }
+}
+
+impl CompletionProvider for OpenAICompletionProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
+        model
+    }
+    fn complete(
+        &self,
+        prompt: Box<dyn CompletionRequest>,
+    ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
+        // Currently the CompletionRequest for OpenAI, includes a 'model' parameter
+        // This means that the model is determined by the CompletionRequest and not the CompletionProvider,
+        // which is currently model based, due to the langauge model.
+        // At some point in the future we should rectify this.
+        let credential = self.credential.read().clone();
+        let request = stream_completion(credential, self.executor.clone(), prompt);
+        async move {
+            let response = request.await?;
+            let stream = response
+                .filter_map(|response| async move {
+                    match response {
+                        Ok(mut response) => Some(Ok(response.choices.pop()?.delta.content?)),
+                        Err(error) => Some(Err(error)),
+                    }
+                })
+                .boxed();
+            Ok(stream)
+        }
+        .boxed()
+    }
+    fn box_clone(&self) -> Box<dyn CompletionProvider> {
+        Box::new((*self).clone())
+    }
+}

crates/ai2/src/providers/open_ai/embedding.rs 🔗

@@ -0,0 +1,313 @@
+use anyhow::{anyhow, Result};
+use async_trait::async_trait;
+use futures::AsyncReadExt;
+use gpui2::Executor;
+use gpui2::{serde_json, AppContext};
+use isahc::http::StatusCode;
+use isahc::prelude::Configurable;
+use isahc::{AsyncBody, Response};
+use lazy_static::lazy_static;
+use parking_lot::{Mutex, RwLock};
+use parse_duration::parse;
+use postage::watch;
+use serde::{Deserialize, Serialize};
+use std::env;
+use std::ops::Add;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+use tiktoken_rs::{cl100k_base, CoreBPE};
+use util::http::{HttpClient, Request};
+use util::ResultExt;
+
+use crate::auth::{CredentialProvider, ProviderCredential};
+use crate::embedding::{Embedding, EmbeddingProvider};
+use crate::models::LanguageModel;
+use crate::providers::open_ai::OpenAILanguageModel;
+
+use crate::providers::open_ai::OPENAI_API_URL;
+
+lazy_static! {
+    static ref OPENAI_BPE_TOKENIZER: CoreBPE = cl100k_base().unwrap();
+}
+
+#[derive(Clone)]
+pub struct OpenAIEmbeddingProvider {
+    model: OpenAILanguageModel,
+    credential: Arc<RwLock<ProviderCredential>>,
+    pub client: Arc<dyn HttpClient>,
+    pub executor: Arc<Executor>,
+    rate_limit_count_rx: watch::Receiver<Option<Instant>>,
+    rate_limit_count_tx: Arc<Mutex<watch::Sender<Option<Instant>>>>,
+}
+
+#[derive(Serialize)]
+struct OpenAIEmbeddingRequest<'a> {
+    model: &'static str,
+    input: Vec<&'a str>,
+}
+
+#[derive(Deserialize)]
+struct OpenAIEmbeddingResponse {
+    data: Vec<OpenAIEmbedding>,
+    usage: OpenAIEmbeddingUsage,
+}
+
+#[derive(Debug, Deserialize)]
+struct OpenAIEmbedding {
+    embedding: Vec<f32>,
+    index: usize,
+    object: String,
+}
+
+#[derive(Deserialize)]
+struct OpenAIEmbeddingUsage {
+    prompt_tokens: usize,
+    total_tokens: usize,
+}
+
+impl OpenAIEmbeddingProvider {
+    pub fn new(client: Arc<dyn HttpClient>, executor: Arc<Executor>) -> Self {
+        let (rate_limit_count_tx, rate_limit_count_rx) = watch::channel_with(None);
+        let rate_limit_count_tx = Arc::new(Mutex::new(rate_limit_count_tx));
+
+        let model = OpenAILanguageModel::load("text-embedding-ada-002");
+        let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
+
+        OpenAIEmbeddingProvider {
+            model,
+            credential,
+            client,
+            executor,
+            rate_limit_count_rx,
+            rate_limit_count_tx,
+        }
+    }
+
+    fn get_api_key(&self) -> Result<String> {
+        match self.credential.read().clone() {
+            ProviderCredential::Credentials { api_key } => Ok(api_key),
+            _ => Err(anyhow!("api credentials not provided")),
+        }
+    }
+
+    fn resolve_rate_limit(&self) {
+        let reset_time = *self.rate_limit_count_tx.lock().borrow();
+
+        if let Some(reset_time) = reset_time {
+            if Instant::now() >= reset_time {
+                *self.rate_limit_count_tx.lock().borrow_mut() = None
+            }
+        }
+
+        log::trace!(
+            "resolving reset time: {:?}",
+            *self.rate_limit_count_tx.lock().borrow()
+        );
+    }
+
+    fn update_reset_time(&self, reset_time: Instant) {
+        let original_time = *self.rate_limit_count_tx.lock().borrow();
+
+        let updated_time = if let Some(original_time) = original_time {
+            if reset_time < original_time {
+                Some(reset_time)
+            } else {
+                Some(original_time)
+            }
+        } else {
+            Some(reset_time)
+        };
+
+        log::trace!("updating rate limit time: {:?}", updated_time);
+
+        *self.rate_limit_count_tx.lock().borrow_mut() = updated_time;
+    }
+    async fn send_request(
+        &self,
+        api_key: &str,
+        spans: Vec<&str>,
+        request_timeout: u64,
+    ) -> Result<Response<AsyncBody>> {
+        let request = Request::post("https://api.openai.com/v1/embeddings")
+            .redirect_policy(isahc::config::RedirectPolicy::Follow)
+            .timeout(Duration::from_secs(request_timeout))
+            .header("Content-Type", "application/json")
+            .header("Authorization", format!("Bearer {}", api_key))
+            .body(
+                serde_json::to_string(&OpenAIEmbeddingRequest {
+                    input: spans.clone(),
+                    model: "text-embedding-ada-002",
+                })
+                .unwrap()
+                .into(),
+            )?;
+
+        Ok(self.client.send(request).await?)
+    }
+}
+
+#[async_trait]
+impl CredentialProvider for OpenAIEmbeddingProvider {
+    fn has_credentials(&self) -> bool {
+        match *self.credential.read() {
+            ProviderCredential::Credentials { .. } => true,
+            _ => false,
+        }
+    }
+    async fn retrieve_credentials(&self, cx: &mut AppContext) -> ProviderCredential {
+        let existing_credential = self.credential.read().clone();
+
+        let retrieved_credential = cx
+            .run_on_main(move |cx| match existing_credential {
+                ProviderCredential::Credentials { .. } => {
+                    return existing_credential.clone();
+                }
+                _ => {
+                    if let Some(api_key) = env::var("OPENAI_API_KEY").log_err() {
+                        return ProviderCredential::Credentials { api_key };
+                    }
+
+                    if let Some(Some((_, api_key))) = cx.read_credentials(OPENAI_API_URL).log_err()
+                    {
+                        if let Some(api_key) = String::from_utf8(api_key).log_err() {
+                            return ProviderCredential::Credentials { api_key };
+                        } else {
+                            return ProviderCredential::NoCredentials;
+                        }
+                    } else {
+                        return ProviderCredential::NoCredentials;
+                    }
+                }
+            })
+            .await;
+
+        *self.credential.write() = retrieved_credential.clone();
+        retrieved_credential
+    }
+
+    async fn save_credentials(&self, cx: &mut AppContext, credential: ProviderCredential) {
+        *self.credential.write() = credential.clone();
+        let credential = credential.clone();
+        cx.run_on_main(move |cx| match credential {
+            ProviderCredential::Credentials { api_key } => {
+                cx.write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
+                    .log_err();
+            }
+            _ => {}
+        })
+        .await;
+    }
+    async fn delete_credentials(&self, cx: &mut AppContext) {
+        cx.run_on_main(move |cx| cx.delete_credentials(OPENAI_API_URL).log_err())
+            .await;
+        *self.credential.write() = ProviderCredential::NoCredentials;
+    }
+}
+
+#[async_trait]
+impl EmbeddingProvider for OpenAIEmbeddingProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
+        model
+    }
+
+    fn max_tokens_per_batch(&self) -> usize {
+        50000
+    }
+
+    fn rate_limit_expiration(&self) -> Option<Instant> {
+        *self.rate_limit_count_rx.borrow()
+    }
+
+    async fn embed_batch(&self, spans: Vec<String>) -> Result<Vec<Embedding>> {
+        const BACKOFF_SECONDS: [usize; 4] = [3, 5, 15, 45];
+        const MAX_RETRIES: usize = 4;
+
+        let api_key = self.get_api_key()?;
+
+        let mut request_number = 0;
+        let mut rate_limiting = false;
+        let mut request_timeout: u64 = 15;
+        let mut response: Response<AsyncBody>;
+        while request_number < MAX_RETRIES {
+            response = self
+                .send_request(
+                    &api_key,
+                    spans.iter().map(|x| &**x).collect(),
+                    request_timeout,
+                )
+                .await?;
+
+            request_number += 1;
+
+            match response.status() {
+                StatusCode::REQUEST_TIMEOUT => {
+                    request_timeout += 5;
+                }
+                StatusCode::OK => {
+                    let mut body = String::new();
+                    response.body_mut().read_to_string(&mut body).await?;
+                    let response: OpenAIEmbeddingResponse = serde_json::from_str(&body)?;
+
+                    log::trace!(
+                        "openai embedding completed. tokens: {:?}",
+                        response.usage.total_tokens
+                    );
+
+                    // If we complete a request successfully that was previously rate_limited
+                    // resolve the rate limit
+                    if rate_limiting {
+                        self.resolve_rate_limit()
+                    }
+
+                    return Ok(response
+                        .data
+                        .into_iter()
+                        .map(|embedding| Embedding::from(embedding.embedding))
+                        .collect());
+                }
+                StatusCode::TOO_MANY_REQUESTS => {
+                    rate_limiting = true;
+                    let mut body = String::new();
+                    response.body_mut().read_to_string(&mut body).await?;
+
+                    let delay_duration = {
+                        let delay = Duration::from_secs(BACKOFF_SECONDS[request_number - 1] as u64);
+                        if let Some(time_to_reset) =
+                            response.headers().get("x-ratelimit-reset-tokens")
+                        {
+                            if let Ok(time_str) = time_to_reset.to_str() {
+                                parse(time_str).unwrap_or(delay)
+                            } else {
+                                delay
+                            }
+                        } else {
+                            delay
+                        }
+                    };
+
+                    // If we've previously rate limited, increment the duration but not the count
+                    let reset_time = Instant::now().add(delay_duration);
+                    self.update_reset_time(reset_time);
+
+                    log::trace!(
+                        "openai rate limiting: waiting {:?} until lifted",
+                        &delay_duration
+                    );
+
+                    self.executor.timer(delay_duration).await;
+                }
+                _ => {
+                    let mut body = String::new();
+                    response.body_mut().read_to_string(&mut body).await?;
+                    return Err(anyhow!(
+                        "open ai bad request: {:?} {:?}",
+                        &response.status(),
+                        body
+                    ));
+                }
+            }
+        }
+        Err(anyhow!("openai max retries"))
+    }
+}

crates/ai2/src/providers/open_ai/mod.rs 🔗

@@ -0,0 +1,9 @@
+pub mod completion;
+pub mod embedding;
+pub mod model;
+
+pub use completion::*;
+pub use embedding::*;
+pub use model::OpenAILanguageModel;
+
+pub const OPENAI_API_URL: &'static str = "https://api.openai.com/v1";

crates/ai2/src/providers/open_ai/model.rs 🔗

@@ -0,0 +1,57 @@
+use anyhow::anyhow;
+use tiktoken_rs::CoreBPE;
+use util::ResultExt;
+
+use crate::models::{LanguageModel, TruncationDirection};
+
+#[derive(Clone)]
+pub struct OpenAILanguageModel {
+    name: String,
+    bpe: Option<CoreBPE>,
+}
+
+impl OpenAILanguageModel {
+    pub fn load(model_name: &str) -> Self {
+        let bpe = tiktoken_rs::get_bpe_from_model(model_name).log_err();
+        OpenAILanguageModel {
+            name: model_name.to_string(),
+            bpe,
+        }
+    }
+}
+
+impl LanguageModel for OpenAILanguageModel {
+    fn name(&self) -> String {
+        self.name.clone()
+    }
+    fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
+        if let Some(bpe) = &self.bpe {
+            anyhow::Ok(bpe.encode_with_special_tokens(content).len())
+        } else {
+            Err(anyhow!("bpe for open ai model was not retrieved"))
+        }
+    }
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String> {
+        if let Some(bpe) = &self.bpe {
+            let tokens = bpe.encode_with_special_tokens(content);
+            if tokens.len() > length {
+                match direction {
+                    TruncationDirection::End => bpe.decode(tokens[..length].to_vec()),
+                    TruncationDirection::Start => bpe.decode(tokens[length..].to_vec()),
+                }
+            } else {
+                bpe.decode(tokens)
+            }
+        } else {
+            Err(anyhow!("bpe for open ai model was not retrieved"))
+        }
+    }
+    fn capacity(&self) -> anyhow::Result<usize> {
+        anyhow::Ok(tiktoken_rs::model::get_context_size(&self.name))
+    }
+}

crates/ai2/src/providers/open_ai/new.rs 🔗

@@ -0,0 +1,11 @@
+pub trait LanguageModel {
+    fn name(&self) -> String;
+    fn count_tokens(&self, content: &str) -> anyhow::Result<usize>;
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String>;
+    fn capacity(&self) -> anyhow::Result<usize>;
+}

crates/ai2/src/test.rs 🔗

@@ -0,0 +1,193 @@
+use std::{
+    sync::atomic::{self, AtomicUsize, Ordering},
+    time::Instant,
+};
+
+use async_trait::async_trait;
+use futures::{channel::mpsc, future::BoxFuture, stream::BoxStream, FutureExt, StreamExt};
+use gpui2::AppContext;
+use parking_lot::Mutex;
+
+use crate::{
+    auth::{CredentialProvider, ProviderCredential},
+    completion::{CompletionProvider, CompletionRequest},
+    embedding::{Embedding, EmbeddingProvider},
+    models::{LanguageModel, TruncationDirection},
+};
+
+#[derive(Clone)]
+pub struct FakeLanguageModel {
+    pub capacity: usize,
+}
+
+impl LanguageModel for FakeLanguageModel {
+    fn name(&self) -> String {
+        "dummy".to_string()
+    }
+    fn count_tokens(&self, content: &str) -> anyhow::Result<usize> {
+        anyhow::Ok(content.chars().collect::<Vec<char>>().len())
+    }
+    fn truncate(
+        &self,
+        content: &str,
+        length: usize,
+        direction: TruncationDirection,
+    ) -> anyhow::Result<String> {
+        println!("TRYING TO TRUNCATE: {:?}", length.clone());
+
+        if length > self.count_tokens(content)? {
+            println!("NOT TRUNCATING");
+            return anyhow::Ok(content.to_string());
+        }
+
+        anyhow::Ok(match direction {
+            TruncationDirection::End => content.chars().collect::<Vec<char>>()[..length]
+                .into_iter()
+                .collect::<String>(),
+            TruncationDirection::Start => content.chars().collect::<Vec<char>>()[length..]
+                .into_iter()
+                .collect::<String>(),
+        })
+    }
+    fn capacity(&self) -> anyhow::Result<usize> {
+        anyhow::Ok(self.capacity)
+    }
+}
+
+pub struct FakeEmbeddingProvider {
+    pub embedding_count: AtomicUsize,
+}
+
+impl Clone for FakeEmbeddingProvider {
+    fn clone(&self) -> Self {
+        FakeEmbeddingProvider {
+            embedding_count: AtomicUsize::new(self.embedding_count.load(Ordering::SeqCst)),
+        }
+    }
+}
+
+impl Default for FakeEmbeddingProvider {
+    fn default() -> Self {
+        FakeEmbeddingProvider {
+            embedding_count: AtomicUsize::default(),
+        }
+    }
+}
+
+impl FakeEmbeddingProvider {
+    pub fn embedding_count(&self) -> usize {
+        self.embedding_count.load(atomic::Ordering::SeqCst)
+    }
+
+    pub fn embed_sync(&self, span: &str) -> Embedding {
+        let mut result = vec![1.0; 26];
+        for letter in span.chars() {
+            let letter = letter.to_ascii_lowercase();
+            if letter as u32 >= 'a' as u32 {
+                let ix = (letter as u32) - ('a' as u32);
+                if ix < 26 {
+                    result[ix as usize] += 1.0;
+                }
+            }
+        }
+
+        let norm = result.iter().map(|x| x * x).sum::<f32>().sqrt();
+        for x in &mut result {
+            *x /= norm;
+        }
+
+        result.into()
+    }
+}
+
+#[async_trait]
+impl CredentialProvider for FakeEmbeddingProvider {
+    fn has_credentials(&self) -> bool {
+        true
+    }
+    async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
+        ProviderCredential::NotNeeded
+    }
+    async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
+    async fn delete_credentials(&self, _cx: &mut AppContext) {}
+}
+
+#[async_trait]
+impl EmbeddingProvider for FakeEmbeddingProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        Box::new(FakeLanguageModel { capacity: 1000 })
+    }
+    fn max_tokens_per_batch(&self) -> usize {
+        1000
+    }
+
+    fn rate_limit_expiration(&self) -> Option<Instant> {
+        None
+    }
+
+    async fn embed_batch(&self, spans: Vec<String>) -> anyhow::Result<Vec<Embedding>> {
+        self.embedding_count
+            .fetch_add(spans.len(), atomic::Ordering::SeqCst);
+
+        anyhow::Ok(spans.iter().map(|span| self.embed_sync(span)).collect())
+    }
+}
+
+pub struct FakeCompletionProvider {
+    last_completion_tx: Mutex<Option<mpsc::Sender<String>>>,
+}
+
+impl Clone for FakeCompletionProvider {
+    fn clone(&self) -> Self {
+        Self {
+            last_completion_tx: Mutex::new(None),
+        }
+    }
+}
+
+impl FakeCompletionProvider {
+    pub fn new() -> Self {
+        Self {
+            last_completion_tx: Mutex::new(None),
+        }
+    }
+
+    pub fn send_completion(&self, completion: impl Into<String>) {
+        let mut tx = self.last_completion_tx.lock();
+        tx.as_mut().unwrap().try_send(completion.into()).unwrap();
+    }
+
+    pub fn finish_completion(&self) {
+        self.last_completion_tx.lock().take().unwrap();
+    }
+}
+
+#[async_trait]
+impl CredentialProvider for FakeCompletionProvider {
+    fn has_credentials(&self) -> bool {
+        true
+    }
+    async fn retrieve_credentials(&self, _cx: &mut AppContext) -> ProviderCredential {
+        ProviderCredential::NotNeeded
+    }
+    async fn save_credentials(&self, _cx: &mut AppContext, _credential: ProviderCredential) {}
+    async fn delete_credentials(&self, _cx: &mut AppContext) {}
+}
+
+impl CompletionProvider for FakeCompletionProvider {
+    fn base_model(&self) -> Box<dyn LanguageModel> {
+        let model: Box<dyn LanguageModel> = Box::new(FakeLanguageModel { capacity: 8190 });
+        model
+    }
+    fn complete(
+        &self,
+        _prompt: Box<dyn CompletionRequest>,
+    ) -> BoxFuture<'static, anyhow::Result<BoxStream<'static, anyhow::Result<String>>>> {
+        let (tx, rx) = mpsc::channel(1);
+        *self.last_completion_tx.lock() = Some(tx);
+        async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed()
+    }
+    fn box_clone(&self) -> Box<dyn CompletionProvider> {
+        Box::new((*self).clone())
+    }
+}

crates/assistant/Cargo.toml 🔗

@@ -45,6 +45,7 @@ tiktoken-rs = "0.5"
 [dev-dependencies]
 editor = { path = "../editor", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
+ai = { path = "../ai", features = ["test-support"]}
 
 ctor.workspace = true
 env_logger.workspace = true

crates/assistant/src/assistant.rs 🔗

@@ -4,7 +4,7 @@ mod codegen;
 mod prompts;
 mod streaming_diff;
 
-use ai::completion::Role;
+use ai::providers::open_ai::Role;
 use anyhow::Result;
 pub use assistant_panel::AssistantPanel;
 use assistant_settings::OpenAIModel;

crates/assistant/src/assistant_panel.rs 🔗

@@ -5,12 +5,14 @@ use crate::{
     MessageId, MessageMetadata, MessageStatus, Role, SavedConversation, SavedConversationMetadata,
     SavedMessage,
 };
+
 use ai::{
-    completion::{
-        stream_completion, OpenAICompletionProvider, OpenAIRequest, RequestMessage, OPENAI_API_URL,
-    },
-    templates::repository_context::PromptCodeSnippet,
+    auth::ProviderCredential,
+    completion::{CompletionProvider, CompletionRequest},
+    providers::open_ai::{OpenAICompletionProvider, OpenAIRequest, RequestMessage},
 };
+
+use ai::prompts::repository_context::PromptCodeSnippet;
 use anyhow::{anyhow, Result};
 use chrono::{DateTime, Local};
 use client::{telemetry::AssistantKind, ClickhouseEvent, TelemetrySettings};
@@ -43,8 +45,8 @@ use search::BufferSearchBar;
 use semantic_index::{SemanticIndex, SemanticIndexStatus};
 use settings::SettingsStore;
 use std::{
-    cell::{Cell, RefCell},
-    cmp, env,
+    cell::Cell,
+    cmp,
     fmt::Write,
     iter,
     ops::Range,
@@ -97,8 +99,8 @@ pub fn init(cx: &mut AppContext) {
     cx.capture_action(ConversationEditor::copy);
     cx.add_action(ConversationEditor::split);
     cx.capture_action(ConversationEditor::cycle_message_role);
-    cx.add_action(AssistantPanel::save_api_key);
-    cx.add_action(AssistantPanel::reset_api_key);
+    cx.add_action(AssistantPanel::save_credentials);
+    cx.add_action(AssistantPanel::reset_credentials);
     cx.add_action(AssistantPanel::toggle_zoom);
     cx.add_action(AssistantPanel::deploy);
     cx.add_action(AssistantPanel::select_next_match);
@@ -140,9 +142,8 @@ pub struct AssistantPanel {
     zoomed: bool,
     has_focus: bool,
     toolbar: ViewHandle<Toolbar>,
-    api_key: Rc<RefCell<Option<String>>>,
+    completion_provider: Box<dyn CompletionProvider>,
     api_key_editor: Option<ViewHandle<Editor>>,
-    has_read_credentials: bool,
     languages: Arc<LanguageRegistry>,
     fs: Arc<dyn Fs>,
     subscriptions: Vec<Subscription>,
@@ -202,6 +203,11 @@ impl AssistantPanel {
                     });
 
                     let semantic_index = SemanticIndex::global(cx);
+                    // Defaulting currently to GPT4, allow for this to be set via config.
+                    let completion_provider = Box::new(OpenAICompletionProvider::new(
+                        "gpt-4",
+                        cx.background().clone(),
+                    ));
 
                     let mut this = Self {
                         workspace: workspace_handle,
@@ -213,9 +219,8 @@ impl AssistantPanel {
                         zoomed: false,
                         has_focus: false,
                         toolbar,
-                        api_key: Rc::new(RefCell::new(None)),
+                        completion_provider,
                         api_key_editor: None,
-                        has_read_credentials: false,
                         languages: workspace.app_state().languages.clone(),
                         fs: workspace.app_state().fs.clone(),
                         width: None,
@@ -254,10 +259,7 @@ impl AssistantPanel {
         cx: &mut ViewContext<Workspace>,
     ) {
         let this = if let Some(this) = workspace.panel::<AssistantPanel>(cx) {
-            if this
-                .update(cx, |assistant, cx| assistant.load_api_key(cx))
-                .is_some()
-            {
+            if this.update(cx, |assistant, _| assistant.has_credentials()) {
                 this
             } else {
                 workspace.focus_panel::<AssistantPanel>(cx);
@@ -289,12 +291,6 @@ impl AssistantPanel {
         cx: &mut ViewContext<Self>,
         project: &ModelHandle<Project>,
     ) {
-        let api_key = if let Some(api_key) = self.api_key.borrow().clone() {
-            api_key
-        } else {
-            return;
-        };
-
         let selection = editor.read(cx).selections.newest_anchor().clone();
         if selection.start.excerpt_id != selection.end.excerpt_id {
             return;
@@ -325,10 +321,13 @@ impl AssistantPanel {
 
         let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
         let provider = Arc::new(OpenAICompletionProvider::new(
-            api_key,
+            "gpt-4",
             cx.background().clone(),
         ));
 
+        // Retrieve Credentials Authenticates the Provider
+        // provider.retrieve_credentials(cx);
+
         let codegen = cx.add_model(|cx| {
             Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
         });
@@ -745,13 +744,14 @@ impl AssistantPanel {
                 content: prompt,
             });
 
-            let request = OpenAIRequest {
+            let request = Box::new(OpenAIRequest {
                 model: model.full_name().into(),
                 messages,
                 stream: true,
                 stop: vec!["|END|>".to_string()],
                 temperature,
-            };
+            });
+
             codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx));
             anyhow::Ok(())
         })
@@ -811,7 +811,7 @@ impl AssistantPanel {
     fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<ConversationEditor> {
         let editor = cx.add_view(|cx| {
             ConversationEditor::new(
-                self.api_key.clone(),
+                self.completion_provider.clone(),
                 self.languages.clone(),
                 self.fs.clone(),
                 self.workspace.clone(),
@@ -870,17 +870,19 @@ impl AssistantPanel {
         }
     }
 
-    fn save_api_key(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+    fn save_credentials(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
         if let Some(api_key) = self
             .api_key_editor
             .as_ref()
             .map(|editor| editor.read(cx).text(cx))
         {
             if !api_key.is_empty() {
-                cx.platform()
-                    .write_credentials(OPENAI_API_URL, "Bearer", api_key.as_bytes())
-                    .log_err();
-                *self.api_key.borrow_mut() = Some(api_key);
+                let credential = ProviderCredential::Credentials {
+                    api_key: api_key.clone(),
+                };
+
+                self.completion_provider.save_credentials(cx, credential);
+
                 self.api_key_editor.take();
                 cx.focus_self();
                 cx.notify();
@@ -890,9 +892,8 @@ impl AssistantPanel {
         }
     }
 
-    fn reset_api_key(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
-        cx.platform().delete_credentials(OPENAI_API_URL).log_err();
-        self.api_key.take();
+    fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
+        self.completion_provider.delete_credentials(cx);
         self.api_key_editor = Some(build_api_key_editor(cx));
         cx.focus_self();
         cx.notify();
@@ -1151,13 +1152,12 @@ impl AssistantPanel {
 
         let fs = self.fs.clone();
         let workspace = self.workspace.clone();
-        let api_key = self.api_key.clone();
         let languages = self.languages.clone();
         cx.spawn(|this, mut cx| async move {
             let saved_conversation = fs.load(&path).await?;
             let saved_conversation = serde_json::from_str(&saved_conversation)?;
             let conversation = cx.add_model(|cx| {
-                Conversation::deserialize(saved_conversation, path.clone(), api_key, languages, cx)
+                Conversation::deserialize(saved_conversation, path.clone(), languages, cx)
             });
             this.update(&mut cx, |this, cx| {
                 // If, by the time we've loaded the conversation, the user has already opened
@@ -1181,30 +1181,12 @@ impl AssistantPanel {
             .position(|editor| editor.read(cx).conversation.read(cx).path.as_deref() == Some(path))
     }
 
-    fn load_api_key(&mut self, cx: &mut ViewContext<Self>) -> Option<String> {
-        if self.api_key.borrow().is_none() && !self.has_read_credentials {
-            self.has_read_credentials = true;
-            let api_key = if let Ok(api_key) = env::var("OPENAI_API_KEY") {
-                Some(api_key)
-            } else if let Some((_, api_key)) = cx
-                .platform()
-                .read_credentials(OPENAI_API_URL)
-                .log_err()
-                .flatten()
-            {
-                String::from_utf8(api_key).log_err()
-            } else {
-                None
-            };
-            if let Some(api_key) = api_key {
-                *self.api_key.borrow_mut() = Some(api_key);
-            } else if self.api_key_editor.is_none() {
-                self.api_key_editor = Some(build_api_key_editor(cx));
-                cx.notify();
-            }
-        }
+    fn has_credentials(&mut self) -> bool {
+        self.completion_provider.has_credentials()
+    }
 
-        self.api_key.borrow().clone()
+    fn load_credentials(&mut self, cx: &mut ViewContext<Self>) {
+        self.completion_provider.retrieve_credentials(cx);
     }
 }
 
@@ -1389,7 +1371,7 @@ impl Panel for AssistantPanel {
 
     fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
         if active {
-            self.load_api_key(cx);
+            self.load_credentials(cx);
 
             if self.editors.is_empty() {
                 self.new_conversation(cx);
@@ -1454,10 +1436,10 @@ struct Conversation {
     token_count: Option<usize>,
     max_token_count: usize,
     pending_token_count: Task<Option<()>>,
-    api_key: Rc<RefCell<Option<String>>>,
     pending_save: Task<Result<()>>,
     path: Option<PathBuf>,
     _subscriptions: Vec<Subscription>,
+    completion_provider: Box<dyn CompletionProvider>,
 }
 
 impl Entity for Conversation {
@@ -1466,9 +1448,9 @@ impl Entity for Conversation {
 
 impl Conversation {
     fn new(
-        api_key: Rc<RefCell<Option<String>>>,
         language_registry: Arc<LanguageRegistry>,
         cx: &mut ModelContext<Self>,
+        completion_provider: Box<dyn CompletionProvider>,
     ) -> Self {
         let markdown = language_registry.language_for_name("Markdown");
         let buffer = cx.add_model(|cx| {
@@ -1507,8 +1489,8 @@ impl Conversation {
             _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
             pending_save: Task::ready(Ok(())),
             path: None,
-            api_key,
             buffer,
+            completion_provider,
         };
         let message = MessageAnchor {
             id: MessageId(post_inc(&mut this.next_message_id.0)),
@@ -1554,7 +1536,6 @@ impl Conversation {
     fn deserialize(
         saved_conversation: SavedConversation,
         path: PathBuf,
-        api_key: Rc<RefCell<Option<String>>>,
         language_registry: Arc<LanguageRegistry>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
@@ -1563,6 +1544,10 @@ impl Conversation {
             None => Some(Uuid::new_v4().to_string()),
         };
         let model = saved_conversation.model;
+        let completion_provider: Box<dyn CompletionProvider> = Box::new(
+            OpenAICompletionProvider::new(model.full_name(), cx.background().clone()),
+        );
+        completion_provider.retrieve_credentials(cx);
         let markdown = language_registry.language_for_name("Markdown");
         let mut message_anchors = Vec::new();
         let mut next_message_id = MessageId(0);
@@ -1609,8 +1594,8 @@ impl Conversation {
             _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
             pending_save: Task::ready(Ok(())),
             path: Some(path),
-            api_key,
             buffer,
+            completion_provider,
         };
         this.count_remaining_tokens(cx);
         this
@@ -1731,11 +1716,11 @@ impl Conversation {
         }
 
         if should_assist {
-            let Some(api_key) = self.api_key.borrow().clone() else {
+            if !self.completion_provider.has_credentials() {
                 return Default::default();
-            };
+            }
 
-            let request = OpenAIRequest {
+            let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
                 model: self.model.full_name().to_string(),
                 messages: self
                     .messages(cx)
@@ -1745,9 +1730,9 @@ impl Conversation {
                 stream: true,
                 stop: vec![],
                 temperature: 1.0,
-            };
+            });
 
-            let stream = stream_completion(api_key, cx.background().clone(), request);
+            let stream = self.completion_provider.complete(request);
             let assistant_message = self
                 .insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
                 .unwrap();
@@ -1765,33 +1750,28 @@ impl Conversation {
                         let mut messages = stream.await?;
 
                         while let Some(message) = messages.next().await {
-                            let mut message = message?;
-                            if let Some(choice) = message.choices.pop() {
-                                this.upgrade(&cx)
-                                    .ok_or_else(|| anyhow!("conversation was dropped"))?
-                                    .update(&mut cx, |this, cx| {
-                                        let text: Arc<str> = choice.delta.content?.into();
-                                        let message_ix =
-                                            this.message_anchors.iter().position(|message| {
-                                                message.id == assistant_message_id
-                                            })?;
-                                        this.buffer.update(cx, |buffer, cx| {
-                                            let offset = this.message_anchors[message_ix + 1..]
-                                                .iter()
-                                                .find(|message| message.start.is_valid(buffer))
-                                                .map_or(buffer.len(), |message| {
-                                                    message
-                                                        .start
-                                                        .to_offset(buffer)
-                                                        .saturating_sub(1)
-                                                });
-                                            buffer.edit([(offset..offset, text)], None, cx);
-                                        });
-                                        cx.emit(ConversationEvent::StreamedCompletion);
-
-                                        Some(())
+                            let text = message?;
+
+                            this.upgrade(&cx)
+                                .ok_or_else(|| anyhow!("conversation was dropped"))?
+                                .update(&mut cx, |this, cx| {
+                                    let message_ix = this
+                                        .message_anchors
+                                        .iter()
+                                        .position(|message| message.id == assistant_message_id)?;
+                                    this.buffer.update(cx, |buffer, cx| {
+                                        let offset = this.message_anchors[message_ix + 1..]
+                                            .iter()
+                                            .find(|message| message.start.is_valid(buffer))
+                                            .map_or(buffer.len(), |message| {
+                                                message.start.to_offset(buffer).saturating_sub(1)
+                                            });
+                                        buffer.edit([(offset..offset, text)], None, cx);
                                     });
-                            }
+                                    cx.emit(ConversationEvent::StreamedCompletion);
+
+                                    Some(())
+                                });
                             smol::future::yield_now().await;
                         }
 
@@ -2013,57 +1993,54 @@ impl Conversation {
 
     fn summarize(&mut self, cx: &mut ModelContext<Self>) {
         if self.message_anchors.len() >= 2 && self.summary.is_none() {
-            let api_key = self.api_key.borrow().clone();
-            if let Some(api_key) = api_key {
-                let messages = self
-                    .messages(cx)
-                    .take(2)
-                    .map(|message| message.to_open_ai_message(self.buffer.read(cx)))
-                    .chain(Some(RequestMessage {
-                        role: Role::User,
-                        content:
-                            "Summarize the conversation into a short title without punctuation"
-                                .into(),
-                    }));
-                let request = OpenAIRequest {
-                    model: self.model.full_name().to_string(),
-                    messages: messages.collect(),
-                    stream: true,
-                    stop: vec![],
-                    temperature: 1.0,
-                };
+            if !self.completion_provider.has_credentials() {
+                return;
+            }
 
-                let stream = stream_completion(api_key, cx.background().clone(), request);
-                self.pending_summary = cx.spawn(|this, mut cx| {
-                    async move {
-                        let mut messages = stream.await?;
+            let messages = self
+                .messages(cx)
+                .take(2)
+                .map(|message| message.to_open_ai_message(self.buffer.read(cx)))
+                .chain(Some(RequestMessage {
+                    role: Role::User,
+                    content: "Summarize the conversation into a short title without punctuation"
+                        .into(),
+                }));
+            let request: Box<dyn CompletionRequest> = Box::new(OpenAIRequest {
+                model: self.model.full_name().to_string(),
+                messages: messages.collect(),
+                stream: true,
+                stop: vec![],
+                temperature: 1.0,
+            });
 
-                        while let Some(message) = messages.next().await {
-                            let mut message = message?;
-                            if let Some(choice) = message.choices.pop() {
-                                let text = choice.delta.content.unwrap_or_default();
-                                this.update(&mut cx, |this, cx| {
-                                    this.summary
-                                        .get_or_insert(Default::default())
-                                        .text
-                                        .push_str(&text);
-                                    cx.emit(ConversationEvent::SummaryChanged);
-                                });
-                            }
-                        }
+            let stream = self.completion_provider.complete(request);
+            self.pending_summary = cx.spawn(|this, mut cx| {
+                async move {
+                    let mut messages = stream.await?;
 
+                    while let Some(message) = messages.next().await {
+                        let text = message?;
                         this.update(&mut cx, |this, cx| {
-                            if let Some(summary) = this.summary.as_mut() {
-                                summary.done = true;
-                                cx.emit(ConversationEvent::SummaryChanged);
-                            }
+                            this.summary
+                                .get_or_insert(Default::default())
+                                .text
+                                .push_str(&text);
+                            cx.emit(ConversationEvent::SummaryChanged);
                         });
-
-                        anyhow::Ok(())
                     }
-                    .log_err()
-                });
-            }
+
+                    this.update(&mut cx, |this, cx| {
+                        if let Some(summary) = this.summary.as_mut() {
+                            summary.done = true;
+                            cx.emit(ConversationEvent::SummaryChanged);
+                        }
+                    });
+
+                    anyhow::Ok(())
+                }
+                .log_err()
+            });
         }
     }
 
@@ -2224,13 +2201,14 @@ struct ConversationEditor {
 
 impl ConversationEditor {
     fn new(
-        api_key: Rc<RefCell<Option<String>>>,
+        completion_provider: Box<dyn CompletionProvider>,
         language_registry: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         workspace: WeakViewHandle<Workspace>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
-        let conversation = cx.add_model(|cx| Conversation::new(api_key, language_registry, cx));
+        let conversation =
+            cx.add_model(|cx| Conversation::new(language_registry, cx, completion_provider));
         Self::for_conversation(conversation, fs, workspace, cx)
     }
 
@@ -3419,6 +3397,7 @@ fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
 mod tests {
     use super::*;
     use crate::MessageId;
+    use ai::test::FakeCompletionProvider;
     use gpui::AppContext;
 
     #[gpui::test]
@@ -3426,7 +3405,9 @@ mod tests {
         cx.set_global(SettingsStore::test(cx));
         init(cx);
         let registry = Arc::new(LanguageRegistry::test());
-        let conversation = cx.add_model(|cx| Conversation::new(Default::default(), registry, cx));
+
+        let completion_provider = Box::new(FakeCompletionProvider::new());
+        let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
         let buffer = conversation.read(cx).buffer.clone();
 
         let message_1 = conversation.read(cx).message_anchors[0].clone();
@@ -3554,7 +3535,9 @@ mod tests {
         cx.set_global(SettingsStore::test(cx));
         init(cx);
         let registry = Arc::new(LanguageRegistry::test());
-        let conversation = cx.add_model(|cx| Conversation::new(Default::default(), registry, cx));
+        let completion_provider = Box::new(FakeCompletionProvider::new());
+
+        let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
         let buffer = conversation.read(cx).buffer.clone();
 
         let message_1 = conversation.read(cx).message_anchors[0].clone();
@@ -3650,7 +3633,8 @@ mod tests {
         cx.set_global(SettingsStore::test(cx));
         init(cx);
         let registry = Arc::new(LanguageRegistry::test());
-        let conversation = cx.add_model(|cx| Conversation::new(Default::default(), registry, cx));
+        let completion_provider = Box::new(FakeCompletionProvider::new());
+        let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
         let buffer = conversation.read(cx).buffer.clone();
 
         let message_1 = conversation.read(cx).message_anchors[0].clone();
@@ -3732,8 +3716,9 @@ mod tests {
         cx.set_global(SettingsStore::test(cx));
         init(cx);
         let registry = Arc::new(LanguageRegistry::test());
+        let completion_provider = Box::new(FakeCompletionProvider::new());
         let conversation =
-            cx.add_model(|cx| Conversation::new(Default::default(), registry.clone(), cx));
+            cx.add_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
         let buffer = conversation.read(cx).buffer.clone();
         let message_0 = conversation.read(cx).message_anchors[0].id;
         let message_1 = conversation.update(cx, |conversation, cx| {
@@ -3770,7 +3755,6 @@ mod tests {
             Conversation::deserialize(
                 conversation.read(cx).serialize(cx),
                 Default::default(),
-                Default::default(),
                 registry.clone(),
                 cx,
             )

crates/assistant/src/codegen.rs 🔗

@@ -1,5 +1,5 @@
 use crate::streaming_diff::{Hunk, StreamingDiff};
-use ai::completion::{CompletionProvider, OpenAIRequest};
+use ai::completion::{CompletionProvider, CompletionRequest};
 use anyhow::Result;
 use editor::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
 use futures::{channel::mpsc, SinkExt, Stream, StreamExt};
@@ -96,7 +96,7 @@ impl Codegen {
         self.error.as_ref()
     }
 
-    pub fn start(&mut self, prompt: OpenAIRequest, cx: &mut ModelContext<Self>) {
+    pub fn start(&mut self, prompt: Box<dyn CompletionRequest>, cx: &mut ModelContext<Self>) {
         let range = self.range();
         let snapshot = self.snapshot.clone();
         let selected_text = snapshot
@@ -336,17 +336,25 @@ fn strip_markdown_codeblock(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use futures::{
-        future::BoxFuture,
-        stream::{self, BoxStream},
-    };
+    use ai::test::FakeCompletionProvider;
+    use futures::stream::{self};
     use gpui::{executor::Deterministic, TestAppContext};
     use indoc::indoc;
     use language::{language_settings, tree_sitter_rust, Buffer, Language, LanguageConfig, Point};
-    use parking_lot::Mutex;
     use rand::prelude::*;
+    use serde::Serialize;
     use settings::SettingsStore;
-    use smol::future::FutureExt;
+
+    #[derive(Serialize)]
+    pub struct DummyCompletionRequest {
+        pub name: String,
+    }
+
+    impl CompletionRequest for DummyCompletionRequest {
+        fn data(&self) -> serde_json::Result<String> {
+            serde_json::to_string(self)
+        }
+    }
 
     #[gpui::test(iterations = 10)]
     async fn test_transform_autoindent(
@@ -372,7 +380,7 @@ mod tests {
             let snapshot = buffer.snapshot(cx);
             snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(4, 5))
         });
-        let provider = Arc::new(TestCompletionProvider::new());
+        let provider = Arc::new(FakeCompletionProvider::new());
         let codegen = cx.add_model(|cx| {
             Codegen::new(
                 buffer.clone(),
@@ -381,7 +389,11 @@ mod tests {
                 cx,
             )
         });
-        codegen.update(cx, |codegen, cx| codegen.start(Default::default(), cx));
+
+        let request = Box::new(DummyCompletionRequest {
+            name: "test".to_string(),
+        });
+        codegen.update(cx, |codegen, cx| codegen.start(request, cx));
 
         let mut new_text = concat!(
             "       let mut x = 0;\n",
@@ -434,7 +446,7 @@ mod tests {
             let snapshot = buffer.snapshot(cx);
             snapshot.anchor_before(Point::new(1, 6))
         });
-        let provider = Arc::new(TestCompletionProvider::new());
+        let provider = Arc::new(FakeCompletionProvider::new());
         let codegen = cx.add_model(|cx| {
             Codegen::new(
                 buffer.clone(),
@@ -443,7 +455,11 @@ mod tests {
                 cx,
             )
         });
-        codegen.update(cx, |codegen, cx| codegen.start(Default::default(), cx));
+
+        let request = Box::new(DummyCompletionRequest {
+            name: "test".to_string(),
+        });
+        codegen.update(cx, |codegen, cx| codegen.start(request, cx));
 
         let mut new_text = concat!(
             "t mut x = 0;\n",
@@ -496,7 +512,7 @@ mod tests {
             let snapshot = buffer.snapshot(cx);
             snapshot.anchor_before(Point::new(1, 2))
         });
-        let provider = Arc::new(TestCompletionProvider::new());
+        let provider = Arc::new(FakeCompletionProvider::new());
         let codegen = cx.add_model(|cx| {
             Codegen::new(
                 buffer.clone(),
@@ -505,7 +521,11 @@ mod tests {
                 cx,
             )
         });
-        codegen.update(cx, |codegen, cx| codegen.start(Default::default(), cx));
+
+        let request = Box::new(DummyCompletionRequest {
+            name: "test".to_string(),
+        });
+        codegen.update(cx, |codegen, cx| codegen.start(request, cx));
 
         let mut new_text = concat!(
             "let mut x = 0;\n",
@@ -593,38 +613,6 @@ mod tests {
         }
     }
 
-    struct TestCompletionProvider {
-        last_completion_tx: Mutex<Option<mpsc::Sender<String>>>,
-    }
-
-    impl TestCompletionProvider {
-        fn new() -> Self {
-            Self {
-                last_completion_tx: Mutex::new(None),
-            }
-        }
-
-        fn send_completion(&self, completion: impl Into<String>) {
-            let mut tx = self.last_completion_tx.lock();
-            tx.as_mut().unwrap().try_send(completion.into()).unwrap();
-        }
-
-        fn finish_completion(&self) {
-            self.last_completion_tx.lock().take().unwrap();
-        }
-    }
-
-    impl CompletionProvider for TestCompletionProvider {
-        fn complete(
-            &self,
-            _prompt: OpenAIRequest,
-        ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> {
-            let (tx, rx) = mpsc::channel(1);
-            *self.last_completion_tx.lock() = Some(tx);
-            async move { Ok(rx.map(|rx| Ok(rx)).boxed()) }.boxed()
-        }
-    }
-
     fn rust_lang() -> Language {
         Language::new(
             LanguageConfig {

crates/assistant/src/prompts.rs 🔗

@@ -1,9 +1,10 @@
-use ai::models::{LanguageModel, OpenAILanguageModel};
-use ai::templates::base::{PromptArguments, PromptChain, PromptPriority, PromptTemplate};
-use ai::templates::file_context::FileContext;
-use ai::templates::generate::GenerateInlineContent;
-use ai::templates::preamble::EngineerPreamble;
-use ai::templates::repository_context::{PromptCodeSnippet, RepositoryContext};
+use ai::models::LanguageModel;
+use ai::prompts::base::{PromptArguments, PromptChain, PromptPriority, PromptTemplate};
+use ai::prompts::file_context::FileContext;
+use ai::prompts::generate::GenerateInlineContent;
+use ai::prompts::preamble::EngineerPreamble;
+use ai::prompts::repository_context::{PromptCodeSnippet, RepositoryContext};
+use ai::providers::open_ai::OpenAILanguageModel;
 use language::{BufferSnapshot, OffsetRangeExt, ToOffset};
 use std::cmp::{self, Reverse};
 use std::ops::Range;

crates/call2/Cargo.toml 🔗

@@ -25,7 +25,7 @@ collections = { path = "../collections" }
 gpui2 = { path = "../gpui2" }
 log.workspace = true
 live_kit_client = { path = "../live_kit_client" }
-fs = { path = "../fs" }
+fs2 = { path = "../fs2" }
 language2 = { path = "../language2" }
 media = { path = "../media" }
 project2 = { path = "../project2" }
@@ -43,7 +43,7 @@ serde_derive.workspace = true
 
 [dev-dependencies]
 client2 = { path = "../client2", features = ["test-support"] }
-fs = { path = "../fs", features = ["test-support"] }
+fs2 = { path = "../fs2", features = ["test-support"] }
 language2 = { path = "../language2", features = ["test-support"] }
 collections = { path = "../collections", features = ["test-support"] }
 gpui2 = { path = "../gpui2", features = ["test-support"] }

crates/call2/src/call2.rs 🔗

@@ -12,8 +12,8 @@ use client2::{
 use collections::HashSet;
 use futures::{future::Shared, FutureExt};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Subscription, Task,
-    WeakHandle,
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Subscription, Task,
+    WeakModel,
 };
 use postage::watch;
 use project2::Project;
@@ -23,10 +23,10 @@ use std::sync::Arc;
 pub use participant::ParticipantLocation;
 pub use room::Room;
 
-pub fn init(client: Arc<Client>, user_store: Handle<UserStore>, cx: &mut AppContext) {
+pub fn init(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut AppContext) {
     CallSettings::register(cx);
 
-    let active_call = cx.entity(|cx| ActiveCall::new(client, user_store, cx));
+    let active_call = cx.build_model(|cx| ActiveCall::new(client, user_store, cx));
     cx.set_global(active_call);
 }
 
@@ -40,16 +40,16 @@ pub struct IncomingCall {
 
 /// Singleton global maintaining the user's participation in a room across workspaces.
 pub struct ActiveCall {
-    room: Option<(Handle<Room>, Vec<Subscription>)>,
-    pending_room_creation: Option<Shared<Task<Result<Handle<Room>, Arc<anyhow::Error>>>>>,
-    location: Option<WeakHandle<Project>>,
+    room: Option<(Model<Room>, Vec<Subscription>)>,
+    pending_room_creation: Option<Shared<Task<Result<Model<Room>, Arc<anyhow::Error>>>>>,
+    location: Option<WeakModel<Project>>,
     pending_invites: HashSet<u64>,
     incoming_call: (
         watch::Sender<Option<IncomingCall>>,
         watch::Receiver<Option<IncomingCall>>,
     ),
     client: Arc<Client>,
-    user_store: Handle<UserStore>,
+    user_store: Model<UserStore>,
     _subscriptions: Vec<client2::Subscription>,
 }
 
@@ -58,11 +58,7 @@ impl EventEmitter for ActiveCall {
 }
 
 impl ActiveCall {
-    fn new(
-        client: Arc<Client>,
-        user_store: Handle<UserStore>,
-        cx: &mut ModelContext<Self>,
-    ) -> Self {
+    fn new(client: Arc<Client>, user_store: Model<UserStore>, cx: &mut ModelContext<Self>) -> Self {
         Self {
             room: None,
             pending_room_creation: None,
@@ -84,7 +80,7 @@ impl ActiveCall {
     }
 
     async fn handle_incoming_call(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::IncomingCall>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -112,7 +108,7 @@ impl ActiveCall {
     }
 
     async fn handle_call_canceled(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CallCanceled>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -129,14 +125,14 @@ impl ActiveCall {
         Ok(())
     }
 
-    pub fn global(cx: &AppContext) -> Handle<Self> {
-        cx.global::<Handle<Self>>().clone()
+    pub fn global(cx: &AppContext) -> Model<Self> {
+        cx.global::<Model<Self>>().clone()
     }
 
     pub fn invite(
         &mut self,
         called_user_id: u64,
-        initial_project: Option<Handle<Project>>,
+        initial_project: Option<Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if !self.pending_invites.insert(called_user_id) {
@@ -291,7 +287,7 @@ impl ActiveCall {
         &mut self,
         channel_id: u64,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Room>>> {
+    ) -> Task<Result<Model<Room>>> {
         if let Some(room) = self.room().cloned() {
             if room.read(cx).channel_id() == Some(channel_id) {
                 return Task::ready(Ok(room));
@@ -327,7 +323,7 @@ impl ActiveCall {
 
     pub fn share_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<u64>> {
         if let Some((room, _)) = self.room.as_ref() {
@@ -340,7 +336,7 @@ impl ActiveCall {
 
     pub fn unshare_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         if let Some((room, _)) = self.room.as_ref() {
@@ -351,13 +347,13 @@ impl ActiveCall {
         }
     }
 
-    pub fn location(&self) -> Option<&WeakHandle<Project>> {
+    pub fn location(&self) -> Option<&WeakModel<Project>> {
         self.location.as_ref()
     }
 
     pub fn set_location(
         &mut self,
-        project: Option<&Handle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if project.is_some() || !*ZED_ALWAYS_ACTIVE {
@@ -371,7 +367,7 @@ impl ActiveCall {
 
     fn set_room(
         &mut self,
-        room: Option<Handle<Room>>,
+        room: Option<Model<Room>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if room.as_ref() != self.room.as_ref().map(|room| &room.0) {
@@ -407,7 +403,7 @@ impl ActiveCall {
         }
     }
 
-    pub fn room(&self) -> Option<&Handle<Room>> {
+    pub fn room(&self) -> Option<&Model<Room>> {
         self.room.as_ref().map(|(room, _)| room)
     }
 

crates/call2/src/participant.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::{anyhow, Result};
 use client2::ParticipantIndex;
 use client2::{proto, User};
-use gpui2::WeakHandle;
+use gpui2::WeakModel;
 pub use live_kit_client::Frame;
 use project2::Project;
 use std::{fmt, sync::Arc};
@@ -33,7 +33,7 @@ impl ParticipantLocation {
 #[derive(Clone, Default)]
 pub struct LocalParticipant {
     pub projects: Vec<proto::ParticipantProject>,
-    pub active_project: Option<WeakHandle<Project>>,
+    pub active_project: Option<WeakModel<Project>>,
 }
 
 #[derive(Clone, Debug)]

crates/call2/src/room.rs 🔗

@@ -13,10 +13,10 @@ use client2::{
     Client, ParticipantIndex, TypedEnvelope, User, UserStore,
 };
 use collections::{BTreeMap, HashMap, HashSet};
-use fs::Fs;
+use fs2::Fs;
 use futures::{FutureExt, StreamExt};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Handle, ModelContext, Task, WeakHandle,
+    AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task, WeakModel,
 };
 use language2::LanguageRegistry;
 use live_kit_client::{LocalTrackPublication, RemoteAudioTrackUpdate, RemoteVideoTrackUpdate};
@@ -61,8 +61,8 @@ pub struct Room {
     channel_id: Option<u64>,
     // live_kit: Option<LiveKitRoom>,
     status: RoomStatus,
-    shared_projects: HashSet<WeakHandle<Project>>,
-    joined_projects: HashSet<WeakHandle<Project>>,
+    shared_projects: HashSet<WeakModel<Project>>,
+    joined_projects: HashSet<WeakModel<Project>>,
     local_participant: LocalParticipant,
     remote_participants: BTreeMap<u64, RemoteParticipant>,
     pending_participants: Vec<Arc<User>>,
@@ -70,7 +70,7 @@ pub struct Room {
     pending_call_count: usize,
     leave_when_empty: bool,
     client: Arc<Client>,
-    user_store: Handle<UserStore>,
+    user_store: Model<UserStore>,
     follows_by_leader_id_project_id: HashMap<(PeerId, u64), Vec<PeerId>>,
     client_subscriptions: Vec<client2::Subscription>,
     _subscriptions: Vec<gpui2::Subscription>,
@@ -111,7 +111,7 @@ impl Room {
         channel_id: Option<u64>,
         live_kit_connection_info: Option<proto::LiveKitConnectionInfo>,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
         todo!()
@@ -237,15 +237,15 @@ impl Room {
 
     pub(crate) fn create(
         called_user_id: u64,
-        initial_project: Option<Handle<Project>>,
+        initial_project: Option<Model<Project>>,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut AppContext,
-    ) -> Task<Result<Handle<Self>>> {
+    ) -> Task<Result<Model<Self>>> {
         cx.spawn(move |mut cx| async move {
             let response = client.request(proto::CreateRoom {}).await?;
             let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-            let room = cx.entity(|cx| {
+            let room = cx.build_model(|cx| {
                 Self::new(
                     room_proto.id,
                     None,
@@ -283,9 +283,9 @@ impl Room {
     pub(crate) fn join_channel(
         channel_id: u64,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut AppContext,
-    ) -> Task<Result<Handle<Self>>> {
+    ) -> Task<Result<Model<Self>>> {
         cx.spawn(move |cx| async move {
             Self::from_join_response(
                 client.request(proto::JoinChannel { channel_id }).await?,
@@ -299,9 +299,9 @@ impl Room {
     pub(crate) fn join(
         call: &IncomingCall,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         cx: &mut AppContext,
-    ) -> Task<Result<Handle<Self>>> {
+    ) -> Task<Result<Model<Self>>> {
         let id = call.room_id;
         cx.spawn(move |cx| async move {
             Self::from_join_response(
@@ -343,11 +343,11 @@ impl Room {
     fn from_join_response(
         response: proto::JoinRoomResponse,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         mut cx: AsyncAppContext,
-    ) -> Result<Handle<Self>> {
+    ) -> Result<Model<Self>> {
         let room_proto = response.room.ok_or_else(|| anyhow!("invalid room"))?;
-        let room = cx.entity(|cx| {
+        let room = cx.build_model(|cx| {
             Self::new(
                 room_proto.id,
                 response.channel_id,
@@ -424,7 +424,7 @@ impl Room {
     }
 
     async fn maintain_connection(
-        this: WeakHandle<Self>,
+        this: WeakModel<Self>,
         client: Arc<Client>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
@@ -661,7 +661,7 @@ impl Room {
     }
 
     async fn handle_room_updated(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::RoomUpdated>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -1101,7 +1101,7 @@ impl Room {
         language_registry: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Project>>> {
+    ) -> Task<Result<Model<Project>>> {
         let client = self.client.clone();
         let user_store = self.user_store.clone();
         cx.emit(Event::RemoteProjectJoined { project_id: id });
@@ -1125,7 +1125,7 @@ impl Room {
 
     pub(crate) fn share_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<u64>> {
         if let Some(project_id) = project.read(cx).remote_id() {
@@ -1161,7 +1161,7 @@ impl Room {
 
     pub(crate) fn unshare_project(
         &mut self,
-        project: Handle<Project>,
+        project: Model<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         let project_id = match project.read(cx).remote_id() {
@@ -1175,7 +1175,7 @@ impl Room {
 
     pub(crate) fn set_location(
         &mut self,
-        project: Option<&Handle<Project>>,
+        project: Option<&Model<Project>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         if self.status.is_offline() {

crates/client2/src/client2.rs 🔗

@@ -14,8 +14,8 @@ use futures::{
     future::BoxFuture, AsyncReadExt, FutureExt, SinkExt, StreamExt, TryFutureExt as _, TryStreamExt,
 };
 use gpui2::{
-    serde_json, AnyHandle, AnyWeakHandle, AppContext, AsyncAppContext, Handle, SemanticVersion,
-    Task, WeakHandle,
+    serde_json, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Model, SemanticVersion, Task,
+    WeakModel,
 };
 use lazy_static::lazy_static;
 use parking_lot::RwLock;
@@ -227,7 +227,7 @@ struct ClientState {
     _reconnect_task: Option<Task<()>>,
     reconnect_interval: Duration,
     entities_by_type_and_remote_id: HashMap<(TypeId, u64), WeakSubscriber>,
-    models_by_message_type: HashMap<TypeId, AnyWeakHandle>,
+    models_by_message_type: HashMap<TypeId, AnyWeakModel>,
     entity_types_by_message_type: HashMap<TypeId, TypeId>,
     #[allow(clippy::type_complexity)]
     message_handlers: HashMap<
@@ -236,7 +236,7 @@ struct ClientState {
             dyn Send
                 + Sync
                 + Fn(
-                    AnyHandle,
+                    AnyModel,
                     Box<dyn AnyTypedEnvelope>,
                     &Arc<Client>,
                     AsyncAppContext,
@@ -246,7 +246,7 @@ struct ClientState {
 }
 
 enum WeakSubscriber {
-    Entity { handle: AnyWeakHandle },
+    Entity { handle: AnyWeakModel },
     Pending(Vec<Box<dyn AnyTypedEnvelope>>),
 }
 
@@ -314,7 +314,7 @@ impl<T> PendingEntitySubscription<T>
 where
     T: 'static + Send,
 {
-    pub fn set_model(mut self, model: &Handle<T>, cx: &mut AsyncAppContext) -> Subscription {
+    pub fn set_model(mut self, model: &Model<T>, cx: &mut AsyncAppContext) -> Subscription {
         self.consumed = true;
         let mut state = self.client.state.write();
         let id = (TypeId::of::<T>(), self.remote_id);
@@ -552,13 +552,13 @@ impl Client {
     #[track_caller]
     pub fn add_message_handler<M, E, H, F>(
         self: &Arc<Self>,
-        entity: WeakHandle<E>,
+        entity: WeakModel<E>,
         handler: H,
     ) -> Subscription
     where
         M: EnvelopedMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<()>> + Send,
     {
         let message_type_id = TypeId::of::<M>();
@@ -594,13 +594,13 @@ impl Client {
 
     pub fn add_request_handler<M, E, H, F>(
         self: &Arc<Self>,
-        model: WeakHandle<E>,
+        model: WeakModel<E>,
         handler: H,
     ) -> Subscription
     where
         M: RequestMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<M::Response>> + Send,
     {
         self.add_message_handler(model, move |handle, envelope, this, cx| {
@@ -616,7 +616,7 @@ impl Client {
     where
         M: EntityMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<()>> + Send,
     {
         self.add_entity_message_handler::<M, E, _, _>(move |subscriber, message, client, cx| {
@@ -628,7 +628,7 @@ impl Client {
     where
         M: EntityMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(AnyHandle, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(AnyModel, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<()>> + Send,
     {
         let model_type_id = TypeId::of::<E>();
@@ -667,7 +667,7 @@ impl Client {
     where
         M: EntityMessage + RequestMessage,
         E: 'static + Send,
-        H: 'static + Send + Sync + Fn(Handle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+        H: 'static + Send + Sync + Fn(Model<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<M::Response>> + Send,
     {
         self.add_model_message_handler(move |entity, envelope, client, cx| {
@@ -1546,7 +1546,7 @@ mod tests {
         let (done_tx1, mut done_rx1) = smol::channel::unbounded();
         let (done_tx2, mut done_rx2) = smol::channel::unbounded();
         client.add_model_message_handler(
-            move |model: Handle<Model>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
+            move |model: Model<TestModel>, _: TypedEnvelope<proto::JoinProject>, _, mut cx| {
                 match model.update(&mut cx, |model, _| model.id).unwrap() {
                     1 => done_tx1.try_send(()).unwrap(),
                     2 => done_tx2.try_send(()).unwrap(),
@@ -1555,15 +1555,15 @@ mod tests {
                 async { Ok(()) }
             },
         );
-        let model1 = cx.entity(|_| Model {
+        let model1 = cx.build_model(|_| TestModel {
             id: 1,
             subscription: None,
         });
-        let model2 = cx.entity(|_| Model {
+        let model2 = cx.build_model(|_| TestModel {
             id: 2,
             subscription: None,
         });
-        let model3 = cx.entity(|_| Model {
+        let model3 = cx.build_model(|_| TestModel {
             id: 3,
             subscription: None,
         });
@@ -1596,7 +1596,7 @@ mod tests {
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let server = FakeServer::for_client(user_id, &client, cx).await;
 
-        let model = cx.entity(|_| Model::default());
+        let model = cx.build_model(|_| TestModel::default());
         let (done_tx1, _done_rx1) = smol::channel::unbounded();
         let (done_tx2, mut done_rx2) = smol::channel::unbounded();
         let subscription1 = client.add_message_handler(
@@ -1624,11 +1624,11 @@ mod tests {
         let client = cx.update(|cx| Client::new(FakeHttpClient::with_404_response(), cx));
         let server = FakeServer::for_client(user_id, &client, cx).await;
 
-        let model = cx.entity(|_| Model::default());
+        let model = cx.build_model(|_| TestModel::default());
         let (done_tx, mut done_rx) = smol::channel::unbounded();
         let subscription = client.add_message_handler(
             model.clone().downgrade(),
-            move |model: Handle<Model>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
+            move |model: Model<TestModel>, _: TypedEnvelope<proto::Ping>, _, mut cx| {
                 model
                     .update(&mut cx, |model, _| model.subscription.take())
                     .unwrap();
@@ -1644,7 +1644,7 @@ mod tests {
     }
 
     #[derive(Default)]
-    struct Model {
+    struct TestModel {
         id: usize,
         subscription: Option<Subscription>,
     }

crates/client2/src/telemetry.rs 🔗

@@ -5,7 +5,9 @@ use parking_lot::Mutex;
 use serde::Serialize;
 use settings2::Settings;
 use std::{env, io::Write, mem, path::PathBuf, sync::Arc, time::Duration};
-use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt};
+use sysinfo::{
+    CpuRefreshKind, Pid, PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt,
+};
 use tempfile::NamedTempFile;
 use util::http::HttpClient;
 use util::{channel::ReleaseChannel, TryFutureExt};
@@ -161,8 +163,16 @@ impl Telemetry {
 
         let this = self.clone();
         cx.spawn(|cx| async move {
-            let mut system = System::new_all();
-            system.refresh_all();
+            // Avoiding calling `System::new_all()`, as there have been crashes related to it
+            let refresh_kind = RefreshKind::new()
+                .with_memory() // For memory usage
+                .with_processes(ProcessRefreshKind::everything()) // For process usage
+                .with_cpu(CpuRefreshKind::everything()); // For core count
+
+            let mut system = System::new_with_specifics(refresh_kind);
+
+            // Avoiding calling `refresh_all()`, just update what we need
+            system.refresh_specifics(refresh_kind);
 
             loop {
                 // Waiting some amount of time before the first query is important to get a reasonable value
@@ -170,8 +180,7 @@ impl Telemetry {
                 const DURATION_BETWEEN_SYSTEM_EVENTS: Duration = Duration::from_secs(60);
                 smol::Timer::after(DURATION_BETWEEN_SYSTEM_EVENTS).await;
 
-                system.refresh_memory();
-                system.refresh_processes();
+                system.refresh_specifics(refresh_kind);
 
                 let current_process = Pid::from_u32(std::process::id());
                 let Some(process) = system.processes().get(&current_process) else {

crates/client2/src/test.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{Client, Connection, Credentials, EstablishConnectionError, UserStore};
 use anyhow::{anyhow, Result};
 use futures::{stream::BoxStream, StreamExt};
-use gpui2::{Context, Executor, Handle, TestAppContext};
+use gpui2::{Context, Executor, Model, TestAppContext};
 use parking_lot::Mutex;
 use rpc2::{
     proto::{self, GetPrivateUserInfo, GetPrivateUserInfoResponse},
@@ -194,9 +194,9 @@ impl FakeServer {
         &self,
         client: Arc<Client>,
         cx: &mut TestAppContext,
-    ) -> Handle<UserStore> {
+    ) -> Model<UserStore> {
         let http_client = FakeHttpClient::with_404_response();
-        let user_store = cx.entity(|cx| UserStore::new(client, http_client, cx));
+        let user_store = cx.build_model(|cx| UserStore::new(client, http_client, cx));
         assert_eq!(
             self.receive::<proto::GetUsers>()
                 .await

crates/client2/src/user.rs 🔗

@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
 use collections::{hash_map::Entry, HashMap, HashSet};
 use feature_flags2::FeatureFlagAppExt;
 use futures::{channel::mpsc, future, AsyncReadExt, Future, StreamExt};
-use gpui2::{AsyncAppContext, EventEmitter, Handle, ImageData, ModelContext, Task};
+use gpui2::{AsyncAppContext, EventEmitter, ImageData, Model, ModelContext, Task};
 use postage::{sink::Sink, watch};
 use rpc2::proto::{RequestMessage, UsersResponse};
 use std::sync::{Arc, Weak};
@@ -213,7 +213,7 @@ impl UserStore {
     }
 
     async fn handle_update_invite_info(
-        this: Handle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateInviteInfo>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -229,7 +229,7 @@ impl UserStore {
     }
 
     async fn handle_show_contacts(
-        this: Handle<Self>,
+        this: Model<Self>,
         _: TypedEnvelope<proto::ShowContacts>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -243,7 +243,7 @@ impl UserStore {
     }
 
     async fn handle_update_contacts(
-        this: Handle<Self>,
+        this: Model<Self>,
         message: TypedEnvelope<proto::UpdateContacts>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -690,7 +690,7 @@ impl User {
 impl Contact {
     async fn from_proto(
         contact: proto::Contact,
-        user_store: &Handle<UserStore>,
+        user_store: &Model<UserStore>,
         cx: &mut AsyncAppContext,
     ) -> Result<Self> {
         let user = user_store

crates/copilot2/src/copilot2.rs 🔗

@@ -7,8 +7,8 @@ use async_tar::Archive;
 use collections::{HashMap, HashSet};
 use futures::{channel::oneshot, future::Shared, Future, FutureExt, TryFutureExt};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EntityId, EventEmitter, Handle, ModelContext, Task,
-    WeakHandle,
+    AppContext, AsyncAppContext, Context, EntityId, EventEmitter, Model, ModelContext, Task,
+    WeakModel,
 };
 use language2::{
     language_settings::{all_language_settings, language_settings},
@@ -49,7 +49,7 @@ pub fn init(
     node_runtime: Arc<dyn NodeRuntime>,
     cx: &mut AppContext,
 ) {
-    let copilot = cx.entity({
+    let copilot = cx.build_model({
         let node_runtime = node_runtime.clone();
         move |cx| Copilot::start(new_server_id, http, node_runtime, cx)
     });
@@ -183,7 +183,7 @@ struct RegisteredBuffer {
 impl RegisteredBuffer {
     fn report_changes(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Copilot>,
     ) -> oneshot::Receiver<(i32, BufferSnapshot)> {
         let (done_tx, done_rx) = oneshot::channel();
@@ -278,7 +278,7 @@ pub struct Copilot {
     http: Arc<dyn HttpClient>,
     node_runtime: Arc<dyn NodeRuntime>,
     server: CopilotServer,
-    buffers: HashSet<WeakHandle<Buffer>>,
+    buffers: HashSet<WeakModel<Buffer>>,
     server_id: LanguageServerId,
     _subscription: gpui2::Subscription,
 }
@@ -292,9 +292,9 @@ impl EventEmitter for Copilot {
 }
 
 impl Copilot {
-    pub fn global(cx: &AppContext) -> Option<Handle<Self>> {
-        if cx.has_global::<Handle<Self>>() {
-            Some(cx.global::<Handle<Self>>().clone())
+    pub fn global(cx: &AppContext) -> Option<Model<Self>> {
+        if cx.has_global::<Model<Self>>() {
+            Some(cx.global::<Model<Self>>().clone())
         } else {
             None
         }
@@ -383,7 +383,7 @@ impl Copilot {
         new_server_id: LanguageServerId,
         http: Arc<dyn HttpClient>,
         node_runtime: Arc<dyn NodeRuntime>,
-        this: WeakHandle<Self>,
+        this: WeakModel<Self>,
         mut cx: AsyncAppContext,
     ) -> impl Future<Output = ()> {
         async move {
@@ -590,7 +590,7 @@ impl Copilot {
         }
     }
 
-    pub fn register_buffer(&mut self, buffer: &Handle<Buffer>, cx: &mut ModelContext<Self>) {
+    pub fn register_buffer(&mut self, buffer: &Model<Buffer>, cx: &mut ModelContext<Self>) {
         let weak_buffer = buffer.downgrade();
         self.buffers.insert(weak_buffer.clone());
 
@@ -646,7 +646,7 @@ impl Copilot {
 
     fn handle_buffer_event(
         &mut self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         event: &language2::Event,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
@@ -706,7 +706,7 @@ impl Copilot {
         Ok(())
     }
 
-    fn unregister_buffer(&mut self, buffer: &WeakHandle<Buffer>) {
+    fn unregister_buffer(&mut self, buffer: &WeakModel<Buffer>) {
         if let Ok(server) = self.server.as_running() {
             if let Some(buffer) = server.registered_buffers.remove(&buffer.entity_id()) {
                 server
@@ -723,7 +723,7 @@ impl Copilot {
 
     pub fn completions<T>(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -735,7 +735,7 @@ impl Copilot {
 
     pub fn completions_cycling<T>(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -792,7 +792,7 @@ impl Copilot {
 
     fn request_completions<R, T>(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>>
@@ -926,7 +926,7 @@ fn id_for_language(language: Option<&Arc<Language>>) -> String {
     }
 }
 
-fn uri_for_buffer(buffer: &Handle<Buffer>, cx: &AppContext) -> lsp2::Url {
+fn uri_for_buffer(buffer: &Model<Buffer>, cx: &AppContext) -> lsp2::Url {
     if let Some(file) = buffer.read(cx).file().and_then(|file| file.as_local()) {
         lsp2::Url::from_file_path(file.abs_path(cx)).unwrap()
     } else {

crates/editor/src/editor.rs 🔗

@@ -967,7 +967,6 @@ impl CompletionsMenu {
             self.selected_item -= 1;
         } else {
             self.selected_item = self.matches.len() - 1;
-            self.list.scroll_to(ScrollTarget::Show(self.selected_item));
         }
         self.list.scroll_to(ScrollTarget::Show(self.selected_item));
         self.attempt_resolve_selected_completion_documentation(project, cx);
@@ -1538,7 +1537,6 @@ impl CodeActionsMenu {
             self.selected_item -= 1;
         } else {
             self.selected_item = self.actions.len() - 1;
-            self.list.scroll_to(ScrollTarget::Show(self.selected_item));
         }
         self.list.scroll_to(ScrollTarget::Show(self.selected_item));
         cx.notify();
@@ -1547,11 +1545,10 @@ impl CodeActionsMenu {
     fn select_next(&mut self, cx: &mut ViewContext<Editor>) {
         if self.selected_item + 1 < self.actions.len() {
             self.selected_item += 1;
-            self.list.scroll_to(ScrollTarget::Show(self.selected_item));
         } else {
             self.selected_item = 0;
-            self.list.scroll_to(ScrollTarget::Show(self.selected_item));
         }
+        self.list.scroll_to(ScrollTarget::Show(self.selected_item));
         cx.notify();
     }
 

crates/gpui2/src/app.rs 🔗

@@ -16,7 +16,7 @@ use crate::{
     current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AnyWindowHandle,
     AppMetadata, AssetSource, ClipboardItem, Context, DispatchPhase, DisplayId, Executor,
     FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly,
-    Pixels, Platform, Point, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
+    Pixels, Platform, Point, Render, SharedString, SubscriberSet, Subscription, SvgRenderer, Task,
     TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, WindowContext,
     WindowHandle, WindowId,
 };
@@ -309,10 +309,17 @@ impl AppContext {
         update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R,
     ) -> Result<R>
     where
-        V: 'static,
+        V: 'static + Send,
     {
         self.update_window(handle.any_handle, |cx| {
-            let root_view = cx.window.root_view.as_ref().unwrap().downcast().unwrap();
+            let root_view = cx
+                .window
+                .root_view
+                .as_ref()
+                .unwrap()
+                .clone()
+                .downcast()
+                .unwrap();
             root_view.update(cx, update)
         })
     }
@@ -685,7 +692,7 @@ impl AppContext {
 
     pub fn observe_release<E: 'static>(
         &mut self,
-        handle: &Handle<E>,
+        handle: &Model<E>,
         mut on_release: impl FnMut(&mut E, &mut AppContext) + Send + 'static,
     ) -> Subscription {
         self.release_listeners.insert(
@@ -750,35 +757,35 @@ impl AppContext {
 }
 
 impl Context for AppContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = T;
 
     /// Build an entity that is owned by the application. The given function will be invoked with
-    /// a `ModelContext` and must return an object representing the entity. A `Handle` will be returned
+    /// a `ModelContext` and must return an object representing the entity. A `Model` will be returned
     /// which can be used to access the entity in a context.
-    fn entity<T: 'static + Send>(
+    fn build_model<T: 'static + Send>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Handle<T> {
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Model<T> {
         self.update(|cx| {
             let slot = cx.entities.reserve();
-            let entity = build_entity(&mut ModelContext::mutable(cx, slot.downgrade()));
+            let entity = build_model(&mut ModelContext::mutable(cx, slot.downgrade()));
             cx.entities.insert(slot, entity)
         })
     }
 
-    /// Update the entity referenced by the given handle. The function is passed a mutable reference to the
+    /// Update the entity referenced by the given model. The function is passed a mutable reference to the
     /// entity along with a `ModelContext` for the entity.
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        model: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> R {
         self.update(|cx| {
-            let mut entity = cx.entities.lease(handle);
+            let mut entity = cx.entities.lease(model);
             let result = update(
                 &mut entity,
-                &mut ModelContext::mutable(cx, handle.downgrade()),
+                &mut ModelContext::mutable(cx, model.downgrade()),
             );
             cx.entities.end_lease(entity);
             result
@@ -861,10 +868,17 @@ impl MainThread<AppContext> {
         update: impl FnOnce(&mut V, &mut MainThread<ViewContext<'_, '_, V>>) -> R,
     ) -> Result<R>
     where
-        V: 'static,
+        V: 'static + Send,
     {
         self.update_window(handle.any_handle, |cx| {
-            let root_view = cx.window.root_view.as_ref().unwrap().downcast().unwrap();
+            let root_view = cx
+                .window
+                .root_view
+                .as_ref()
+                .unwrap()
+                .clone()
+                .downcast()
+                .unwrap();
             root_view.update(cx, update)
         })
     }
@@ -872,7 +886,7 @@ impl MainThread<AppContext> {
     /// Opens a new window with the given option and the root view returned by the given function.
     /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
     /// functionality.
-    pub fn open_window<V: 'static>(
+    pub fn open_window<V: Render>(
         &mut self,
         options: crate::WindowOptions,
         build_root_view: impl FnOnce(&mut WindowContext) -> View<V> + Send + 'static,
@@ -955,10 +969,8 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
 /// Contains state associated with an active drag operation, started by dragging an element
 /// within the window or by dragging into the app from the underlying platform.
 pub(crate) struct AnyDrag {
-    pub drag_handle_view: Option<AnyView>,
+    pub view: AnyView,
     pub cursor_offset: Point<Pixels>,
-    pub state: AnyBox,
-    pub state_type: TypeId,
 }
 
 #[cfg(test)]

crates/gpui2/src/app/async_context.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    AnyWindowHandle, AppContext, Component, Context, Executor, Handle, MainThread, ModelContext,
-    Result, Task, View, ViewContext, VisualContext, WindowContext, WindowHandle,
+    AnyWindowHandle, AppContext, Context, Executor, MainThread, Model, ModelContext, Result, Task,
+    View, ViewContext, VisualContext, WindowContext, WindowHandle,
 };
 use anyhow::Context as _;
 use derive_more::{Deref, DerefMut};
@@ -14,25 +14,25 @@ pub struct AsyncAppContext {
 }
 
 impl Context for AsyncAppContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = Result<T>;
 
-    fn entity<T: 'static>(
+    fn build_model<T: 'static>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send,
     {
         let app = self.app.upgrade().context("app was released")?;
         let mut lock = app.lock(); // Need this to compile
-        Ok(lock.entity(build_entity))
+        Ok(lock.build_model(build_model))
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R> {
         let app = self.app.upgrade().context("app was released")?;
         let mut lock = app.lock(); // Need this to compile
@@ -84,7 +84,7 @@ impl AsyncAppContext {
         update: impl FnOnce(&mut V, &mut ViewContext<'_, '_, V>) -> R,
     ) -> Result<R>
     where
-        V: 'static,
+        V: 'static + Send,
     {
         let app = self.app.upgrade().context("app was released")?;
         let mut app_context = app.lock();
@@ -234,24 +234,24 @@ impl AsyncWindowContext {
 }
 
 impl Context for AsyncWindowContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = Result<T>;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Result<Model<T>>
     where
         T: 'static + Send,
     {
         self.app
-            .update_window(self.window, |cx| cx.entity(build_entity))
+            .update_window(self.window, |cx| cx.build_model(build_model))
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Result<R> {
         self.app
             .update_window(self.window, |cx| cx.update_entity(handle, update))
@@ -261,17 +261,15 @@ impl Context for AsyncWindowContext {
 impl VisualContext for AsyncWindowContext {
     type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>;
 
-    fn build_view<E, V>(
+    fn build_view<V>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+        build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        E: Component<V>,
         V: 'static + Send,
     {
         self.app
-            .update_window(self.window, |cx| cx.build_view(build_entity, render))
+            .update_window(self.window, |cx| cx.build_view(build_view_state))
     }
 
     fn update_view<V: 'static, R>(

crates/gpui2/src/app/entity_map.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{AnyBox, AppContext, Context, EntityHandle};
+use crate::{AnyBox, AppContext, Context};
 use anyhow::{anyhow, Result};
 use derive_more::{Deref, DerefMut};
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
@@ -53,29 +53,29 @@ impl EntityMap {
     /// Reserve a slot for an entity, which you can subsequently use with `insert`.
     pub fn reserve<T: 'static>(&self) -> Slot<T> {
         let id = self.ref_counts.write().counts.insert(1.into());
-        Slot(Handle::new(id, Arc::downgrade(&self.ref_counts)))
+        Slot(Model::new(id, Arc::downgrade(&self.ref_counts)))
     }
 
     /// Insert an entity into a slot obtained by calling `reserve`.
-    pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Handle<T>
+    pub fn insert<T>(&mut self, slot: Slot<T>, entity: T) -> Model<T>
     where
         T: 'static + Send,
     {
-        let handle = slot.0;
-        self.entities.insert(handle.entity_id, Box::new(entity));
-        handle
+        let model = slot.0;
+        self.entities.insert(model.entity_id, Box::new(entity));
+        model
     }
 
     /// Move an entity to the stack.
-    pub fn lease<'a, T>(&mut self, handle: &'a Handle<T>) -> Lease<'a, T> {
-        self.assert_valid_context(handle);
+    pub fn lease<'a, T>(&mut self, model: &'a Model<T>) -> Lease<'a, T> {
+        self.assert_valid_context(model);
         let entity = Some(
             self.entities
-                .remove(handle.entity_id)
+                .remove(model.entity_id)
                 .expect("Circular entity lease. Is the entity already being updated?"),
         );
         Lease {
-            handle,
+            model,
             entity,
             entity_type: PhantomData,
         }
@@ -84,18 +84,18 @@ impl EntityMap {
     /// Return an entity after moving it to the stack.
     pub fn end_lease<T>(&mut self, mut lease: Lease<T>) {
         self.entities
-            .insert(lease.handle.entity_id, lease.entity.take().unwrap());
+            .insert(lease.model.entity_id, lease.entity.take().unwrap());
     }
 
-    pub fn read<T: 'static>(&self, handle: &Handle<T>) -> &T {
-        self.assert_valid_context(handle);
-        self.entities[handle.entity_id].downcast_ref().unwrap()
+    pub fn read<T: 'static>(&self, model: &Model<T>) -> &T {
+        self.assert_valid_context(model);
+        self.entities[model.entity_id].downcast_ref().unwrap()
     }
 
-    fn assert_valid_context(&self, handle: &AnyHandle) {
+    fn assert_valid_context(&self, model: &AnyModel) {
         debug_assert!(
-            Weak::ptr_eq(&handle.entity_map, &Arc::downgrade(&self.ref_counts)),
-            "used a handle with the wrong context"
+            Weak::ptr_eq(&model.entity_map, &Arc::downgrade(&self.ref_counts)),
+            "used a model with the wrong context"
         );
     }
 
@@ -115,7 +115,7 @@ impl EntityMap {
 
 pub struct Lease<'a, T> {
     entity: Option<AnyBox>,
-    pub handle: &'a Handle<T>,
+    pub model: &'a Model<T>,
     entity_type: PhantomData<T>,
 }
 
@@ -143,15 +143,15 @@ impl<'a, T> Drop for Lease<'a, T> {
 }
 
 #[derive(Deref, DerefMut)]
-pub struct Slot<T>(Handle<T>);
+pub struct Slot<T>(Model<T>);
 
-pub struct AnyHandle {
+pub struct AnyModel {
     pub(crate) entity_id: EntityId,
-    entity_type: TypeId,
+    pub(crate) entity_type: TypeId,
     entity_map: Weak<RwLock<EntityRefCounts>>,
 }
 
-impl AnyHandle {
+impl AnyModel {
     fn new(id: EntityId, entity_type: TypeId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self {
         Self {
             entity_id: id,
@@ -164,18 +164,18 @@ impl AnyHandle {
         self.entity_id
     }
 
-    pub fn downgrade(&self) -> AnyWeakHandle {
-        AnyWeakHandle {
+    pub fn downgrade(&self) -> AnyWeakModel {
+        AnyWeakModel {
             entity_id: self.entity_id,
             entity_type: self.entity_type,
             entity_ref_counts: self.entity_map.clone(),
         }
     }
 
-    pub fn downcast<T: 'static>(&self) -> Option<Handle<T>> {
+    pub fn downcast<T: 'static>(&self) -> Option<Model<T>> {
         if TypeId::of::<T>() == self.entity_type {
-            Some(Handle {
-                any_handle: self.clone(),
+            Some(Model {
+                any_model: self.clone(),
                 entity_type: PhantomData,
             })
         } else {
@@ -184,16 +184,16 @@ impl AnyHandle {
     }
 }
 
-impl Clone for AnyHandle {
+impl Clone for AnyModel {
     fn clone(&self) -> Self {
         if let Some(entity_map) = self.entity_map.upgrade() {
             let entity_map = entity_map.read();
             let count = entity_map
                 .counts
                 .get(self.entity_id)
-                .expect("detected over-release of a handle");
+                .expect("detected over-release of a model");
             let prev_count = count.fetch_add(1, SeqCst);
-            assert_ne!(prev_count, 0, "Detected over-release of a handle.");
+            assert_ne!(prev_count, 0, "Detected over-release of a model.");
         }
 
         Self {
@@ -204,16 +204,16 @@ impl Clone for AnyHandle {
     }
 }
 
-impl Drop for AnyHandle {
+impl Drop for AnyModel {
     fn drop(&mut self) {
         if let Some(entity_map) = self.entity_map.upgrade() {
             let entity_map = entity_map.upgradable_read();
             let count = entity_map
                 .counts
                 .get(self.entity_id)
-                .expect("Detected over-release of a handle.");
+                .expect("Detected over-release of a model.");
             let prev_count = count.fetch_sub(1, SeqCst);
-            assert_ne!(prev_count, 0, "Detected over-release of a handle.");
+            assert_ne!(prev_count, 0, "Detected over-release of a model.");
             if prev_count == 1 {
                 // We were the last reference to this entity, so we can remove it.
                 let mut entity_map = RwLockUpgradableReadGuard::upgrade(entity_map);
@@ -223,60 +223,65 @@ impl Drop for AnyHandle {
     }
 }
 
-impl<T> From<Handle<T>> for AnyHandle {
-    fn from(handle: Handle<T>) -> Self {
-        handle.any_handle
+impl<T> From<Model<T>> for AnyModel {
+    fn from(model: Model<T>) -> Self {
+        model.any_model
     }
 }
 
-impl Hash for AnyHandle {
+impl Hash for AnyModel {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.entity_id.hash(state);
     }
 }
 
-impl PartialEq for AnyHandle {
+impl PartialEq for AnyModel {
     fn eq(&self, other: &Self) -> bool {
         self.entity_id == other.entity_id
     }
 }
 
-impl Eq for AnyHandle {}
+impl Eq for AnyModel {}
 
 #[derive(Deref, DerefMut)]
-pub struct Handle<T> {
+pub struct Model<T> {
     #[deref]
     #[deref_mut]
-    any_handle: AnyHandle,
-    entity_type: PhantomData<T>,
+    pub(crate) any_model: AnyModel,
+    pub(crate) entity_type: PhantomData<T>,
 }
 
-unsafe impl<T> Send for Handle<T> {}
-unsafe impl<T> Sync for Handle<T> {}
+unsafe impl<T> Send for Model<T> {}
+unsafe impl<T> Sync for Model<T> {}
 
-impl<T: 'static> Handle<T> {
+impl<T: 'static> Model<T> {
     fn new(id: EntityId, entity_map: Weak<RwLock<EntityRefCounts>>) -> Self
     where
         T: 'static,
     {
         Self {
-            any_handle: AnyHandle::new(id, TypeId::of::<T>(), entity_map),
+            any_model: AnyModel::new(id, TypeId::of::<T>(), entity_map),
             entity_type: PhantomData,
         }
     }
 
-    pub fn downgrade(&self) -> WeakHandle<T> {
-        WeakHandle {
-            any_handle: self.any_handle.downgrade(),
+    pub fn downgrade(&self) -> WeakModel<T> {
+        WeakModel {
+            any_model: self.any_model.downgrade(),
             entity_type: self.entity_type,
         }
     }
 
+    /// Convert this into a dynamically typed model.
+    pub fn into_any(self) -> AnyModel {
+        self.any_model
+    }
+
     pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
         cx.entities.read(self)
     }
 
-    /// Update the entity referenced by this handle with the given function.
+    /// Update the entity referenced by this model with the given function.
     ///
     /// The update function receives a context appropriate for its environment.
     /// When updating in an `AppContext`, it receives a `ModelContext`.
@@ -284,7 +289,7 @@ impl<T: 'static> Handle<T> {
     pub fn update<C, R>(
         &self,
         cx: &mut C,
-        update: impl FnOnce(&mut T, &mut C::EntityContext<'_, T>) -> R,
+        update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R,
     ) -> C::Result<R>
     where
         C: Context,
@@ -293,73 +298,54 @@ impl<T: 'static> Handle<T> {
     }
 }
 
-impl<T> Clone for Handle<T> {
+impl<T> Clone for Model<T> {
     fn clone(&self) -> Self {
         Self {
-            any_handle: self.any_handle.clone(),
+            any_model: self.any_model.clone(),
             entity_type: self.entity_type,
         }
     }
 }
 
-impl<T> std::fmt::Debug for Handle<T> {
+impl<T> std::fmt::Debug for Model<T> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         write!(
             f,
-            "Handle {{ entity_id: {:?}, entity_type: {:?} }}",
-            self.any_handle.entity_id,
+            "Model {{ entity_id: {:?}, entity_type: {:?} }}",
+            self.any_model.entity_id,
             type_name::<T>()
         )
     }
 }
 
-impl<T> Hash for Handle<T> {
+impl<T> Hash for Model<T> {
     fn hash<H: Hasher>(&self, state: &mut H) {
-        self.any_handle.hash(state);
+        self.any_model.hash(state);
     }
 }
 
-impl<T> PartialEq for Handle<T> {
+impl<T> PartialEq for Model<T> {
     fn eq(&self, other: &Self) -> bool {
-        self.any_handle == other.any_handle
+        self.any_model == other.any_model
     }
 }
 
-impl<T> Eq for Handle<T> {}
+impl<T> Eq for Model<T> {}
 
-impl<T> PartialEq<WeakHandle<T>> for Handle<T> {
-    fn eq(&self, other: &WeakHandle<T>) -> bool {
-        self.entity_id == other.entity_id
-    }
-}
-
-impl<T: 'static> EntityHandle<T> for Handle<T> {
-    type Weak = WeakHandle<T>;
-
-    fn entity_id(&self) -> EntityId {
-        self.entity_id
-    }
-
-    fn downgrade(&self) -> Self::Weak {
-        self.downgrade()
-    }
-
-    fn upgrade_from(weak: &Self::Weak) -> Option<Self>
-    where
-        Self: Sized,
-    {
-        weak.upgrade()
+impl<T> PartialEq<WeakModel<T>> for Model<T> {
+    fn eq(&self, other: &WeakModel<T>) -> bool {
+        self.entity_id() == other.entity_id()
     }
 }
 
 #[derive(Clone)]
-pub struct AnyWeakHandle {
+pub struct AnyWeakModel {
     pub(crate) entity_id: EntityId,
     entity_type: TypeId,
     entity_ref_counts: Weak<RwLock<EntityRefCounts>>,
 }
 
-impl AnyWeakHandle {
+impl AnyWeakModel {
     pub fn entity_id(&self) -> EntityId {
         self.entity_id
     }
@@ -373,14 +359,14 @@ impl AnyWeakHandle {
         ref_count > 0
     }
 
-    pub fn upgrade(&self) -> Option<AnyHandle> {
+    pub fn upgrade(&self) -> Option<AnyModel> {
         let entity_map = self.entity_ref_counts.upgrade()?;
         entity_map
             .read()
             .counts
             .get(self.entity_id)?
             .fetch_add(1, SeqCst);
-        Some(AnyHandle {
+        Some(AnyModel {
             entity_id: self.entity_id,
             entity_type: self.entity_type,
             entity_map: self.entity_ref_counts.clone(),
@@ -388,55 +374,55 @@ impl AnyWeakHandle {
     }
 }
 
-impl<T> From<WeakHandle<T>> for AnyWeakHandle {
-    fn from(handle: WeakHandle<T>) -> Self {
-        handle.any_handle
+impl<T> From<WeakModel<T>> for AnyWeakModel {
+    fn from(model: WeakModel<T>) -> Self {
+        model.any_model
     }
 }
 
-impl Hash for AnyWeakHandle {
+impl Hash for AnyWeakModel {
     fn hash<H: Hasher>(&self, state: &mut H) {
         self.entity_id.hash(state);
     }
 }
 
-impl PartialEq for AnyWeakHandle {
+impl PartialEq for AnyWeakModel {
     fn eq(&self, other: &Self) -> bool {
         self.entity_id == other.entity_id
     }
 }
 
-impl Eq for AnyWeakHandle {}
+impl Eq for AnyWeakModel {}
 
 #[derive(Deref, DerefMut)]
-pub struct WeakHandle<T> {
+pub struct WeakModel<T> {
     #[deref]
     #[deref_mut]
-    any_handle: AnyWeakHandle,
+    any_model: AnyWeakModel,
     entity_type: PhantomData<T>,
 }
 
-unsafe impl<T> Send for WeakHandle<T> {}
-unsafe impl<T> Sync for WeakHandle<T> {}
+unsafe impl<T> Send for WeakModel<T> {}
+unsafe impl<T> Sync for WeakModel<T> {}
 
-impl<T> Clone for WeakHandle<T> {
+impl<T> Clone for WeakModel<T> {
     fn clone(&self) -> Self {
         Self {
-            any_handle: self.any_handle.clone(),
+            any_model: self.any_model.clone(),
             entity_type: self.entity_type,
         }
     }
 }
 
-impl<T: 'static> WeakHandle<T> {
-    pub fn upgrade(&self) -> Option<Handle<T>> {
-        Some(Handle {
-            any_handle: self.any_handle.upgrade()?,
+impl<T: 'static> WeakModel<T> {
+    pub fn upgrade(&self) -> Option<Model<T>> {
+        Some(Model {
+            any_model: self.any_model.upgrade()?,
             entity_type: self.entity_type,
         })
     }
 
-    /// Update the entity referenced by this handle with the given function if
+    /// Update the entity referenced by this model with the given function if
     /// the referenced entity still exists. Returns an error if the entity has
     /// been released.
     ///
@@ -446,7 +432,7 @@ impl<T: 'static> WeakHandle<T> {
     pub fn update<C, R>(
         &self,
         cx: &mut C,
-        update: impl FnOnce(&mut T, &mut C::EntityContext<'_, T>) -> R,
+        update: impl FnOnce(&mut T, &mut C::ModelContext<'_, T>) -> R,
     ) -> Result<R>
     where
         C: Context,
@@ -460,22 +446,22 @@ impl<T: 'static> WeakHandle<T> {
     }
 }
 
-impl<T> Hash for WeakHandle<T> {
+impl<T> Hash for WeakModel<T> {
     fn hash<H: Hasher>(&self, state: &mut H) {
-        self.any_handle.hash(state);
+        self.any_model.hash(state);
     }
 }
 
-impl<T> PartialEq for WeakHandle<T> {
+impl<T> PartialEq for WeakModel<T> {
     fn eq(&self, other: &Self) -> bool {
-        self.any_handle == other.any_handle
+        self.any_model == other.any_model
     }
 }
 
-impl<T> Eq for WeakHandle<T> {}
+impl<T> Eq for WeakModel<T> {}
 
-impl<T> PartialEq<Handle<T>> for WeakHandle<T> {
-    fn eq(&self, other: &Handle<T>) -> bool {
-        self.entity_id == other.entity_id
+impl<T> PartialEq<Model<T>> for WeakModel<T> {
+    fn eq(&self, other: &Model<T>) -> bool {
+        self.entity_id() == other.entity_id()
     }
 }

crates/gpui2/src/app/model_context.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    AppContext, AsyncAppContext, Context, Effect, EntityId, EventEmitter, Handle, MainThread,
-    Reference, Subscription, Task, WeakHandle,
+    AppContext, AsyncAppContext, Context, Effect, EntityId, EventEmitter, MainThread, Model,
+    Reference, Subscription, Task, WeakModel,
 };
 use derive_more::{Deref, DerefMut};
 use futures::FutureExt;
@@ -15,11 +15,11 @@ pub struct ModelContext<'a, T> {
     #[deref]
     #[deref_mut]
     app: Reference<'a, AppContext>,
-    model_state: WeakHandle<T>,
+    model_state: WeakModel<T>,
 }
 
 impl<'a, T: 'static> ModelContext<'a, T> {
-    pub(crate) fn mutable(app: &'a mut AppContext, model_state: WeakHandle<T>) -> Self {
+    pub(crate) fn mutable(app: &'a mut AppContext, model_state: WeakModel<T>) -> Self {
         Self {
             app: Reference::Mutable(app),
             model_state,
@@ -30,20 +30,20 @@ impl<'a, T: 'static> ModelContext<'a, T> {
         self.model_state.entity_id
     }
 
-    pub fn handle(&self) -> Handle<T> {
+    pub fn handle(&self) -> Model<T> {
         self.weak_handle()
             .upgrade()
             .expect("The entity must be alive if we have a model context")
     }
 
-    pub fn weak_handle(&self) -> WeakHandle<T> {
+    pub fn weak_handle(&self) -> WeakModel<T> {
         self.model_state.clone()
     }
 
     pub fn observe<T2: 'static>(
         &mut self,
-        handle: &Handle<T2>,
-        mut on_notify: impl FnMut(&mut T, Handle<T2>, &mut ModelContext<'_, T>) + Send + 'static,
+        handle: &Model<T2>,
+        mut on_notify: impl FnMut(&mut T, Model<T2>, &mut ModelContext<'_, T>) + Send + 'static,
     ) -> Subscription
     where
         T: 'static + Send,
@@ -65,10 +65,8 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn subscribe<E: 'static + EventEmitter>(
         &mut self,
-        handle: &Handle<E>,
-        mut on_event: impl FnMut(&mut T, Handle<E>, &E::Event, &mut ModelContext<'_, T>)
-            + Send
-            + 'static,
+        handle: &Model<E>,
+        mut on_event: impl FnMut(&mut T, Model<E>, &E::Event, &mut ModelContext<'_, T>) + Send + 'static,
     ) -> Subscription
     where
         T: 'static + Send,
@@ -107,7 +105,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn observe_release<E: 'static>(
         &mut self,
-        handle: &Handle<E>,
+        handle: &Model<E>,
         mut on_release: impl FnMut(&mut T, &mut E, &mut ModelContext<'_, T>) + Send + 'static,
     ) -> Subscription
     where
@@ -182,7 +180,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn spawn<Fut, R>(
         &self,
-        f: impl FnOnce(WeakHandle<T>, AsyncAppContext) -> Fut + Send + 'static,
+        f: impl FnOnce(WeakModel<T>, AsyncAppContext) -> Fut + Send + 'static,
     ) -> Task<R>
     where
         T: 'static,
@@ -195,7 +193,7 @@ impl<'a, T: 'static> ModelContext<'a, T> {
 
     pub fn spawn_on_main<Fut, R>(
         &self,
-        f: impl FnOnce(WeakHandle<T>, MainThread<AsyncAppContext>) -> Fut + Send + 'static,
+        f: impl FnOnce(WeakModel<T>, MainThread<AsyncAppContext>) -> Fut + Send + 'static,
     ) -> Task<R>
     where
         Fut: Future<Output = R> + 'static,
@@ -220,23 +218,23 @@ where
 }
 
 impl<'a, T> Context for ModelContext<'a, T> {
-    type EntityContext<'b, U> = ModelContext<'b, U>;
+    type ModelContext<'b, U> = ModelContext<'b, U>;
     type Result<U> = U;
 
-    fn entity<U>(
+    fn build_model<U>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, U>) -> U,
-    ) -> Handle<U>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, U>) -> U,
+    ) -> Model<U>
     where
         U: 'static + Send,
     {
-        self.app.entity(build_entity)
+        self.app.build_model(build_model)
     }
 
     fn update_entity<U: 'static, R>(
         &mut self,
-        handle: &Handle<U>,
-        update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, U>) -> R,
+        handle: &Model<U>,
+        update: impl FnOnce(&mut U, &mut Self::ModelContext<'_, U>) -> R,
     ) -> R {
         self.app.update_entity(handle, update)
     }

crates/gpui2/src/app/test_context.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, Handle, MainThread,
+    AnyWindowHandle, AppContext, AsyncAppContext, Context, Executor, MainThread, Model,
     ModelContext, Result, Task, TestDispatcher, TestPlatform, WindowContext,
 };
 use parking_lot::Mutex;
@@ -12,24 +12,24 @@ pub struct TestAppContext {
 }
 
 impl Context for TestAppContext {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = T;
 
-    fn entity<T: 'static>(
+    fn build_model<T: 'static>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send,
     {
         let mut lock = self.app.lock();
-        lock.entity(build_entity)
+        lock.build_model(build_model)
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R> {
         let mut lock = self.app.lock();
         lock.update_entity(handle, update)

crates/gpui2/src/element.rs 🔗

@@ -4,7 +4,7 @@ pub(crate) use smallvec::SmallVec;
 use std::{any::Any, mem};
 
 pub trait Element<V: 'static> {
-    type ElementState: 'static;
+    type ElementState: 'static + Send;
 
     fn id(&self) -> Option<ElementId>;
 

crates/gpui2/src/gpui2.rs 🔗

@@ -70,33 +70,31 @@ use taffy::TaffyLayoutEngine;
 type AnyBox = Box<dyn Any + Send>;
 
 pub trait Context {
-    type EntityContext<'a, T>;
+    type ModelContext<'a, T>;
     type Result<T>;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send;
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R>;
 }
 
 pub trait VisualContext: Context {
     type ViewContext<'a, 'w, V>;
 
-    fn build_view<E, V>(
+    fn build_view<V>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+        build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        E: Component<V>,
         V: 'static + Send;
 
     fn update_view<V: 'static, R>(
@@ -140,37 +138,37 @@ impl<T> DerefMut for MainThread<T> {
 }
 
 impl<C: Context> Context for MainThread<C> {
-    type EntityContext<'a, T> = MainThread<C::EntityContext<'a, T>>;
+    type ModelContext<'a, T> = MainThread<C::ModelContext<'a, T>>;
     type Result<T> = C::Result<T>;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Self::Result<Handle<T>>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Self::Result<Model<T>>
     where
         T: 'static + Send,
     {
-        self.0.entity(|cx| {
+        self.0.build_model(|cx| {
             let cx = unsafe {
                 mem::transmute::<
-                    &mut C::EntityContext<'_, T>,
-                    &mut MainThread<C::EntityContext<'_, T>>,
+                    &mut C::ModelContext<'_, T>,
+                    &mut MainThread<C::ModelContext<'_, T>>,
                 >(cx)
             };
-            build_entity(cx)
+            build_model(cx)
         })
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        handle: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> Self::Result<R> {
         self.0.update_entity(handle, |entity, cx| {
             let cx = unsafe {
                 mem::transmute::<
-                    &mut C::EntityContext<'_, T>,
-                    &mut MainThread<C::EntityContext<'_, T>>,
+                    &mut C::ModelContext<'_, T>,
+                    &mut MainThread<C::ModelContext<'_, T>>,
                 >(cx)
             };
             update(entity, cx)
@@ -181,27 +179,22 @@ impl<C: Context> Context for MainThread<C> {
 impl<C: VisualContext> VisualContext for MainThread<C> {
     type ViewContext<'a, 'w, V> = MainThread<C::ViewContext<'a, 'w, V>>;
 
-    fn build_view<E, V>(
+    fn build_view<V>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+        build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        E: Component<V>,
         V: 'static + Send,
     {
-        self.0.build_view(
-            |cx| {
-                let cx = unsafe {
-                    mem::transmute::<
-                        &mut C::ViewContext<'_, '_, V>,
-                        &mut MainThread<C::ViewContext<'_, '_, V>>,
-                    >(cx)
-                };
-                build_entity(cx)
-            },
-            render,
-        )
+        self.0.build_view(|cx| {
+            let cx = unsafe {
+                mem::transmute::<
+                    &mut C::ViewContext<'_, '_, V>,
+                    &mut MainThread<C::ViewContext<'_, '_, V>>,
+                >(cx)
+            };
+            build_view_state(cx)
+        })
     }
 
     fn update_view<V: 'static, R>(

crates/gpui2/src/interactive.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    point, px, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, Component,
-    DispatchContext, DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke,
-    Modifiers, Overflow, Pixels, Point, SharedString, Size, Style, StyleRefinement, View,
+    div, point, px, Action, AnyDrag, AnyView, AppContext, BorrowWindow, Bounds, Component,
+    DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke,
+    Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, View,
     ViewContext,
 };
 use collections::HashMap;
@@ -258,17 +258,17 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
         self
     }
 
-    fn on_drop<S: 'static>(
+    fn on_drop<W: 'static + Send>(
         mut self,
-        listener: impl Fn(&mut V, S, &mut ViewContext<V>) + Send + 'static,
+        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + Send + 'static,
     ) -> Self
     where
         Self: Sized,
     {
         self.stateless_interaction().drop_listeners.push((
-            TypeId::of::<S>(),
-            Box::new(move |view, drag_state, cx| {
-                listener(view, *drag_state.downcast().unwrap(), cx);
+            TypeId::of::<W>(),
+            Box::new(move |view, dragged_view, cx| {
+                listener(view, dragged_view.downcast().unwrap(), cx);
             }),
         ));
         self
@@ -314,36 +314,22 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
         self
     }
 
-    fn on_drag<S, R, E>(
+    fn on_drag<W>(
         mut self,
-        listener: impl Fn(&mut V, &mut ViewContext<V>) -> Drag<S, R, V, E> + Send + 'static,
+        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + Send + 'static,
     ) -> Self
     where
         Self: Sized,
-        S: Any + Send,
-        R: Fn(&mut V, &mut ViewContext<V>) -> E,
-        R: 'static + Send,
-        E: Component<V>,
+        W: 'static + Send + Render,
     {
         debug_assert!(
             self.stateful_interaction().drag_listener.is_none(),
             "calling on_drag more than once on the same element is not supported"
         );
         self.stateful_interaction().drag_listener =
-            Some(Box::new(move |view_state, cursor_offset, cx| {
-                let drag = listener(view_state, cx);
-                let drag_handle_view = Some(
-                    View::for_handle(cx.handle().upgrade().unwrap(), move |view_state, cx| {
-                        (drag.render_drag_handle)(view_state, cx)
-                    })
-                    .into_any(),
-                );
-                AnyDrag {
-                    drag_handle_view,
-                    cursor_offset,
-                    state: Box::new(drag.state),
-                    state_type: TypeId::of::<S>(),
-                }
+            Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
+                view: listener(view_state, cx).into_any(),
+                cursor_offset,
             }));
         self
     }
@@ -412,7 +398,7 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
         if let Some(drag) = cx.active_drag.take() {
             for (state_type, group_drag_style) in &self.as_stateless().group_drag_over_styles {
                 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
-                    if *state_type == drag.state_type
+                    if *state_type == drag.view.entity_type()
                         && group_bounds.contains_point(&mouse_position)
                     {
                         style.refine(&group_drag_style.style);
@@ -421,7 +407,8 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
             }
 
             for (state_type, drag_over_style) in &self.as_stateless().drag_over_styles {
-                if *state_type == drag.state_type && bounds.contains_point(&mouse_position) {
+                if *state_type == drag.view.entity_type() && bounds.contains_point(&mouse_position)
+                {
                     style.refine(drag_over_style);
                 }
             }
@@ -509,7 +496,7 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
             cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     if let Some(drag_state_type) =
-                        cx.active_drag.as_ref().map(|drag| drag.state_type)
+                        cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
                     {
                         for (drop_state_type, listener) in &drop_listeners {
                             if *drop_state_type == drag_state_type {
@@ -517,7 +504,7 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
                                     .active_drag
                                     .take()
                                     .expect("checked for type drag state type above");
-                                listener(view, drag.state, cx);
+                                listener(view, drag.view.clone(), cx);
                                 cx.notify();
                                 cx.stop_propagation();
                             }
@@ -685,7 +672,7 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
     }
 }
 
-type DropListener<V> = dyn Fn(&mut V, AnyBox, &mut ViewContext<V>) + 'static + Send;
+type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static + Send;
 
 pub struct StatelessInteraction<V> {
     pub dispatch_context: DispatchContext,
@@ -866,7 +853,7 @@ pub struct Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Component<V>,
+    E: Component<()>,
 {
     pub state: S,
     pub render_drag_handle: R,
@@ -877,7 +864,7 @@ impl<S, R, V, E> Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Component<V>,
+    E: Component<()>,
 {
     pub fn new(state: S, render_drag_handle: R) -> Self {
         Drag {
@@ -888,6 +875,10 @@ where
     }
 }
 
+// impl<S, R, V, E> Render for Drag<S, R, V, E> {
+//     // fn render(&mut self, cx: ViewContext<Self>) ->
+// }
+
 #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
 pub enum MouseButton {
     Left,
@@ -995,6 +986,14 @@ impl Deref for MouseExitEvent {
 #[derive(Debug, Clone, Default)]
 pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
 
+impl Render for ExternalPaths {
+    type Element = Div<Self>;
+
+    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+        div() // Intentionally left empty because the platform will render icons for the dragged files
+    }
+}
+
 #[derive(Debug, Clone)]
 pub enum FileDropEvent {
     Entered {

crates/gpui2/src/view.rs 🔗

@@ -1,45 +1,35 @@
 use crate::{
-    AnyBox, AnyElement, AppContext, AvailableSpace, BorrowWindow, Bounds, Component, Element,
-    ElementId, EntityHandle, EntityId, Flatten, Handle, LayoutId, Pixels, Size, ViewContext,
-    VisualContext, WeakHandle, WindowContext,
+    AnyBox, AnyElement, AnyModel, AppContext, AvailableSpace, BorrowWindow, Bounds, Component,
+    Element, ElementId, EntityHandle, EntityId, Flatten, LayoutId, Model, Pixels, Size,
+    ViewContext, VisualContext, WeakModel, WindowContext,
 };
 use anyhow::{Context, Result};
-use parking_lot::Mutex;
 use std::{
-    any::Any,
+    any::{Any, TypeId},
     marker::PhantomData,
-    sync::{Arc, Weak},
+    sync::Arc,
 };
 
-pub struct View<V> {
-    pub(crate) state: Handle<V>,
-    render: Arc<Mutex<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyElement<V> + Send + 'static>>,
+pub trait Render: 'static + Sized {
+    type Element: Element<Self> + 'static + Send;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
 }
 
-impl<V: 'static> View<V> {
-    pub fn for_handle<E>(
-        state: Handle<V>,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
-    ) -> View<V>
-    where
-        E: Component<V>,
-    {
-        View {
-            state,
-            render: Arc::new(Mutex::new(
-                move |state: &mut V, cx: &mut ViewContext<'_, '_, V>| render(state, cx).render(),
-            )),
-        }
-    }
+pub struct View<V> {
+    pub(crate) model: Model<V>,
+}
 
+impl<V: Render> View<V> {
     pub fn into_any(self) -> AnyView {
         AnyView(Arc::new(self))
     }
+}
 
+impl<V: 'static> View<V> {
     pub fn downgrade(&self) -> WeakView<V> {
         WeakView {
-            state: self.state.downgrade(),
-            render: Arc::downgrade(&self.render),
+            model: self.model.downgrade(),
         }
     }
 
@@ -55,20 +45,19 @@ impl<V: 'static> View<V> {
     }
 
     pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V {
-        cx.entities.read(&self.state)
+        self.model.read(cx)
     }
 }
 
 impl<V> Clone for View<V> {
     fn clone(&self) -> Self {
         Self {
-            state: self.state.clone(),
-            render: self.render.clone(),
+            model: self.model.clone(),
         }
     }
 }
 
-impl<V: 'static, ParentViewState: 'static> Component<ParentViewState> for View<V> {
+impl<V: Render, ParentViewState: 'static> Component<ParentViewState> for View<V> {
     fn render(self) -> AnyElement<ParentViewState> {
         AnyElement::new(EraseViewState {
             view: self,
@@ -77,11 +66,14 @@ impl<V: 'static, ParentViewState: 'static> Component<ParentViewState> for View<V
     }
 }
 
-impl<V: 'static> Element<()> for View<V> {
+impl<V> Element<()> for View<V>
+where
+    V: Render,
+{
     type ElementState = AnyElement<V>;
 
-    fn id(&self) -> Option<ElementId> {
-        Some(ElementId::View(self.state.entity_id))
+    fn id(&self) -> Option<crate::ElementId> {
+        Some(ElementId::View(self.model.entity_id))
     }
 
     fn initialize(
@@ -91,7 +83,7 @@ impl<V: 'static> Element<()> for View<V> {
         cx: &mut ViewContext<()>,
     ) -> Self::ElementState {
         self.update(cx, |state, cx| {
-            let mut any_element = (self.render.lock())(state, cx);
+            let mut any_element = AnyElement::new(state.render(cx));
             any_element.initialize(state, cx);
             any_element
         })
@@ -121,7 +113,7 @@ impl<T: 'static> EntityHandle<T> for View<T> {
     type Weak = WeakView<T>;
 
     fn entity_id(&self) -> EntityId {
-        self.state.entity_id
+        self.model.entity_id
     }
 
     fn downgrade(&self) -> Self::Weak {
@@ -137,15 +129,13 @@ impl<T: 'static> EntityHandle<T> for View<T> {
 }
 
 pub struct WeakView<V> {
-    pub(crate) state: WeakHandle<V>,
-    render: Weak<Mutex<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyElement<V> + Send + 'static>>,
+    pub(crate) model: WeakModel<V>,
 }
 
 impl<V: 'static> WeakView<V> {
     pub fn upgrade(&self) -> Option<View<V>> {
-        let state = self.state.upgrade()?;
-        let render = self.render.upgrade()?;
-        Some(View { state, render })
+        let model = self.model.upgrade()?;
+        Some(View { model })
     }
 
     pub fn update<C, R>(
@@ -165,8 +155,7 @@ impl<V: 'static> WeakView<V> {
 impl<V> Clone for WeakView<V> {
     fn clone(&self) -> Self {
         Self {
-            state: self.state.clone(),
-            render: self.render.clone(),
+            model: self.model.clone(),
         }
     }
 }
@@ -178,13 +167,13 @@ struct EraseViewState<V, ParentV> {
 
 unsafe impl<V, ParentV> Send for EraseViewState<V, ParentV> {}
 
-impl<V: 'static, ParentV: 'static> Component<ParentV> for EraseViewState<V, ParentV> {
+impl<V: Render, ParentV: 'static> Component<ParentV> for EraseViewState<V, ParentV> {
     fn render(self) -> AnyElement<ParentV> {
         AnyElement::new(self)
     }
 }
 
-impl<V: 'static, ParentV: 'static> Element<ParentV> for EraseViewState<V, ParentV> {
+impl<V: Render, ParentV: 'static> Element<ParentV> for EraseViewState<V, ParentV> {
     type ElementState = AnyBox;
 
     fn id(&self) -> Option<ElementId> {
@@ -221,30 +210,43 @@ impl<V: 'static, ParentV: 'static> Element<ParentV> for EraseViewState<V, Parent
 }
 
 trait ViewObject: Send + Sync {
+    fn entity_type(&self) -> TypeId;
     fn entity_id(&self) -> EntityId;
+    fn model(&self) -> AnyModel;
     fn initialize(&self, cx: &mut WindowContext) -> AnyBox;
     fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
     fn paint(&self, bounds: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext);
     fn as_any(&self) -> &dyn Any;
 }
 
-impl<V: 'static> ViewObject for View<V> {
+impl<V> ViewObject for View<V>
+where
+    V: Render,
+{
+    fn entity_type(&self) -> TypeId {
+        TypeId::of::<V>()
+    }
+
     fn entity_id(&self) -> EntityId {
-        self.state.entity_id
+        self.model.entity_id
+    }
+
+    fn model(&self) -> AnyModel {
+        self.model.clone().into_any()
     }
 
     fn initialize(&self, cx: &mut WindowContext) -> AnyBox {
-        cx.with_element_id(self.state.entity_id, |_global_id, cx| {
+        cx.with_element_id(self.model.entity_id, |_global_id, cx| {
             self.update(cx, |state, cx| {
-                let mut any_element = Box::new((self.render.lock())(state, cx));
+                let mut any_element = Box::new(AnyElement::new(state.render(cx)));
                 any_element.initialize(state, cx);
-                any_element as AnyBox
+                any_element
             })
         })
     }
 
     fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId {
-        cx.with_element_id(self.state.entity_id, |_global_id, cx| {
+        cx.with_element_id(self.model.entity_id, |_global_id, cx| {
             self.update(cx, |state, cx| {
                 let element = element.downcast_mut::<AnyElement<V>>().unwrap();
                 element.layout(state, cx)
@@ -253,7 +255,7 @@ impl<V: 'static> ViewObject for View<V> {
     }
 
     fn paint(&self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
-        cx.with_element_id(self.state.entity_id, |_global_id, cx| {
+        cx.with_element_id(self.model.entity_id, |_global_id, cx| {
             self.update(cx, |state, cx| {
                 let element = element.downcast_mut::<AnyElement<V>>().unwrap();
                 element.paint(state, cx);
@@ -270,8 +272,12 @@ impl<V: 'static> ViewObject for View<V> {
 pub struct AnyView(Arc<dyn ViewObject>);
 
 impl AnyView {
-    pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
-        self.0.as_any().downcast_ref().cloned()
+    pub fn downcast<V: 'static + Send>(self) -> Option<View<V>> {
+        self.0.model().downcast().map(|model| View { model })
+    }
+
+    pub(crate) fn entity_type(&self) -> TypeId {
+        self.0.entity_type()
     }
 
     pub(crate) fn draw(&self, available_space: Size<AvailableSpace>, cx: &mut WindowContext) {
@@ -343,6 +349,18 @@ impl<ParentV: 'static> Component<ParentV> for EraseAnyViewState<ParentV> {
     }
 }
 
+impl<T, E> Render for T
+where
+    T: 'static + FnMut(&mut WindowContext) -> E,
+    E: 'static + Send + Element<T>,
+{
+    type Element = E;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        (self)(cx)
+    }
+}
+
 impl<ParentV: 'static> Element<ParentV> for EraseAnyViewState<ParentV> {
     type ElementState = AnyBox;
 

crates/gpui2/src/window.rs 🔗

@@ -1,14 +1,14 @@
 use crate::{
     px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
-    EntityHandle, EntityId, EventEmitter, ExternalPaths, FileDropEvent, FocusEvent, FontId,
-    GlobalElementId, GlyphId, Handle, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch,
-    KeyMatcher, Keystroke, LayoutId, MainThread, MainThreadOnly, ModelContext, Modifiers,
-    MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
-    PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
-    RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
-    Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
-    WeakHandle, WeakView, WindowOptions, SUBPIXEL_VARIANTS,
+    EntityHandle, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId,
+    GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke,
+    LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite,
+    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
+    PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
+    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
+    TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView,
+    WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use collections::HashMap;
@@ -918,15 +918,13 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             root_view.draw(available_space, cx);
         });
 
-        if let Some(mut active_drag) = self.app.active_drag.take() {
+        if let Some(active_drag) = self.app.active_drag.take() {
             self.stack(1, |cx| {
                 let offset = cx.mouse_position() - active_drag.cursor_offset;
                 cx.with_element_offset(Some(offset), |cx| {
                     let available_space =
                         size(AvailableSpace::MinContent, AvailableSpace::MinContent);
-                    if let Some(drag_handle_view) = &mut active_drag.drag_handle_view {
-                        drag_handle_view.draw(available_space, cx);
-                    }
+                    active_drag.view.draw(available_space, cx);
                     cx.active_drag = Some(active_drag);
                 });
             });
@@ -994,12 +992,12 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             InputEvent::FileDrop(file_drop) => match file_drop {
                 FileDropEvent::Entered { position, files } => {
                     self.window.mouse_position = position;
-                    self.active_drag.get_or_insert_with(|| AnyDrag {
-                        drag_handle_view: None,
-                        cursor_offset: position,
-                        state: Box::new(files),
-                        state_type: TypeId::of::<ExternalPaths>(),
-                    });
+                    if self.active_drag.is_none() {
+                        self.active_drag = Some(AnyDrag {
+                            view: self.build_view(|_| files).into_any(),
+                            cursor_offset: position,
+                        });
+                    }
                     InputEvent::MouseDown(MouseDownEvent {
                         position,
                         button: MouseButton::Left,
@@ -1267,30 +1265,30 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 }
 
 impl Context for WindowContext<'_, '_> {
-    type EntityContext<'a, T> = ModelContext<'a, T>;
+    type ModelContext<'a, T> = ModelContext<'a, T>;
     type Result<T> = T;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Handle<T>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Model<T>
     where
         T: 'static + Send,
     {
         let slot = self.app.entities.reserve();
-        let entity = build_entity(&mut ModelContext::mutable(&mut *self.app, slot.downgrade()));
-        self.entities.insert(slot, entity)
+        let model = build_model(&mut ModelContext::mutable(&mut *self.app, slot.downgrade()));
+        self.entities.insert(slot, model)
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        model: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> R {
-        let mut entity = self.entities.lease(handle);
+        let mut entity = self.entities.lease(model);
         let result = update(
             &mut *entity,
-            &mut ModelContext::mutable(&mut *self.app, handle.downgrade()),
+            &mut ModelContext::mutable(&mut *self.app, model.downgrade()),
         );
         self.entities.end_lease(entity);
         result
@@ -1300,21 +1298,17 @@ impl Context for WindowContext<'_, '_> {
 impl VisualContext for WindowContext<'_, '_> {
     type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>;
 
-    /// Builds a new view in the current window. The first argument is a function that builds
-    /// an entity representing the view's state. It is invoked with a `ViewContext` that provides
-    /// entity-specific access to the window and application state during construction. The second
-    /// argument is a render function that returns a component based on the view's state.
-    fn build_view<E, V>(
+    fn build_view<V>(
         &mut self,
         build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
     ) -> Self::Result<View<V>>
     where
-        E: crate::Component<V>,
         V: 'static + Send,
     {
         let slot = self.app.entities.reserve();
-        let view = View::for_handle(slot.clone(), render);
+        let view = View {
+            model: slot.clone(),
+        };
         let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade());
         let entity = build_view_state(&mut cx);
         self.entities.insert(slot, entity);
@@ -1327,7 +1321,7 @@ impl VisualContext for WindowContext<'_, '_> {
         view: &View<T>,
         update: impl FnOnce(&mut T, &mut Self::ViewContext<'_, '_, T>) -> R,
     ) -> Self::Result<R> {
-        let mut lease = self.app.entities.lease(&view.state);
+        let mut lease = self.app.entities.lease(&view.model);
         let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade());
         let result = update(&mut *lease, &mut cx);
         cx.app.entities.end_lease(lease);
@@ -1582,8 +1576,8 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
         self.view.clone()
     }
 
-    pub fn handle(&self) -> WeakHandle<V> {
-        self.view.state.clone()
+    pub fn model(&self) -> WeakModel<V> {
+        self.view.model.clone()
     }
 
     pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
@@ -1603,8 +1597,8 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
 
     pub fn observe<E>(
         &mut self,
-        handle: &Handle<E>,
-        mut on_notify: impl FnMut(&mut V, Handle<E>, &mut ViewContext<'_, '_, V>) + Send + 'static,
+        handle: &Model<E>,
+        mut on_notify: impl FnMut(&mut V, Model<E>, &mut ViewContext<'_, '_, V>) + Send + 'static,
     ) -> Subscription
     where
         E: 'static,
@@ -1665,7 +1659,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
     ) -> Subscription {
         let window_handle = self.window.handle;
         self.app.release_listeners.insert(
-            self.view.state.entity_id,
+            self.view.model.entity_id,
             Box::new(move |this, cx| {
                 let this = this.downcast_mut().expect("invalid entity type");
                 // todo!("are we okay with silently swallowing the error?")
@@ -1676,7 +1670,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
 
     pub fn observe_release<T: 'static>(
         &mut self,
-        handle: &Handle<T>,
+        handle: &Model<T>,
         mut on_release: impl FnMut(&mut V, &mut T, &mut ViewContext<'_, '_, V>) + Send + 'static,
     ) -> Subscription
     where
@@ -1698,7 +1692,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
     pub fn notify(&mut self) {
         self.window_cx.notify();
         self.window_cx.app.push_effect(Effect::Notify {
-            emitter: self.view.state.entity_id,
+            emitter: self.view.model.entity_id,
         });
     }
 
@@ -1878,7 +1872,7 @@ where
     V::Event: Any + Send,
 {
     pub fn emit(&mut self, event: V::Event) {
-        let emitter = self.view.state.entity_id;
+        let emitter = self.view.model.entity_id;
         self.app.push_effect(Effect::Emit {
             emitter,
             event: Box::new(event),
@@ -1897,41 +1891,36 @@ impl<'a, 'w, V: 'static> MainThread<ViewContext<'a, 'w, V>> {
 }
 
 impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> {
-    type EntityContext<'b, U> = ModelContext<'b, U>;
+    type ModelContext<'b, U> = ModelContext<'b, U>;
     type Result<U> = U;
 
-    fn entity<T>(
+    fn build_model<T>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::EntityContext<'_, T>) -> T,
-    ) -> Handle<T>
+        build_model: impl FnOnce(&mut Self::ModelContext<'_, T>) -> T,
+    ) -> Model<T>
     where
         T: 'static + Send,
     {
-        self.window_cx.entity(build_entity)
+        self.window_cx.build_model(build_model)
     }
 
     fn update_entity<T: 'static, R>(
         &mut self,
-        handle: &Handle<T>,
-        update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, T>) -> R,
+        model: &Model<T>,
+        update: impl FnOnce(&mut T, &mut Self::ModelContext<'_, T>) -> R,
     ) -> R {
-        self.window_cx.update_entity(handle, update)
+        self.window_cx.update_entity(model, update)
     }
 }
 
 impl<V: 'static> VisualContext for ViewContext<'_, '_, V> {
     type ViewContext<'a, 'w, V2> = ViewContext<'a, 'w, V2>;
 
-    fn build_view<E, V2>(
+    fn build_view<W: 'static + Send>(
         &mut self,
-        build_entity: impl FnOnce(&mut Self::ViewContext<'_, '_, V2>) -> V2,
-        render: impl Fn(&mut V2, &mut ViewContext<'_, '_, V2>) -> E + Send + 'static,
-    ) -> Self::Result<View<V2>>
-    where
-        E: crate::Component<V2>,
-        V2: 'static + Send,
-    {
-        self.window_cx.build_view(build_entity, render)
+        build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, W>) -> W,
+    ) -> Self::Result<View<W>> {
+        self.window_cx.build_view(build_view)
     }
 
     fn update_view<V2: 'static, R>(

crates/language2/src/buffer_tests.rs 🔗

@@ -5,7 +5,7 @@ use crate::language_settings::{
 use crate::Buffer;
 use clock::ReplicaId;
 use collections::BTreeMap;
-use gpui2::{AppContext, Handle};
+use gpui2::{AppContext, Model};
 use gpui2::{Context, TestAppContext};
 use indoc::indoc;
 use proto::deserialize_operation;
@@ -42,7 +42,7 @@ fn init_logger() {
 fn test_line_endings(cx: &mut gpui2::AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "one\r\ntwo\rthree")
             .with_language(Arc::new(rust_lang()), cx);
         assert_eq!(buffer.text(), "one\ntwo\nthree");
@@ -138,8 +138,8 @@ fn test_edit_events(cx: &mut gpui2::AppContext) {
     let buffer_1_events = Arc::new(Mutex::new(Vec::new()));
     let buffer_2_events = Arc::new(Mutex::new(Vec::new()));
 
-    let buffer1 = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef"));
-    let buffer2 = cx.entity(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
+    let buffer1 = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), "abcdef"));
+    let buffer2 = cx.build_model(|cx| Buffer::new(1, cx.entity_id().as_u64(), "abcdef"));
     let buffer1_ops = Arc::new(Mutex::new(Vec::new()));
     buffer1.update(cx, {
         let buffer1_ops = buffer1_ops.clone();
@@ -218,7 +218,7 @@ fn test_edit_events(cx: &mut gpui2::AppContext) {
 #[gpui2::test]
 async fn test_apply_diff(cx: &mut TestAppContext) {
     let text = "a\nbb\nccc\ndddd\neeeee\nffffff\n";
-    let buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
+    let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
     let anchor = buffer.update(cx, |buffer, _| buffer.anchor_before(Point::new(3, 3)));
 
     let text = "a\nccc\ndddd\nffffff\n";
@@ -250,7 +250,7 @@ async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) {
     ]
     .join("\n");
 
-    let buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
+    let buffer = cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), text));
 
     // Spawn a task to format the buffer's whitespace.
     // Pause so that the foratting task starts running.
@@ -314,7 +314,7 @@ async fn test_normalize_whitespace(cx: &mut gpui2::TestAppContext) {
 #[gpui2::test]
 async fn test_reparse(cx: &mut gpui2::TestAppContext) {
     let text = "fn a() {}";
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
 
@@ -442,7 +442,7 @@ async fn test_reparse(cx: &mut gpui2::TestAppContext) {
 
 #[gpui2::test]
 async fn test_resetting_language(cx: &mut gpui2::TestAppContext) {
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), "{}").with_language(Arc::new(rust_lang()), cx);
         buffer.set_sync_parse_timeout(Duration::ZERO);
@@ -492,7 +492,7 @@ async fn test_outline(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
@@ -578,7 +578,7 @@ async fn test_outline_nodes_with_newlines(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let outline = buffer
@@ -616,7 +616,7 @@ async fn test_outline_with_extra_context(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(language), cx)
     });
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
@@ -660,7 +660,7 @@ async fn test_symbols_containing(cx: &mut gpui2::TestAppContext) {
     "#
     .unindent();
 
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx)
     });
     let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
@@ -881,7 +881,7 @@ fn test_enclosing_bracket_ranges_where_brackets_are_not_outermost_children(cx: &
 
 #[gpui2::test]
 fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "fn a() { b(|c| {}) }";
         let buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -922,7 +922,7 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
 fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "fn a() {}";
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -965,7 +965,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
         settings.defaults.hard_tabs = Some(true);
     });
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "fn a() {}";
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -1006,7 +1006,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
 fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let entity_id = cx.entity_id();
         let mut buffer = Buffer::new(
             0,
@@ -1080,7 +1080,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
         buffer
     });
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         eprintln!("second buffer: {:?}", cx.entity_id());
 
         let mut buffer = Buffer::new(
@@ -1147,7 +1147,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
 fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer = Buffer::new(
             0,
             cx.entity_id().as_u64(),
@@ -1209,7 +1209,7 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
 fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer = Buffer::new(
             0,
             cx.entity_id().as_u64(),
@@ -1266,7 +1266,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
 fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "a\nb";
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), text).with_language(Arc::new(rust_lang()), cx);
@@ -1284,7 +1284,7 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
 fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "
             const a: usize = 1;
             fn b() {
@@ -1326,7 +1326,7 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
 fn test_autoindent_block_mode(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = r#"
             fn a() {
                 b();
@@ -1410,7 +1410,7 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
 fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = r#"
             fn a() {
                 if b() {
@@ -1490,7 +1490,7 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
 fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = "
             * one
                 - a
@@ -1559,7 +1559,7 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
     language_registry.add(html_language.clone());
     language_registry.add(javascript_language.clone());
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let (text, ranges) = marked_text_ranges(
             &"
                 <div>ˇ
@@ -1610,7 +1610,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
         settings.defaults.tab_size = Some(2.try_into().unwrap());
     });
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let mut buffer =
             Buffer::new(0, cx.entity_id().as_u64(), "").with_language(Arc::new(ruby_lang()), cx);
 
@@ -1653,7 +1653,7 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
 fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let language = Language::new(
             LanguageConfig {
                 name: "JavaScript".into(),
@@ -1742,7 +1742,7 @@ fn test_language_scope_at_with_javascript(cx: &mut AppContext) {
 fn test_language_scope_at_with_rust(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let language = Language::new(
             LanguageConfig {
                 name: "Rust".into(),
@@ -1810,7 +1810,7 @@ fn test_language_scope_at_with_rust(cx: &mut AppContext) {
 fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
     init_settings(cx, |_| {});
 
-    cx.entity(|cx| {
+    cx.build_model(|cx| {
         let text = r#"
             <ol>
             <% people.each do |person| %>
@@ -1858,7 +1858,7 @@ fn test_language_scope_at_with_combined_injections(cx: &mut AppContext) {
 fn test_serialization(cx: &mut gpui2::AppContext) {
     let mut now = Instant::now();
 
-    let buffer1 = cx.entity(|cx| {
+    let buffer1 = cx.build_model(|cx| {
         let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "abc");
         buffer.edit([(3..3, "D")], None, cx);
 
@@ -1881,7 +1881,7 @@ fn test_serialization(cx: &mut gpui2::AppContext) {
     let ops = cx
         .executor()
         .block(buffer1.read(cx).serialize_ops(None, cx));
-    let buffer2 = cx.entity(|cx| {
+    let buffer2 = cx.build_model(|cx| {
         let mut buffer = Buffer::from_proto(1, state, None).unwrap();
         buffer
             .apply_ops(
@@ -1914,10 +1914,11 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
     let mut replica_ids = Vec::new();
     let mut buffers = Vec::new();
     let network = Arc::new(Mutex::new(Network::new(rng.clone())));
-    let base_buffer = cx.entity(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str()));
+    let base_buffer =
+        cx.build_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), base_text.as_str()));
 
     for i in 0..rng.gen_range(min_peers..=max_peers) {
-        let buffer = cx.entity(|cx| {
+        let buffer = cx.build_model(|cx| {
             let state = base_buffer.read(cx).to_proto();
             let ops = cx
                 .executor()
@@ -2034,7 +2035,7 @@ fn test_random_collaboration(cx: &mut AppContext, mut rng: StdRng) {
                     new_replica_id,
                     replica_id
                 );
-                new_buffer = Some(cx.entity(|cx| {
+                new_buffer = Some(cx.build_model(|cx| {
                     let mut new_buffer =
                         Buffer::from_proto(new_replica_id, old_buffer_state, None).unwrap();
                     new_buffer
@@ -2396,7 +2397,7 @@ fn javascript_lang() -> Language {
     .unwrap()
 }
 
-fn get_tree_sexp(buffer: &Handle<Buffer>, cx: &mut gpui2::TestAppContext) -> String {
+fn get_tree_sexp(buffer: &Model<Buffer>, cx: &mut gpui2::TestAppContext) -> String {
     buffer.update(cx, |buffer, _| {
         let snapshot = buffer.snapshot();
         let layers = snapshot.syntax.layers(buffer.as_text_snapshot());
@@ -2412,7 +2413,7 @@ fn assert_bracket_pairs(
     cx: &mut AppContext,
 ) {
     let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
-    let buffer = cx.entity(|cx| {
+    let buffer = cx.build_model(|cx| {
         Buffer::new(0, cx.entity_id().as_u64(), expected_text.clone())
             .with_language(Arc::new(language), cx)
     });

crates/menu2/Cargo.toml 🔗

@@ -0,0 +1,12 @@
+[package]
+name = "menu2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/menu2.rs"
+doctest = false
+
+[dependencies]
+gpui2 = { path = "../gpui2" }

crates/menu2/src/menu2.rs 🔗

@@ -0,0 +1,25 @@
+// todo!(use actions! macro)
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Cancel;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct Confirm;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct SecondaryConfirm;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct SelectPrev;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct SelectNext;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct SelectFirst;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct SelectLast;
+
+#[derive(Clone, Debug, Default, PartialEq)]
+pub struct ShowContextMenu;

crates/prettier2/Cargo.toml 🔗

@@ -16,7 +16,7 @@ client2 = { path = "../client2" }
 collections = { path = "../collections"}
 language2 = { path = "../language2" }
 gpui2 = { path = "../gpui2" }
-fs = { path = "../fs" }
+fs2 = { path = "../fs2" }
 lsp2 = { path = "../lsp2" }
 node_runtime = { path = "../node_runtime"}
 util = { path = "../util" }
@@ -32,4 +32,4 @@ parking_lot.workspace = true
 [dev-dependencies]
 language2 = { path = "../language2", features = ["test-support"] }
 gpui2 = { path = "../gpui2", features = ["test-support"] }
-fs = { path = "../fs",  features = ["test-support"] }
+fs2 = { path = "../fs2",  features = ["test-support"] }

crates/prettier2/src/prettier2.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::Context;
 use collections::{HashMap, HashSet};
-use fs::Fs;
-use gpui2::{AsyncAppContext, Handle};
+use fs2::Fs;
+use gpui2::{AsyncAppContext, Model};
 use language2::{language_settings::language_settings, Buffer, BundledFormatter, Diff};
 use lsp2::{LanguageServer, LanguageServerId};
 use node_runtime::NodeRuntime;
@@ -183,7 +183,7 @@ impl Prettier {
 
     pub async fn format(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         buffer_path: Option<PathBuf>,
         cx: &mut AsyncAppContext,
     ) -> anyhow::Result<Diff> {

crates/project2/Cargo.toml 🔗

@@ -25,7 +25,7 @@ client2 = { path = "../client2" }
 clock = { path = "../clock" }
 collections = { path = "../collections" }
 db2 = { path = "../db2" }
-fs = { path = "../fs" }
+fs2 = { path = "../fs2" }
 fsevent = { path = "../fsevent" }
 fuzzy2 = { path = "../fuzzy2" }
 git = { path = "../git" }
@@ -71,7 +71,7 @@ pretty_assertions.workspace = true
 client2 = { path = "../client2", features = ["test-support"] }
 collections = { path = "../collections", features = ["test-support"] }
 db2 = { path = "../db2", features = ["test-support"] }
-fs = { path = "../fs",  features = ["test-support"] }
+fs2 = { path = "../fs2",  features = ["test-support"] }
 gpui2 = { path = "../gpui2", features = ["test-support"] }
 language2 = { path = "../language2", features = ["test-support"] }
 lsp2 = { path = "../lsp2", features = ["test-support"] }

crates/project2/src/lsp_command.rs 🔗

@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Result};
 use async_trait::async_trait;
 use client2::proto::{self, PeerId};
 use futures::future;
-use gpui2::{AppContext, AsyncAppContext, Handle};
+use gpui2::{AppContext, AsyncAppContext, Model};
 use language2::{
     language_settings::{language_settings, InlayHintKind},
     point_from_lsp, point_to_lsp,
@@ -53,8 +53,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send {
     async fn response_from_lsp(
         self,
         message: <Self::LspRequest as lsp2::request::Request>::Result,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
@@ -63,8 +63,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self>;
 
@@ -79,8 +79,8 @@ pub(crate) trait LspCommand: 'static + Sized + Send {
     async fn response_from_proto(
         self,
         message: <Self::ProtoRequest as proto::RequestMessage>::Response,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Self::Response>;
 
@@ -180,8 +180,8 @@ impl LspCommand for PrepareRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::PrepareRenameResponse>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
@@ -215,8 +215,8 @@ impl LspCommand for PrepareRename {
 
     async fn from_proto(
         message: proto::PrepareRename,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -256,8 +256,8 @@ impl LspCommand for PrepareRename {
     async fn response_from_proto(
         self,
         message: proto::PrepareRenameResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Range<Anchor>>> {
         if message.can_rename {
@@ -307,8 +307,8 @@ impl LspCommand for PerformRename {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::WorkspaceEdit>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
@@ -343,8 +343,8 @@ impl LspCommand for PerformRename {
 
     async fn from_proto(
         message: proto::PerformRename,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -379,8 +379,8 @@ impl LspCommand for PerformRename {
     async fn response_from_proto(
         self,
         message: proto::PerformRenameResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<ProjectTransaction> {
         let message = message
@@ -426,8 +426,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::GotoDefinitionResponse>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -447,8 +447,8 @@ impl LspCommand for GetDefinition {
 
     async fn from_proto(
         message: proto::GetDefinition,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -479,8 +479,8 @@ impl LspCommand for GetDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetDefinitionResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -527,8 +527,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::GotoTypeDefinitionResponse>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
@@ -548,8 +548,8 @@ impl LspCommand for GetTypeDefinition {
 
     async fn from_proto(
         message: proto::GetTypeDefinition,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -580,8 +580,8 @@ impl LspCommand for GetTypeDefinition {
     async fn response_from_proto(
         self,
         message: proto::GetTypeDefinitionResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         cx: AsyncAppContext,
     ) -> Result<Vec<LocationLink>> {
         location_links_from_proto(message.links, project, cx).await
@@ -593,8 +593,8 @@ impl LspCommand for GetTypeDefinition {
 }
 
 fn language_server_for_buffer(
-    project: &Handle<Project>,
-    buffer: &Handle<Buffer>,
+    project: &Model<Project>,
+    buffer: &Model<Buffer>,
     server_id: LanguageServerId,
     cx: &mut AsyncAppContext,
 ) -> Result<(Arc<CachedLspAdapter>, Arc<LanguageServer>)> {
@@ -609,7 +609,7 @@ fn language_server_for_buffer(
 
 async fn location_links_from_proto(
     proto_links: Vec<proto::LocationLink>,
-    project: Handle<Project>,
+    project: Model<Project>,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
     let mut links = Vec::new();
@@ -671,8 +671,8 @@ async fn location_links_from_proto(
 
 async fn location_links_from_lsp(
     message: Option<lsp2::GotoDefinitionResponse>,
-    project: Handle<Project>,
-    buffer: Handle<Buffer>,
+    project: Model<Project>,
+    buffer: Model<Buffer>,
     server_id: LanguageServerId,
     mut cx: AsyncAppContext,
 ) -> Result<Vec<LocationLink>> {
@@ -814,8 +814,8 @@ impl LspCommand for GetReferences {
     async fn response_from_lsp(
         self,
         locations: Option<Vec<lsp2::Location>>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
@@ -868,8 +868,8 @@ impl LspCommand for GetReferences {
 
     async fn from_proto(
         message: proto::GetReferences,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -910,8 +910,8 @@ impl LspCommand for GetReferences {
     async fn response_from_proto(
         self,
         message: proto::GetReferencesResponse,
-        project: Handle<Project>,
-        _: Handle<Buffer>,
+        project: Model<Project>,
+        _: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Location>> {
         let mut locations = Vec::new();
@@ -977,8 +977,8 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_lsp(
         self,
         lsp_highlights: Option<Vec<lsp2::DocumentHighlight>>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
@@ -1016,8 +1016,8 @@ impl LspCommand for GetDocumentHighlights {
 
     async fn from_proto(
         message: proto::GetDocumentHighlights,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1060,8 +1060,8 @@ impl LspCommand for GetDocumentHighlights {
     async fn response_from_proto(
         self,
         message: proto::GetDocumentHighlightsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<DocumentHighlight>> {
         let mut highlights = Vec::new();
@@ -1123,8 +1123,8 @@ impl LspCommand for GetHover {
     async fn response_from_lsp(
         self,
         message: Option<lsp2::Hover>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         _: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
@@ -1206,8 +1206,8 @@ impl LspCommand for GetHover {
 
     async fn from_proto(
         message: Self::ProtoRequest,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1272,8 +1272,8 @@ impl LspCommand for GetHover {
     async fn response_from_proto(
         self,
         message: proto::GetHoverResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self::Response> {
         let contents: Vec<_> = message
@@ -1341,8 +1341,8 @@ impl LspCommand for GetCompletions {
     async fn response_from_lsp(
         self,
         completions: Option<lsp2::CompletionResponse>,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
@@ -1484,8 +1484,8 @@ impl LspCommand for GetCompletions {
 
     async fn from_proto(
         message: proto::GetCompletions,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let version = deserialize_version(&message.version);
@@ -1523,8 +1523,8 @@ impl LspCommand for GetCompletions {
     async fn response_from_proto(
         self,
         message: proto::GetCompletionsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<Completion>> {
         buffer
@@ -1589,8 +1589,8 @@ impl LspCommand for GetCodeActions {
     async fn response_from_lsp(
         self,
         actions: Option<lsp2::CodeActionResponse>,
-        _: Handle<Project>,
-        _: Handle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         server_id: LanguageServerId,
         _: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
@@ -1623,8 +1623,8 @@ impl LspCommand for GetCodeActions {
 
     async fn from_proto(
         message: proto::GetCodeActions,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -1663,8 +1663,8 @@ impl LspCommand for GetCodeActions {
     async fn response_from_proto(
         self,
         message: proto::GetCodeActionsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Vec<CodeAction>> {
         buffer
@@ -1726,8 +1726,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp2::TextEdit>>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
@@ -1763,8 +1763,8 @@ impl LspCommand for OnTypeFormatting {
 
     async fn from_proto(
         message: proto::OnTypeFormatting,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let position = message
@@ -1805,8 +1805,8 @@ impl LspCommand for OnTypeFormatting {
     async fn response_from_proto(
         self,
         message: proto::OnTypeFormattingResponse,
-        _: Handle<Project>,
-        _: Handle<Buffer>,
+        _: Model<Project>,
+        _: Model<Buffer>,
         _: AsyncAppContext,
     ) -> Result<Option<Transaction>> {
         let Some(transaction) = message.transaction else {
@@ -1825,7 +1825,7 @@ impl LspCommand for OnTypeFormatting {
 impl InlayHints {
     pub async fn lsp_to_project_hint(
         lsp_hint: lsp2::InlayHint,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         server_id: LanguageServerId,
         resolve_state: ResolveState,
         force_no_type_left_padding: bool,
@@ -2230,8 +2230,8 @@ impl LspCommand for InlayHints {
     async fn response_from_lsp(
         self,
         message: Option<Vec<lsp2::InlayHint>>,
-        project: Handle<Project>,
-        buffer: Handle<Buffer>,
+        project: Model<Project>,
+        buffer: Model<Buffer>,
         server_id: LanguageServerId,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
@@ -2286,8 +2286,8 @@ impl LspCommand for InlayHints {
 
     async fn from_proto(
         message: proto::InlayHints,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> Result<Self> {
         let start = message
@@ -2326,8 +2326,8 @@ impl LspCommand for InlayHints {
     async fn response_from_proto(
         self,
         message: proto::InlayHintsResponse,
-        _: Handle<Project>,
-        buffer: Handle<Buffer>,
+        _: Model<Project>,
+        buffer: Model<Buffer>,
         mut cx: AsyncAppContext,
     ) -> anyhow::Result<Vec<InlayHint>> {
         buffer

crates/project2/src/project2.rs 🔗

@@ -26,8 +26,8 @@ use futures::{
 };
 use globset::{Glob, GlobSet, GlobSetBuilder};
 use gpui2::{
-    AnyHandle, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Handle, ModelContext,
-    Task, WeakHandle,
+    AnyModel, AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext,
+    Task, WeakModel,
 };
 use itertools::Itertools;
 use language2::{
@@ -89,7 +89,7 @@ use util::{
     post_inc, ResultExt, TryFutureExt as _,
 };
 
-pub use fs::*;
+pub use fs2::*;
 pub use worktree::*;
 
 const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4;
@@ -128,7 +128,7 @@ pub struct Project {
     next_entry_id: Arc<AtomicUsize>,
     join_project_response_message_id: u32,
     next_diagnostic_group_id: usize,
-    user_store: Handle<UserStore>,
+    user_store: Model<UserStore>,
     fs: Arc<dyn Fs>,
     client_state: Option<ProjectClientState>,
     collaborators: HashMap<proto::PeerId, Collaborator>,
@@ -140,20 +140,20 @@ pub struct Project {
     #[allow(clippy::type_complexity)]
     loading_buffers_by_path: HashMap<
         ProjectPath,
-        postage::watch::Receiver<Option<Result<Handle<Buffer>, Arc<anyhow::Error>>>>,
+        postage::watch::Receiver<Option<Result<Model<Buffer>, Arc<anyhow::Error>>>>,
     >,
     #[allow(clippy::type_complexity)]
     loading_local_worktrees:
-        HashMap<Arc<Path>, Shared<Task<Result<Handle<Worktree>, Arc<anyhow::Error>>>>>,
+        HashMap<Arc<Path>, Shared<Task<Result<Model<Worktree>, Arc<anyhow::Error>>>>>,
     opened_buffers: HashMap<u64, OpenBuffer>,
     local_buffer_ids_by_path: HashMap<ProjectPath, u64>,
     local_buffer_ids_by_entry_id: HashMap<ProjectEntryId, u64>,
     /// A mapping from a buffer ID to None means that we've started waiting for an ID but haven't finished loading it.
     /// Used for re-issuing buffer requests when peers temporarily disconnect
-    incomplete_remote_buffers: HashMap<u64, Option<Handle<Buffer>>>,
+    incomplete_remote_buffers: HashMap<u64, Option<Model<Buffer>>>,
     buffer_snapshots: HashMap<u64, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
     buffers_being_formatted: HashSet<u64>,
-    buffers_needing_diff: HashSet<WeakHandle<Buffer>>,
+    buffers_needing_diff: HashSet<WeakModel<Buffer>>,
     git_diff_debouncer: DelayedDebounced,
     nonce: u128,
     _maintain_buffer_languages: Task<()>,
@@ -244,15 +244,15 @@ enum LocalProjectUpdate {
 }
 
 enum OpenBuffer {
-    Strong(Handle<Buffer>),
-    Weak(WeakHandle<Buffer>),
+    Strong(Model<Buffer>),
+    Weak(WeakModel<Buffer>),
     Operations(Vec<Operation>),
 }
 
 #[derive(Clone)]
 enum WorktreeHandle {
-    Strong(Handle<Worktree>),
-    Weak(WeakHandle<Worktree>),
+    Strong(Model<Worktree>),
+    Weak(WeakModel<Worktree>),
 }
 
 enum ProjectClientState {
@@ -344,7 +344,7 @@ pub struct DiagnosticSummary {
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct Location {
-    pub buffer: Handle<Buffer>,
+    pub buffer: Model<Buffer>,
     pub range: Range<language2::Anchor>,
 }
 
@@ -457,7 +457,7 @@ impl Hover {
 }
 
 #[derive(Default)]
-pub struct ProjectTransaction(pub HashMap<Handle<Buffer>, language2::Transaction>);
+pub struct ProjectTransaction(pub HashMap<Model<Buffer>, language2::Transaction>);
 
 impl DiagnosticSummary {
     fn new<'a, T: 'a>(diagnostics: impl IntoIterator<Item = &'a DiagnosticEntry<T>>) -> Self {
@@ -527,7 +527,7 @@ pub enum FormatTrigger {
 }
 
 struct ProjectLspAdapterDelegate {
-    project: Handle<Project>,
+    project: Model<Project>,
     http_client: Arc<dyn HttpClient>,
 }
 
@@ -543,7 +543,7 @@ impl FormatTrigger {
 #[derive(Clone, Debug, PartialEq)]
 enum SearchMatchCandidate {
     OpenBuffer {
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         // This might be an unnamed file without representation on filesystem
         path: Option<Arc<Path>>,
     },
@@ -621,12 +621,12 @@ impl Project {
     pub fn local(
         client: Arc<Client>,
         node: Arc<dyn NodeRuntime>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         cx: &mut AppContext,
-    ) -> Handle<Self> {
-        cx.entity(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.build_model(|cx: &mut ModelContext<Self>| {
             let (tx, rx) = mpsc::unbounded();
             cx.spawn(move |this, cx| Self::send_buffer_ordered_messages(this, rx, cx))
                 .detach();
@@ -687,11 +687,11 @@ impl Project {
     pub async fn remote(
         remote_id: u64,
         client: Arc<Client>,
-        user_store: Handle<UserStore>,
+        user_store: Model<UserStore>,
         languages: Arc<LanguageRegistry>,
         fs: Arc<dyn Fs>,
         mut cx: AsyncAppContext,
-    ) -> Result<Handle<Self>> {
+    ) -> Result<Model<Self>> {
         client.authenticate_and_connect(true, &cx).await?;
 
         let subscription = client.subscribe_to_entity(remote_id)?;
@@ -700,7 +700,7 @@ impl Project {
                 project_id: remote_id,
             })
             .await?;
-        let this = cx.entity(|cx| {
+        let this = cx.build_model(|cx| {
             let replica_id = response.payload.replica_id as ReplicaId;
 
             let mut worktrees = Vec::new();
@@ -981,7 +981,7 @@ impl Project {
         cx.notify();
     }
 
-    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Handle<Buffer>> {
+    pub fn buffer_for_id(&self, remote_id: u64) -> Option<Model<Buffer>> {
         self.opened_buffers
             .get(&remote_id)
             .and_then(|buffer| buffer.upgrade())
@@ -995,11 +995,11 @@ impl Project {
         self.client.clone()
     }
 
-    pub fn user_store(&self) -> Handle<UserStore> {
+    pub fn user_store(&self) -> Model<UserStore> {
         self.user_store.clone()
     }
 
-    pub fn opened_buffers(&self) -> Vec<Handle<Buffer>> {
+    pub fn opened_buffers(&self) -> Vec<Model<Buffer>> {
         self.opened_buffers
             .values()
             .filter_map(|b| b.upgrade())
@@ -1061,7 +1061,7 @@ impl Project {
     }
 
     /// Collect all worktrees, including ones that don't appear in the project panel
-    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Handle<Worktree>> {
+    pub fn worktrees<'a>(&'a self) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees
             .iter()
             .filter_map(move |worktree| worktree.upgrade())
@@ -1071,7 +1071,7 @@ impl Project {
     pub fn visible_worktrees<'a>(
         &'a self,
         cx: &'a AppContext,
-    ) -> impl 'a + DoubleEndedIterator<Item = Handle<Worktree>> {
+    ) -> impl 'a + DoubleEndedIterator<Item = Model<Worktree>> {
         self.worktrees.iter().filter_map(|worktree| {
             worktree.upgrade().and_then(|worktree| {
                 if worktree.read(cx).is_visible() {
@@ -1088,7 +1088,7 @@ impl Project {
             .map(|tree| tree.read(cx).root_name())
     }
 
-    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Handle<Worktree>> {
+    pub fn worktree_for_id(&self, id: WorktreeId, cx: &AppContext) -> Option<Model<Worktree>> {
         self.worktrees()
             .find(|worktree| worktree.read(cx).id() == id)
     }
@@ -1097,7 +1097,7 @@ impl Project {
         &self,
         entry_id: ProjectEntryId,
         cx: &AppContext,
-    ) -> Option<Handle<Worktree>> {
+    ) -> Option<Model<Worktree>> {
         self.worktrees()
             .find(|worktree| worktree.read(cx).contains_entry(entry_id))
     }
@@ -1652,12 +1652,12 @@ impl Project {
         text: &str,
         language: Option<Arc<Language>>,
         cx: &mut ModelContext<Self>,
-    ) -> Result<Handle<Buffer>> {
+    ) -> Result<Model<Buffer>> {
         if self.is_remote() {
             return Err(anyhow!("creating buffers as a guest is not supported yet"));
         }
         let id = post_inc(&mut self.next_buffer_id);
-        let buffer = cx.entity(|cx| {
+        let buffer = cx.build_model(|cx| {
             Buffer::new(self.replica_id(), id, text).with_language(
                 language.unwrap_or_else(|| language2::PLAIN_TEXT.clone()),
                 cx,
@@ -1671,7 +1671,7 @@ impl Project {
         &mut self,
         path: impl Into<ProjectPath>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(ProjectEntryId, AnyHandle)>> {
+    ) -> Task<Result<(ProjectEntryId, AnyModel)>> {
         let task = self.open_buffer(path, cx);
         cx.spawn(move |_, mut cx| async move {
             let buffer = task.await?;
@@ -1681,7 +1681,7 @@ impl Project {
                 })?
                 .ok_or_else(|| anyhow!("no project entry"))?;
 
-            let buffer: &AnyHandle = &buffer;
+            let buffer: &AnyModel = &buffer;
             Ok((project_entry_id, buffer.clone()))
         })
     }
@@ -1690,7 +1690,7 @@ impl Project {
         &mut self,
         abs_path: impl AsRef<Path>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if let Some((worktree, relative_path)) = self.find_local_worktree(abs_path.as_ref(), cx) {
             self.open_buffer((worktree.read(cx).id(), relative_path), cx)
         } else {
@@ -1702,7 +1702,7 @@ impl Project {
         &mut self,
         path: impl Into<ProjectPath>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let project_path = path.into();
         let worktree = if let Some(worktree) = self.worktree_for_id(project_path.worktree_id, cx) {
             worktree
@@ -1757,9 +1757,9 @@ impl Project {
     fn open_local_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let buffer_id = post_inc(&mut self.next_buffer_id);
         let load_buffer = worktree.update(cx, |worktree, cx| {
             let worktree = worktree.as_local_mut().unwrap();
@@ -1775,9 +1775,9 @@ impl Project {
     fn open_remote_buffer_internal(
         &mut self,
         path: &Arc<Path>,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let rpc = self.client.clone();
         let project_id = self.remote_id().unwrap();
         let remote_worktree_id = worktree.read(cx).id();
@@ -1805,7 +1805,7 @@ impl Project {
         language_server_id: LanguageServerId,
         language_server_name: LanguageServerName,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         cx.spawn(move |this, mut cx| async move {
             let abs_path = abs_path
                 .to_file_path()
@@ -1843,7 +1843,7 @@ impl Project {
         &mut self,
         id: u64,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if let Some(buffer) = self.buffer_for_id(id) {
             Task::ready(Ok(buffer))
         } else if self.is_local() {
@@ -1866,7 +1866,7 @@ impl Project {
 
     pub fn save_buffers(
         &self,
-        buffers: HashSet<Handle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         cx.spawn(move |this, mut cx| async move {
@@ -1881,7 +1881,7 @@ impl Project {
 
     pub fn save_buffer(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
         let Some(file) = File::from_dyn(buffer.read(cx).file()) else {
@@ -1897,7 +1897,7 @@ impl Project {
 
     pub fn save_buffer_as(
         &mut self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         abs_path: PathBuf,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
@@ -1933,7 +1933,7 @@ impl Project {
         &mut self,
         path: &ProjectPath,
         cx: &mut ModelContext<Self>,
-    ) -> Option<Handle<Buffer>> {
+    ) -> Option<Model<Buffer>> {
         let worktree = self.worktree_for_id(path.worktree_id, cx)?;
         self.opened_buffers.values().find_map(|buffer| {
             let buffer = buffer.upgrade()?;
@@ -1948,7 +1948,7 @@ impl Project {
 
     fn register_buffer(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Result<()> {
         self.request_buffer_diff_recalculation(buffer, cx);
@@ -2030,7 +2030,7 @@ impl Project {
 
     fn register_buffer_with_language_servers(
         &mut self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) {
         let buffer = buffer_handle.read(cx);
@@ -2114,7 +2114,7 @@ impl Project {
 
     fn unregister_buffer_from_language_servers(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         old_file: &File,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2149,7 +2149,7 @@ impl Project {
 
     fn register_buffer_with_copilot(
         &self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) {
         if let Some(copilot) = Copilot::global(cx) {
@@ -2158,7 +2158,7 @@ impl Project {
     }
 
     async fn send_buffer_ordered_messages(
-        this: WeakHandle<Self>,
+        this: WeakModel<Self>,
         rx: UnboundedReceiver<BufferOrderedMessage>,
         mut cx: AsyncAppContext,
     ) -> Result<()> {
@@ -2166,7 +2166,7 @@ impl Project {
 
         let mut operations_by_buffer_id = HashMap::default();
         async fn flush_operations(
-            this: &WeakHandle<Project>,
+            this: &WeakModel<Project>,
             operations_by_buffer_id: &mut HashMap<u64, Vec<proto::Operation>>,
             needs_resync_with_host: &mut bool,
             is_local: bool,
@@ -2267,7 +2267,7 @@ impl Project {
 
     fn on_buffer_event(
         &mut self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         event: &BufferEvent,
         cx: &mut ModelContext<Self>,
     ) -> Option<()> {
@@ -2480,7 +2480,7 @@ impl Project {
 
     fn request_buffer_diff_recalculation(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) {
         self.buffers_needing_diff.insert(buffer.downgrade());
@@ -2676,7 +2676,7 @@ impl Project {
 
     fn detect_language_for_buffer(
         &mut self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         cx: &mut ModelContext<Self>,
     ) -> Option<()> {
         // If the buffer has a language, set it and start the language server if we haven't already.
@@ -2694,7 +2694,7 @@ impl Project {
 
     pub fn set_language_for_buffer(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         new_language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
@@ -2735,7 +2735,7 @@ impl Project {
 
     fn start_language_servers(
         &mut self,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         worktree_path: Arc<Path>,
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
@@ -2931,7 +2931,7 @@ impl Project {
     }
 
     async fn setup_and_insert_language_server(
-        this: WeakHandle<Self>,
+        this: WeakModel<Self>,
         initialization_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         adapter: Arc<CachedLspAdapter>,
@@ -2970,7 +2970,7 @@ impl Project {
     }
 
     async fn setup_pending_language_server(
-        this: WeakHandle<Self>,
+        this: WeakModel<Self>,
         initialization_options: Option<serde_json::Value>,
         pending_server: PendingLanguageServer,
         adapter: Arc<CachedLspAdapter>,
@@ -3350,10 +3350,10 @@ impl Project {
 
     pub fn restart_language_servers_for_buffers(
         &mut self,
-        buffers: impl IntoIterator<Item = Handle<Buffer>>,
+        buffers: impl IntoIterator<Item = Model<Buffer>>,
         cx: &mut ModelContext<Self>,
     ) -> Option<()> {
-        let language_server_lookup_info: HashSet<(Handle<Worktree>, Arc<Language>)> = buffers
+        let language_server_lookup_info: HashSet<(Model<Worktree>, Arc<Language>)> = buffers
             .into_iter()
             .filter_map(|buffer| {
                 let buffer = buffer.read(cx);
@@ -3377,7 +3377,7 @@ impl Project {
     // TODO This will break in the case where the adapter's root paths and worktrees are not equal
     fn restart_language_servers(
         &mut self,
-        worktree: Handle<Worktree>,
+        worktree: Model<Worktree>,
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
@@ -3748,7 +3748,7 @@ impl Project {
     }
 
     async fn on_lsp_workspace_edit(
-        this: WeakHandle<Self>,
+        this: WeakModel<Self>,
         params: lsp2::ApplyWorkspaceEditParams,
         server_id: LanguageServerId,
         adapter: Arc<CachedLspAdapter>,
@@ -3949,7 +3949,7 @@ impl Project {
 
     fn update_buffer_diagnostics(
         &mut self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         server_id: LanguageServerId,
         version: Option<i32>,
         mut diagnostics: Vec<DiagnosticEntry<Unclipped<PointUtf16>>>,
@@ -4022,7 +4022,7 @@ impl Project {
 
     pub fn reload_buffers(
         &self,
-        buffers: HashSet<Handle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<ProjectTransaction>> {
@@ -4088,7 +4088,7 @@ impl Project {
 
     pub fn format(
         &self,
-        buffers: HashSet<Handle<Buffer>>,
+        buffers: HashSet<Model<Buffer>>,
         push_to_history: bool,
         trigger: FormatTrigger,
         cx: &mut ModelContext<Project>,
@@ -4360,8 +4360,8 @@ impl Project {
     }
 
     async fn format_via_lsp(
-        this: &WeakHandle<Self>,
-        buffer: &Handle<Buffer>,
+        this: &WeakModel<Self>,
+        buffer: &Model<Buffer>,
         abs_path: &Path,
         language_server: &Arc<LanguageServer>,
         tab_size: NonZeroU32,
@@ -4410,7 +4410,7 @@ impl Project {
     }
 
     async fn format_via_external_command(
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         buffer_abs_path: &Path,
         command: &str,
         arguments: &[String],
@@ -4470,7 +4470,7 @@ impl Project {
 
     pub fn definition<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<LocationLink>>> {
@@ -4485,7 +4485,7 @@ impl Project {
 
     pub fn type_definition<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<LocationLink>>> {
@@ -4500,7 +4500,7 @@ impl Project {
 
     pub fn references<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Location>>> {
@@ -4515,7 +4515,7 @@ impl Project {
 
     pub fn document_highlights<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<DocumentHighlight>>> {
@@ -4694,7 +4694,7 @@ impl Project {
         &mut self,
         symbol: &Symbol,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         if self.is_local() {
             let language_server_id = if let Some(id) = self.language_server_ids.get(&(
                 symbol.source_worktree_id,
@@ -4748,7 +4748,7 @@ impl Project {
 
     pub fn hover<T: ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Option<Hover>>> {
@@ -4763,7 +4763,7 @@ impl Project {
 
     pub fn completions<T: ToOffset + ToPointUtf16>(
         &self,
-        buffer: &Handle<Buffer>,
+        buffer: &Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<Completion>>> {
@@ -4817,7 +4817,7 @@ impl Project {
 
     pub fn apply_additional_edits_for_completion(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         completion: Completion,
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
@@ -4928,7 +4928,7 @@ impl Project {
 
     pub fn code_actions<T: Clone + ToOffset>(
         &self,
-        buffer_handle: &Handle<Buffer>,
+        buffer_handle: &Model<Buffer>,
         range: Range<T>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Vec<CodeAction>>> {
@@ -4944,7 +4944,7 @@ impl Project {
 
     pub fn apply_code_action(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         mut action: CodeAction,
         push_to_history: bool,
         cx: &mut ModelContext<Self>,
@@ -5052,7 +5052,7 @@ impl Project {
 
     fn apply_on_type_formatting(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: Anchor,
         trigger: String,
         cx: &mut ModelContext<Self>,
@@ -5113,8 +5113,8 @@ impl Project {
     }
 
     async fn deserialize_edits(
-        this: Handle<Self>,
-        buffer_to_edit: Handle<Buffer>,
+        this: Model<Self>,
+        buffer_to_edit: Model<Buffer>,
         edits: Vec<lsp2::TextEdit>,
         push_to_history: bool,
         _: Arc<CachedLspAdapter>,
@@ -5155,7 +5155,7 @@ impl Project {
     }
 
     async fn deserialize_workspace_edit(
-        this: Handle<Self>,
+        this: Model<Self>,
         edit: lsp2::WorkspaceEdit,
         push_to_history: bool,
         lsp_adapter: Arc<CachedLspAdapter>,
@@ -5201,7 +5201,7 @@ impl Project {
                         fs.create_file(
                             &abs_path,
                             op.options
-                                .map(|options| fs::CreateOptions {
+                                .map(|options| fs2::CreateOptions {
                                     overwrite: options.overwrite.unwrap_or(false),
                                     ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
                                 })
@@ -5224,7 +5224,7 @@ impl Project {
                         &source_abs_path,
                         &target_abs_path,
                         op.options
-                            .map(|options| fs::RenameOptions {
+                            .map(|options| fs2::RenameOptions {
                                 overwrite: options.overwrite.unwrap_or(false),
                                 ignore_if_exists: options.ignore_if_exists.unwrap_or(false),
                             })
@@ -5240,7 +5240,7 @@ impl Project {
                         .map_err(|_| anyhow!("can't convert URI to path"))?;
                     let options = op
                         .options
-                        .map(|options| fs::RemoveOptions {
+                        .map(|options| fs2::RemoveOptions {
                             recursive: options.recursive.unwrap_or(false),
                             ignore_if_not_exists: options.ignore_if_not_exists.unwrap_or(false),
                         })
@@ -5310,7 +5310,7 @@ impl Project {
 
     pub fn prepare_rename<T: ToPointUtf16>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: T,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<Option<Range<Anchor>>>> {
@@ -5325,7 +5325,7 @@ impl Project {
 
     pub fn perform_rename<T: ToPointUtf16>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: T,
         new_name: String,
         push_to_history: bool,
@@ -5346,7 +5346,7 @@ impl Project {
 
     pub fn on_type_format<T: ToPointUtf16>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         position: T,
         trigger: String,
         push_to_history: bool,
@@ -5375,7 +5375,7 @@ impl Project {
 
     pub fn inlay_hints<T: ToOffset>(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         range: Range<T>,
         cx: &mut ModelContext<Self>,
     ) -> Task<anyhow::Result<Vec<InlayHint>>> {
@@ -5436,7 +5436,7 @@ impl Project {
     pub fn resolve_inlay_hint(
         &self,
         hint: InlayHint,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         server_id: LanguageServerId,
         cx: &mut ModelContext<Self>,
     ) -> Task<anyhow::Result<InlayHint>> {
@@ -5501,7 +5501,7 @@ impl Project {
         &self,
         query: SearchQuery,
         cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Handle<Buffer>, Vec<Range<Anchor>>)> {
+    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
         if self.is_local() {
             self.search_local(query, cx)
         } else if let Some(project_id) = self.remote_id() {
@@ -5545,7 +5545,7 @@ impl Project {
         &self,
         query: SearchQuery,
         cx: &mut ModelContext<Self>,
-    ) -> Receiver<(Handle<Buffer>, Vec<Range<Anchor>>)> {
+    ) -> Receiver<(Model<Buffer>, Vec<Range<Anchor>>)> {
         // Local search is split into several phases.
         // TL;DR is that we do 2 passes; initial pass to pick files which contain at least one match
         // and the second phase that finds positions of all the matches found in the candidate files.
@@ -5638,7 +5638,7 @@ impl Project {
                     .scoped(|scope| {
                         #[derive(Clone)]
                         struct FinishedStatus {
-                            entry: Option<(Handle<Buffer>, Vec<Range<Anchor>>)>,
+                            entry: Option<(Model<Buffer>, Vec<Range<Anchor>>)>,
                             buffer_index: SearchMatchCandidateIndex,
                         }
 
@@ -5728,8 +5728,8 @@ impl Project {
 
     /// Pick paths that might potentially contain a match of a given search query.
     async fn background_search(
-        unnamed_buffers: Vec<Handle<Buffer>>,
-        opened_buffers: HashMap<Arc<Path>, (Handle<Buffer>, BufferSnapshot)>,
+        unnamed_buffers: Vec<Model<Buffer>>,
+        opened_buffers: HashMap<Arc<Path>, (Model<Buffer>, BufferSnapshot)>,
         executor: Executor,
         fs: Arc<dyn Fs>,
         workers: usize,
@@ -5829,7 +5829,7 @@ impl Project {
 
     fn request_lsp<R: LspCommand>(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         server: LanguageServerToQuery,
         request: R,
         cx: &mut ModelContext<Self>,
@@ -5893,7 +5893,7 @@ impl Project {
 
     fn send_lsp_proto_request<R: LspCommand>(
         &self,
-        buffer: Handle<Buffer>,
+        buffer: Model<Buffer>,
         project_id: u64,
         request: R,
         cx: &mut ModelContext<'_, Project>,
@@ -5922,7 +5922,7 @@ impl Project {
     ) -> (
         futures::channel::oneshot::Receiver<Vec<SearchMatchCandidate>>,
         Receiver<(
-            Option<(Handle<Buffer>, BufferSnapshot)>,
+            Option<(Model<Buffer>, BufferSnapshot)>,
             SearchMatchCandidateIndex,
         )>,
     ) {
@@ -5976,7 +5976,7 @@ impl Project {
         abs_path: impl AsRef<Path>,
         visible: bool,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<(Handle<Worktree>, PathBuf)>> {
+    ) -> Task<Result<(Model<Worktree>, PathBuf)>> {
         let abs_path = abs_path.as_ref();
         if let Some((tree, relative_path)) = self.find_local_worktree(abs_path, cx) {
             Task::ready(Ok((tree, relative_path)))
@@ -5991,7 +5991,7 @@ impl Project {
         &self,
         abs_path: &Path,
         cx: &AppContext,
-    ) -> Option<(Handle<Worktree>, PathBuf)> {
+    ) -> Option<(Model<Worktree>, PathBuf)> {
         for tree in &self.worktrees {
             if let Some(tree) = tree.upgrade() {
                 if let Some(relative_path) = tree
@@ -6018,7 +6018,7 @@ impl Project {
         abs_path: impl AsRef<Path>,
         visible: bool,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Result<Handle<Worktree>>> {
+    ) -> Task<Result<Model<Worktree>>> {
         let fs = self.fs.clone();
         let client = self.client.clone();
         let next_entry_id = self.next_entry_id.clone();
@@ -6078,7 +6078,7 @@ impl Project {
         self.metadata_changed(cx);
     }
 
-    fn add_worktree(&mut self, worktree: &Handle<Worktree>, cx: &mut ModelContext<Self>) {
+    fn add_worktree(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
         cx.observe(worktree, |_, _, cx| cx.notify()).detach();
         if worktree.read(cx).is_local() {
             cx.subscribe(worktree, |this, worktree, event, cx| match event {
@@ -6128,7 +6128,7 @@ impl Project {
 
     fn update_local_worktree_buffers(
         &mut self,
-        worktree_handle: &Handle<Worktree>,
+        worktree_handle: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<Self>,
     ) {
@@ -6242,7 +6242,7 @@ impl Project {
 
     fn update_local_worktree_language_servers(
         &mut self,
-        worktree_handle: &Handle<Worktree>,
+        worktree_handle: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<Self>,
     ) {
@@ -6304,7 +6304,7 @@ impl Project {
 
     fn update_local_worktree_buffers_git_repos(
         &mut self,
-        worktree_handle: Handle<Worktree>,
+        worktree_handle: Model<Worktree>,
         changed_repos: &UpdatedGitRepositoriesSet,
         cx: &mut ModelContext<Self>,
     ) {
@@ -6407,7 +6407,7 @@ impl Project {
 
     fn update_local_worktree_settings(
         &mut self,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         changes: &UpdatedEntriesSet,
         cx: &mut ModelContext<Self>,
     ) {
@@ -6473,7 +6473,7 @@ impl Project {
 
     fn update_prettier_settings(
         &self,
-        worktree: &Handle<Worktree>,
+        worktree: &Model<Worktree>,
         changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
         cx: &mut ModelContext<'_, Project>,
     ) {
@@ -6636,7 +6636,7 @@ impl Project {
     // RPC message handlers
 
     async fn handle_unshare_project(
-        this: Handle<Self>,
+        this: Model<Self>,
         _: TypedEnvelope<proto::UnshareProject>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6652,7 +6652,7 @@ impl Project {
     }
 
     async fn handle_add_collaborator(
-        this: Handle<Self>,
+        this: Model<Self>,
         mut envelope: TypedEnvelope<proto::AddProjectCollaborator>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6676,7 +6676,7 @@ impl Project {
     }
 
     async fn handle_update_project_collaborator(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateProjectCollaborator>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6726,7 +6726,7 @@ impl Project {
     }
 
     async fn handle_remove_collaborator(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::RemoveProjectCollaborator>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6755,7 +6755,7 @@ impl Project {
     }
 
     async fn handle_update_project(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateProject>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6770,7 +6770,7 @@ impl Project {
     }
 
     async fn handle_update_worktree(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateWorktree>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6788,7 +6788,7 @@ impl Project {
     }
 
     async fn handle_update_worktree_settings(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6812,7 +6812,7 @@ impl Project {
     }
 
     async fn handle_create_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CreateProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6837,7 +6837,7 @@ impl Project {
     }
 
     async fn handle_rename_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::RenameProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6865,7 +6865,7 @@ impl Project {
     }
 
     async fn handle_copy_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::CopyProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6893,7 +6893,7 @@ impl Project {
     }
 
     async fn handle_delete_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::DeleteProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6923,7 +6923,7 @@ impl Project {
     }
 
     async fn handle_expand_project_entry(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::ExpandProjectEntry>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6946,7 +6946,7 @@ impl Project {
     }
 
     async fn handle_update_diagnostic_summary(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateDiagnosticSummary>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -6976,7 +6976,7 @@ impl Project {
     }
 
     async fn handle_start_language_server(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::StartLanguageServer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -7001,7 +7001,7 @@ impl Project {
     }
 
     async fn handle_update_language_server(
-        this: Handle<Self>,
+        this: Model<Self>,
         envelope: TypedEnvelope<proto::UpdateLanguageServer>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,

crates/project2/src/terminals.rs 🔗

@@ -1,5 +1,5 @@
 use crate::Project;
-use gpui2::{AnyWindowHandle, Context, Handle, ModelContext, WeakHandle};
+use gpui2::{AnyWindowHandle, Context, Model, ModelContext, WeakModel};
 use settings2::Settings;
 use std::path::{Path, PathBuf};
 use terminal2::{
@@ -11,7 +11,7 @@ use terminal2::{
 use std::os::unix::ffi::OsStrExt;
 
 pub struct Terminals {
-    pub(crate) local_handles: Vec<WeakHandle<terminal2::Terminal>>,
+    pub(crate) local_handles: Vec<WeakModel<terminal2::Terminal>>,
 }
 
 impl Project {
@@ -20,7 +20,7 @@ impl Project {
         working_directory: Option<PathBuf>,
         window: AnyWindowHandle,
         cx: &mut ModelContext<Self>,
-    ) -> anyhow::Result<Handle<Terminal>> {
+    ) -> anyhow::Result<Model<Terminal>> {
         if self.is_remote() {
             return Err(anyhow::anyhow!(
                 "creating terminals as a guest is not supported yet"
@@ -40,7 +40,7 @@ impl Project {
                 |_, _| todo!("color_for_index"),
             )
             .map(|builder| {
-                let terminal_handle = cx.entity(|cx| builder.subscribe(cx));
+                let terminal_handle = cx.build_model(|cx| builder.subscribe(cx));
 
                 self.terminals
                     .local_handles
@@ -108,7 +108,7 @@ impl Project {
     fn activate_python_virtual_environment(
         &mut self,
         activate_script: Option<PathBuf>,
-        terminal_handle: &Handle<Terminal>,
+        terminal_handle: &Model<Terminal>,
         cx: &mut ModelContext<Project>,
     ) {
         if let Some(activate_script) = activate_script {
@@ -121,7 +121,7 @@ impl Project {
         }
     }
 
-    pub fn local_terminal_handles(&self) -> &Vec<WeakHandle<terminal2::Terminal>> {
+    pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal2::Terminal>> {
         &self.terminals.local_handles
     }
 }

crates/project2/src/worktree.rs 🔗

@@ -6,7 +6,7 @@ use anyhow::{anyhow, Context as _, Result};
 use client2::{proto, Client};
 use clock::ReplicaId;
 use collections::{HashMap, HashSet, VecDeque};
-use fs::{
+use fs2::{
     repository::{GitFileStatus, GitRepository, RepoPath},
     Fs,
 };
@@ -22,7 +22,7 @@ use futures::{
 use fuzzy2::CharBag;
 use git::{DOT_GIT, GITIGNORE};
 use gpui2::{
-    AppContext, AsyncAppContext, Context, EventEmitter, Executor, Handle, ModelContext, Task,
+    AppContext, AsyncAppContext, Context, EventEmitter, Executor, Model, ModelContext, Task,
 };
 use language2::{
     proto::{
@@ -292,7 +292,7 @@ impl Worktree {
         fs: Arc<dyn Fs>,
         next_entry_id: Arc<AtomicUsize>,
         cx: &mut AsyncAppContext,
-    ) -> Result<Handle<Self>> {
+    ) -> Result<Model<Self>> {
         // After determining whether the root entry is a file or a directory, populate the
         // snapshot's "root name", which will be used for the purpose of fuzzy matching.
         let abs_path = path.into();
@@ -301,7 +301,7 @@ impl Worktree {
             .await
             .context("failed to stat worktree path")?;
 
-        cx.entity(move |cx: &mut ModelContext<Worktree>| {
+        cx.build_model(move |cx: &mut ModelContext<Worktree>| {
             let root_name = abs_path
                 .file_name()
                 .map_or(String::new(), |f| f.to_string_lossy().to_string());
@@ -406,8 +406,8 @@ impl Worktree {
         worktree: proto::WorktreeMetadata,
         client: Arc<Client>,
         cx: &mut AppContext,
-    ) -> Handle<Self> {
-        cx.entity(|cx: &mut ModelContext<Self>| {
+    ) -> Model<Self> {
+        cx.build_model(|cx: &mut ModelContext<Self>| {
             let snapshot = Snapshot {
                 id: WorktreeId(worktree.id as usize),
                 abs_path: Arc::from(PathBuf::from(worktree.abs_path)),
@@ -593,7 +593,7 @@ impl LocalWorktree {
         id: u64,
         path: &Path,
         cx: &mut ModelContext<Worktree>,
-    ) -> Task<Result<Handle<Buffer>>> {
+    ) -> Task<Result<Model<Buffer>>> {
         let path = Arc::from(path);
         cx.spawn(move |this, mut cx| async move {
             let (file, contents, diff_base) = this
@@ -603,7 +603,7 @@ impl LocalWorktree {
                 .executor()
                 .spawn(async move { text::Buffer::new(0, id, contents) })
                 .await;
-            cx.entity(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
+            cx.build_model(|_| Buffer::build(text_buffer, diff_base, Some(Arc::new(file))))
         })
     }
 
@@ -920,7 +920,7 @@ impl LocalWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         path: Arc<Path>,
         has_changed_file: bool,
         cx: &mut ModelContext<Worktree>,
@@ -1331,7 +1331,7 @@ impl RemoteWorktree {
 
     pub fn save_buffer(
         &self,
-        buffer_handle: Handle<Buffer>,
+        buffer_handle: Model<Buffer>,
         cx: &mut ModelContext<Worktree>,
     ) -> Task<Result<()>> {
         let buffer = buffer_handle.read(cx);
@@ -2577,7 +2577,7 @@ impl fmt::Debug for Snapshot {
 
 #[derive(Clone, PartialEq)]
 pub struct File {
-    pub worktree: Handle<Worktree>,
+    pub worktree: Model<Worktree>,
     pub path: Arc<Path>,
     pub mtime: SystemTime,
     pub(crate) entry_id: ProjectEntryId,
@@ -2701,7 +2701,7 @@ impl language2::LocalFile for File {
 }
 
 impl File {
-    pub fn for_entry(entry: Entry, worktree: Handle<Worktree>) -> Arc<Self> {
+    pub fn for_entry(entry: Entry, worktree: Model<Worktree>) -> Arc<Self> {
         Arc::new(Self {
             worktree,
             path: entry.path.clone(),
@@ -2714,7 +2714,7 @@ impl File {
 
     pub fn from_proto(
         proto: rpc2::proto::File,
-        worktree: Handle<Worktree>,
+        worktree: Model<Worktree>,
         cx: &AppContext,
     ) -> Result<Self> {
         let worktree_id = worktree
@@ -2815,7 +2815,7 @@ pub type UpdatedGitRepositoriesSet = Arc<[(Arc<Path>, GitRepositoryChange)]>;
 impl Entry {
     fn new(
         path: Arc<Path>,
-        metadata: &fs::Metadata,
+        metadata: &fs2::Metadata,
         next_entry_id: &AtomicUsize,
         root_char_bag: CharBag,
     ) -> Self {

crates/semantic_index/Cargo.toml 🔗

@@ -42,6 +42,7 @@ sha1 = "0.10.5"
 ndarray = { version = "0.15.0" }
 
 [dev-dependencies]
+ai = { path = "../ai", features = ["test-support"] }
 collections = { path = "../collections", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }

crates/semantic_index/src/embedding_queue.rs 🔗

@@ -41,7 +41,6 @@ pub struct EmbeddingQueue {
     pending_batch_token_count: usize,
     finished_files_tx: channel::Sender<FileToEmbed>,
     finished_files_rx: channel::Receiver<FileToEmbed>,
-    api_key: Option<String>,
 }
 
 #[derive(Clone)]
@@ -51,11 +50,7 @@ pub struct FileFragmentToEmbed {
 }
 
 impl EmbeddingQueue {
-    pub fn new(
-        embedding_provider: Arc<dyn EmbeddingProvider>,
-        executor: Arc<Background>,
-        api_key: Option<String>,
-    ) -> Self {
+    pub fn new(embedding_provider: Arc<dyn EmbeddingProvider>, executor: Arc<Background>) -> Self {
         let (finished_files_tx, finished_files_rx) = channel::unbounded();
         Self {
             embedding_provider,
@@ -64,14 +59,9 @@ impl EmbeddingQueue {
             pending_batch_token_count: 0,
             finished_files_tx,
             finished_files_rx,
-            api_key,
         }
     }
 
-    pub fn set_api_key(&mut self, api_key: Option<String>) {
-        self.api_key = api_key
-    }
-
     pub fn push(&mut self, file: FileToEmbed) {
         if file.spans.is_empty() {
             self.finished_files_tx.try_send(file).unwrap();
@@ -118,7 +108,6 @@ impl EmbeddingQueue {
 
         let finished_files_tx = self.finished_files_tx.clone();
         let embedding_provider = self.embedding_provider.clone();
-        let api_key = self.api_key.clone();
 
         self.executor
             .spawn(async move {
@@ -143,7 +132,7 @@ impl EmbeddingQueue {
                     return;
                 };
 
-                match embedding_provider.embed_batch(spans, api_key).await {
+                match embedding_provider.embed_batch(spans).await {
                     Ok(embeddings) => {
                         let mut embeddings = embeddings.into_iter();
                         for fragment in batch {

crates/semantic_index/src/parsing.rs 🔗

@@ -1,4 +1,7 @@
-use ai::embedding::{Embedding, EmbeddingProvider};
+use ai::{
+    embedding::{Embedding, EmbeddingProvider},
+    models::TruncationDirection,
+};
 use anyhow::{anyhow, Result};
 use language::{Grammar, Language};
 use rusqlite::{
@@ -108,7 +111,14 @@ impl CodeContextRetriever {
             .replace("<language>", language_name.as_ref())
             .replace("<item>", &content);
         let digest = SpanDigest::from(document_span.as_str());
-        let (document_span, token_count) = self.embedding_provider.truncate(&document_span);
+        let model = self.embedding_provider.base_model();
+        let document_span = model.truncate(
+            &document_span,
+            model.capacity()?,
+            ai::models::TruncationDirection::End,
+        )?;
+        let token_count = model.count_tokens(&document_span)?;
+
         Ok(vec![Span {
             range: 0..content.len(),
             content: document_span,
@@ -131,7 +141,15 @@ impl CodeContextRetriever {
             )
             .replace("<item>", &content);
         let digest = SpanDigest::from(document_span.as_str());
-        let (document_span, token_count) = self.embedding_provider.truncate(&document_span);
+
+        let model = self.embedding_provider.base_model();
+        let document_span = model.truncate(
+            &document_span,
+            model.capacity()?,
+            ai::models::TruncationDirection::End,
+        )?;
+        let token_count = model.count_tokens(&document_span)?;
+
         Ok(vec![Span {
             range: 0..content.len(),
             content: document_span,
@@ -222,8 +240,13 @@ impl CodeContextRetriever {
                 .replace("<language>", language_name.as_ref())
                 .replace("item", &span.content);
 
-            let (document_content, token_count) =
-                self.embedding_provider.truncate(&document_content);
+            let model = self.embedding_provider.base_model();
+            let document_content = model.truncate(
+                &document_content,
+                model.capacity()?,
+                TruncationDirection::End,
+            )?;
+            let token_count = model.count_tokens(&document_content)?;
 
             span.content = document_content;
             span.token_count = token_count;

crates/semantic_index/src/semantic_index.rs 🔗

@@ -7,7 +7,8 @@ pub mod semantic_index_settings;
 mod semantic_index_tests;
 
 use crate::semantic_index_settings::SemanticIndexSettings;
-use ai::embedding::{Embedding, EmbeddingProvider, OpenAIEmbeddings};
+use ai::embedding::{Embedding, EmbeddingProvider};
+use ai::providers::open_ai::OpenAIEmbeddingProvider;
 use anyhow::{anyhow, Result};
 use collections::{BTreeMap, HashMap, HashSet};
 use db::VectorDatabase;
@@ -88,7 +89,7 @@ pub fn init(
         let semantic_index = SemanticIndex::new(
             fs,
             db_file_path,
-            Arc::new(OpenAIEmbeddings::new(http_client, cx.background())),
+            Arc::new(OpenAIEmbeddingProvider::new(http_client, cx.background())),
             language_registry,
             cx.clone(),
         )
@@ -123,8 +124,6 @@ pub struct SemanticIndex {
     _embedding_task: Task<()>,
     _parsing_files_tasks: Vec<Task<()>>,
     projects: HashMap<WeakModelHandle<Project>, ProjectState>,
-    api_key: Option<String>,
-    embedding_queue: Arc<Mutex<EmbeddingQueue>>,
 }
 
 struct ProjectState {
@@ -278,18 +277,18 @@ impl SemanticIndex {
         }
     }
 
-    pub fn authenticate(&mut self, cx: &AppContext) {
-        if self.api_key.is_none() {
-            self.api_key = self.embedding_provider.retrieve_credentials(cx);
-
-            self.embedding_queue
-                .lock()
-                .set_api_key(self.api_key.clone());
+    pub fn authenticate(&mut self, cx: &AppContext) -> bool {
+        if !self.embedding_provider.has_credentials() {
+            self.embedding_provider.retrieve_credentials(cx);
+        } else {
+            return true;
         }
+
+        self.embedding_provider.has_credentials()
     }
 
     pub fn is_authenticated(&self) -> bool {
-        self.api_key.is_some()
+        self.embedding_provider.has_credentials()
     }
 
     pub fn enabled(cx: &AppContext) -> bool {
@@ -339,7 +338,7 @@ impl SemanticIndex {
         Ok(cx.add_model(|cx| {
             let t0 = Instant::now();
             let embedding_queue =
-                EmbeddingQueue::new(embedding_provider.clone(), cx.background().clone(), None);
+                EmbeddingQueue::new(embedding_provider.clone(), cx.background().clone());
             let _embedding_task = cx.background().spawn({
                 let embedded_files = embedding_queue.finished_files();
                 let db = db.clone();
@@ -404,8 +403,6 @@ impl SemanticIndex {
                 _embedding_task,
                 _parsing_files_tasks,
                 projects: Default::default(),
-                api_key: None,
-                embedding_queue
             }
         }))
     }
@@ -720,13 +717,13 @@ impl SemanticIndex {
 
         let index = self.index_project(project.clone(), cx);
         let embedding_provider = self.embedding_provider.clone();
-        let api_key = self.api_key.clone();
 
         cx.spawn(|this, mut cx| async move {
             index.await?;
             let t0 = Instant::now();
+
             let query = embedding_provider
-                .embed_batch(vec![query], api_key)
+                .embed_batch(vec![query])
                 .await?
                 .pop()
                 .ok_or_else(|| anyhow!("could not embed query"))?;
@@ -944,7 +941,6 @@ impl SemanticIndex {
         let fs = self.fs.clone();
         let db_path = self.db.path().clone();
         let background = cx.background().clone();
-        let api_key = self.api_key.clone();
         cx.background().spawn(async move {
             let db = VectorDatabase::new(fs, db_path.clone(), background).await?;
             let mut results = Vec::<SearchResult>::new();
@@ -959,15 +955,10 @@ impl SemanticIndex {
                     .parse_file_with_template(None, &snapshot.text(), language)
                     .log_err()
                     .unwrap_or_default();
-                if Self::embed_spans(
-                    &mut spans,
-                    embedding_provider.as_ref(),
-                    &db,
-                    api_key.clone(),
-                )
-                .await
-                .log_err()
-                .is_some()
+                if Self::embed_spans(&mut spans, embedding_provider.as_ref(), &db)
+                    .await
+                    .log_err()
+                    .is_some()
                 {
                     for span in spans {
                         let similarity = span.embedding.unwrap().similarity(&query);
@@ -1007,9 +998,8 @@ impl SemanticIndex {
         project: ModelHandle<Project>,
         cx: &mut ModelContext<Self>,
     ) -> Task<Result<()>> {
-        if self.api_key.is_none() {
-            self.authenticate(cx);
-            if self.api_key.is_none() {
+        if !self.is_authenticated() {
+            if !self.authenticate(cx) {
                 return Task::ready(Err(anyhow!("user is not authenticated")));
             }
         }
@@ -1192,7 +1182,6 @@ impl SemanticIndex {
         spans: &mut [Span],
         embedding_provider: &dyn EmbeddingProvider,
         db: &VectorDatabase,
-        api_key: Option<String>,
     ) -> Result<()> {
         let mut batch = Vec::new();
         let mut batch_tokens = 0;
@@ -1215,7 +1204,7 @@ impl SemanticIndex {
 
             if batch_tokens + span.token_count > embedding_provider.max_tokens_per_batch() {
                 let batch_embeddings = embedding_provider
-                    .embed_batch(mem::take(&mut batch), api_key.clone())
+                    .embed_batch(mem::take(&mut batch))
                     .await?;
                 embeddings.extend(batch_embeddings);
                 batch_tokens = 0;
@@ -1227,7 +1216,7 @@ impl SemanticIndex {
 
         if !batch.is_empty() {
             let batch_embeddings = embedding_provider
-                .embed_batch(mem::take(&mut batch), api_key)
+                .embed_batch(mem::take(&mut batch))
                 .await?;
 
             embeddings.extend(batch_embeddings);

crates/semantic_index/src/semantic_index_tests.rs 🔗

@@ -4,10 +4,9 @@ use crate::{
     semantic_index_settings::SemanticIndexSettings,
     FileToEmbed, JobHandle, SearchResult, SemanticIndex, EMBEDDING_QUEUE_FLUSH_TIMEOUT,
 };
-use ai::embedding::{DummyEmbeddings, Embedding, EmbeddingProvider};
-use anyhow::Result;
-use async_trait::async_trait;
-use gpui::{executor::Deterministic, AppContext, Task, TestAppContext};
+use ai::test::FakeEmbeddingProvider;
+
+use gpui::{executor::Deterministic, Task, TestAppContext};
 use language::{Language, LanguageConfig, LanguageRegistry, ToOffset};
 use parking_lot::Mutex;
 use pretty_assertions::assert_eq;
@@ -15,14 +14,7 @@ use project::{project_settings::ProjectSettings, search::PathMatcher, FakeFs, Fs
 use rand::{rngs::StdRng, Rng};
 use serde_json::json;
 use settings::SettingsStore;
-use std::{
-    path::Path,
-    sync::{
-        atomic::{self, AtomicUsize},
-        Arc,
-    },
-    time::{Instant, SystemTime},
-};
+use std::{path::Path, sync::Arc, time::SystemTime};
 use unindent::Unindent;
 use util::RandomCharIter;
 
@@ -228,7 +220,7 @@ async fn test_embedding_batching(cx: &mut TestAppContext, mut rng: StdRng) {
 
     let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
 
-    let mut queue = EmbeddingQueue::new(embedding_provider.clone(), cx.background(), None);
+    let mut queue = EmbeddingQueue::new(embedding_provider.clone(), cx.background());
     for file in &files {
         queue.push(file.clone());
     }
@@ -280,7 +272,7 @@ fn assert_search_results(
 #[gpui::test]
 async fn test_code_context_retrieval_rust() {
     let language = rust_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = "
@@ -382,7 +374,7 @@ async fn test_code_context_retrieval_rust() {
 #[gpui::test]
 async fn test_code_context_retrieval_json() {
     let language = json_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = r#"
@@ -466,7 +458,7 @@ fn assert_documents_eq(
 #[gpui::test]
 async fn test_code_context_retrieval_javascript() {
     let language = js_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = "
@@ -565,7 +557,7 @@ async fn test_code_context_retrieval_javascript() {
 #[gpui::test]
 async fn test_code_context_retrieval_lua() {
     let language = lua_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = r#"
@@ -639,7 +631,7 @@ async fn test_code_context_retrieval_lua() {
 #[gpui::test]
 async fn test_code_context_retrieval_elixir() {
     let language = elixir_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = r#"
@@ -756,7 +748,7 @@ async fn test_code_context_retrieval_elixir() {
 #[gpui::test]
 async fn test_code_context_retrieval_cpp() {
     let language = cpp_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = "
@@ -909,7 +901,7 @@ async fn test_code_context_retrieval_cpp() {
 #[gpui::test]
 async fn test_code_context_retrieval_ruby() {
     let language = ruby_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = r#"
@@ -1100,7 +1092,7 @@ async fn test_code_context_retrieval_ruby() {
 #[gpui::test]
 async fn test_code_context_retrieval_php() {
     let language = php_lang();
-    let embedding_provider = Arc::new(DummyEmbeddings {});
+    let embedding_provider = Arc::new(FakeEmbeddingProvider::default());
     let mut retriever = CodeContextRetriever::new(embedding_provider);
 
     let text = r#"
@@ -1248,65 +1240,6 @@ async fn test_code_context_retrieval_php() {
     );
 }
 
-#[derive(Default)]
-struct FakeEmbeddingProvider {
-    embedding_count: AtomicUsize,
-}
-
-impl FakeEmbeddingProvider {
-    fn embedding_count(&self) -> usize {
-        self.embedding_count.load(atomic::Ordering::SeqCst)
-    }
-
-    fn embed_sync(&self, span: &str) -> Embedding {
-        let mut result = vec![1.0; 26];
-        for letter in span.chars() {
-            let letter = letter.to_ascii_lowercase();
-            if letter as u32 >= 'a' as u32 {
-                let ix = (letter as u32) - ('a' as u32);
-                if ix < 26 {
-                    result[ix as usize] += 1.0;
-                }
-            }
-        }
-
-        let norm = result.iter().map(|x| x * x).sum::<f32>().sqrt();
-        for x in &mut result {
-            *x /= norm;
-        }
-
-        result.into()
-    }
-}
-
-#[async_trait]
-impl EmbeddingProvider for FakeEmbeddingProvider {
-    fn retrieve_credentials(&self, _cx: &AppContext) -> Option<String> {
-        Some("Fake Credentials".to_string())
-    }
-    fn truncate(&self, span: &str) -> (String, usize) {
-        (span.to_string(), 1)
-    }
-
-    fn max_tokens_per_batch(&self) -> usize {
-        200
-    }
-
-    fn rate_limit_expiration(&self) -> Option<Instant> {
-        None
-    }
-
-    async fn embed_batch(
-        &self,
-        spans: Vec<String>,
-        _api_key: Option<String>,
-    ) -> Result<Vec<Embedding>> {
-        self.embedding_count
-            .fetch_add(spans.len(), atomic::Ordering::SeqCst);
-        Ok(spans.iter().map(|span| self.embed_sync(span)).collect())
-    }
-}
-
 fn js_lang() -> Arc<Language> {
     Arc::new(
         Language::new(

crates/storybook2/src/stories.rs 🔗

@@ -1,9 +1,11 @@
+mod colors;
 mod focus;
 mod kitchen_sink;
 mod scroll;
 mod text;
 mod z_index;
 
+pub use colors::*;
 pub use focus::*;
 pub use kitchen_sink::*;
 pub use scroll::*;

crates/storybook2/src/stories/colors.rs 🔗

@@ -0,0 +1,38 @@
+use crate::story::Story;
+use gpui2::{px, Div, Render};
+use ui::prelude::*;
+
+pub struct ColorsStory;
+
+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();
+
+        Story::container(cx)
+            .child(Story::title(cx, "Colors"))
+            .child(
+                div()
+                    .id("colors")
+                    .flex()
+                    .flex_col()
+                    .gap_1()
+                    .overflow_y_scroll()
+                    .text_color(gpui2::white())
+                    .children(color_scales.into_iter().map(|(name, scale)| {
+                        div()
+                            .flex()
+                            .child(
+                                div()
+                                    .w(px(75.))
+                                    .line_height(px(24.))
+                                    .child(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 🔗

@@ -1,9 +1,9 @@
-use crate::themes::rose_pine;
 use gpui2::{
-    div, Focusable, KeyBinding, ParentElement, StatelessInteractive, Styled, View, VisualContext,
-    WindowContext,
+    div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteraction,
+    StatelessInteractive, Styled, View, VisualContext, WindowContext,
 };
 use serde::Deserialize;
+use theme2::theme;
 
 #[derive(Clone, Default, PartialEq, Deserialize)]
 struct ActionA;
@@ -14,12 +14,10 @@ struct ActionB;
 #[derive(Clone, Default, PartialEq, Deserialize)]
 struct ActionC;
 
-pub struct FocusStory {
-    text: View<()>,
-}
+pub struct FocusStory {}
 
 impl FocusStory {
-    pub fn view(cx: &mut WindowContext) -> View<()> {
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
         cx.bind_keys([
             KeyBinding::new("cmd-a", ActionA, Some("parent")),
             KeyBinding::new("cmd-a", ActionB, Some("child-1")),
@@ -27,91 +25,92 @@ impl FocusStory {
         ]);
         cx.register_action_type::<ActionA>();
         cx.register_action_type::<ActionB>();
-        let theme = rose_pine();
 
-        let color_1 = theme.lowest.negative.default.foreground;
-        let color_2 = theme.lowest.positive.default.foreground;
-        let color_3 = theme.lowest.warning.default.foreground;
-        let color_4 = theme.lowest.accent.default.foreground;
-        let color_5 = theme.lowest.variant.default.foreground;
-        let color_6 = theme.highest.negative.default.foreground;
+        cx.build_view(move |cx| Self {})
+    }
+}
+
+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 child_1 = cx.focus_handle();
         let child_2 = cx.focus_handle();
 
-        cx.build_view(
-            |_| (),
-            move |_, cx| {
+        div()
+            .id("parent")
+            .focusable()
+            .context("parent")
+            .on_action(|_, action: &ActionA, phase, cx| {
+                println!("Action A dispatched on parent during {:?}", phase);
+            })
+            .on_action(|_, action: &ActionB, phase, cx| {
+                println!("Action B dispatched on parent during {:?}", phase);
+            })
+            .on_focus(|_, _, _| println!("Parent focused"))
+            .on_blur(|_, _, _| println!("Parent blurred"))
+            .on_focus_in(|_, _, _| println!("Parent focus_in"))
+            .on_focus_out(|_, _, _| println!("Parent focus_out"))
+            .on_key_down(|_, event, phase, _| {
+                println!("Key down on parent {:?} {:?}", phase, event)
+            })
+            .on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event))
+            .size_full()
+            .bg(color_1)
+            .focus(|style| style.bg(color_2))
+            .focus_in(|style| style.bg(color_3))
+            .child(
                 div()
-                    .id("parent")
-                    .focusable()
-                    .context("parent")
-                    .on_action(|_, action: &ActionA, phase, cx| {
-                        println!("Action A dispatched on parent during {:?}", phase);
-                    })
+                    .track_focus(&child_1)
+                    .context("child-1")
                     .on_action(|_, action: &ActionB, phase, cx| {
-                        println!("Action B dispatched on parent during {:?}", phase);
+                        println!("Action B dispatched on child 1 during {:?}", phase);
+                    })
+                    .w_full()
+                    .h_6()
+                    .bg(color_4)
+                    .focus(|style| style.bg(color_5))
+                    .in_focus(|style| style.bg(color_6))
+                    .on_focus(|_, _, _| println!("Child 1 focused"))
+                    .on_blur(|_, _, _| println!("Child 1 blurred"))
+                    .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
+                    .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
+                    .on_key_down(|_, event, phase, _| {
+                        println!("Key down on child 1 {:?} {:?}", phase, event)
+                    })
+                    .on_key_up(|_, event, phase, _| {
+                        println!("Key up on child 1 {:?} {:?}", phase, event)
+                    })
+                    .child("Child 1"),
+            )
+            .child(
+                div()
+                    .track_focus(&child_2)
+                    .context("child-2")
+                    .on_action(|_, action: &ActionC, phase, cx| {
+                        println!("Action C dispatched on child 2 during {:?}", phase);
                     })
-                    .on_focus(|_, _, _| println!("Parent focused"))
-                    .on_blur(|_, _, _| println!("Parent blurred"))
-                    .on_focus_in(|_, _, _| println!("Parent focus_in"))
-                    .on_focus_out(|_, _, _| println!("Parent focus_out"))
+                    .w_full()
+                    .h_6()
+                    .bg(color_4)
+                    .on_focus(|_, _, _| println!("Child 2 focused"))
+                    .on_blur(|_, _, _| println!("Child 2 blurred"))
+                    .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
+                    .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
                     .on_key_down(|_, event, phase, _| {
-                        println!("Key down on parent {:?} {:?}", phase, event)
+                        println!("Key down on child 2 {:?} {:?}", phase, event)
                     })
                     .on_key_up(|_, event, phase, _| {
-                        println!("Key up on parent {:?} {:?}", phase, event)
+                        println!("Key up on child 2 {:?} {:?}", phase, event)
                     })
-                    .size_full()
-                    .bg(color_1)
-                    .focus(|style| style.bg(color_2))
-                    .focus_in(|style| style.bg(color_3))
-                    .child(
-                        div()
-                            .track_focus(&child_1)
-                            .context("child-1")
-                            .on_action(|_, action: &ActionB, phase, cx| {
-                                println!("Action B dispatched on child 1 during {:?}", phase);
-                            })
-                            .w_full()
-                            .h_6()
-                            .bg(color_4)
-                            .focus(|style| style.bg(color_5))
-                            .in_focus(|style| style.bg(color_6))
-                            .on_focus(|_, _, _| println!("Child 1 focused"))
-                            .on_blur(|_, _, _| println!("Child 1 blurred"))
-                            .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
-                            .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
-                            .on_key_down(|_, event, phase, _| {
-                                println!("Key down on child 1 {:?} {:?}", phase, event)
-                            })
-                            .on_key_up(|_, event, phase, _| {
-                                println!("Key up on child 1 {:?} {:?}", phase, event)
-                            })
-                            .child("Child 1"),
-                    )
-                    .child(
-                        div()
-                            .track_focus(&child_2)
-                            .context("child-2")
-                            .on_action(|_, action: &ActionC, phase, cx| {
-                                println!("Action C dispatched on child 2 during {:?}", phase);
-                            })
-                            .w_full()
-                            .h_6()
-                            .bg(color_4)
-                            .on_focus(|_, _, _| println!("Child 2 focused"))
-                            .on_blur(|_, _, _| println!("Child 2 blurred"))
-                            .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
-                            .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
-                            .on_key_down(|_, event, phase, _| {
-                                println!("Key down on child 2 {:?} {:?}", phase, event)
-                            })
-                            .on_key_up(|_, event, phase, _| {
-                                println!("Key up on child 2 {:?} {:?}", phase, event)
-                            })
-                            .child("Child 2"),
-                    )
-            },
-        )
+                    .child("Child 2"),
+            )
     }
 }

crates/storybook2/src/stories/kitchen_sink.rs 🔗

@@ -1,26 +1,23 @@
-use gpui2::{AppContext, Context, View};
+use crate::{
+    story::Story,
+    story_selector::{ComponentStory, ElementStory},
+};
+use gpui2::{Div, Render, StatefulInteraction, View, VisualContext};
 use strum::IntoEnumIterator;
 use ui::prelude::*;
 
-use crate::story::Story;
-use crate::story_selector::{ComponentStory, ElementStory};
-
-pub struct KitchenSinkStory {}
+pub struct KitchenSinkStory;
 
 impl KitchenSinkStory {
-    pub fn new() -> Self {
-        Self {}
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self)
     }
+}
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.entity(|cx| Self::new());
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
-    }
+impl Render for KitchenSinkStory {
+    type Element = Div<Self, StatefulInteraction<Self>>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let element_stories = ElementStory::iter()
             .map(|selector| selector.story(cx))
             .collect::<Vec<_>>();

crates/storybook2/src/stories/scroll.rs 🔗

@@ -1,58 +1,54 @@
-use crate::themes::rose_pine;
 use gpui2::{
-    div, px, Component, ParentElement, SharedString, Styled, View, VisualContext, WindowContext,
+    div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled,
+    View, VisualContext, WindowContext,
 };
+use theme2::theme;
 
-pub struct ScrollStory {
-    text: View<()>,
-}
+pub struct ScrollStory;
 
 impl ScrollStory {
-    pub fn view(cx: &mut WindowContext) -> View<()> {
-        let theme = rose_pine();
-
-        {
-            cx.build_view(|cx| (), move |_, cx| checkerboard(1))
-        }
+    pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
+        cx.build_view(|cx| ScrollStory)
     }
 }
 
-fn checkerboard<S>(depth: usize) -> impl Component<S>
-where
-    S: 'static + Send + Sync,
-{
-    let theme = rose_pine();
-    let color_1 = theme.lowest.positive.default.background;
-    let color_2 = theme.lowest.warning.default.background;
+impl Render for ScrollStory {
+    type Element = Div<Self, StatefulInteraction<Self>>;
 
-    div()
-        .id("parent")
-        .bg(theme.lowest.base.default.background)
-        .size_full()
-        .overflow_scroll()
-        .children((0..10).map(|row| {
-            div()
-                .w(px(1000.))
-                .h(px(100.))
-                .flex()
-                .flex_row()
-                .children((0..10).map(|column| {
-                    let id = SharedString::from(format!("{}, {}", row, column));
-                    let bg = if row % 2 == column % 2 {
-                        color_1
-                    } else {
-                        color_2
-                    };
-                    div().id(id).bg(bg).size(px(100. / depth as f32)).when(
-                        row >= 5 && column >= 5,
-                        |d| {
-                            d.overflow_scroll()
-                                .child(div().size(px(50.)).bg(color_1))
-                                .child(div().size(px(50.)).bg(color_2))
-                                .child(div().size(px(50.)).bg(color_1))
-                                .child(div().size(px(50.)).bg(color_2))
-                        },
-                    )
-                }))
-        }))
+    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;
+
+        div()
+            .id("parent")
+            .bg(theme.background)
+            .size_full()
+            .overflow_scroll()
+            .children((0..10).map(|row| {
+                div()
+                    .w(px(1000.))
+                    .h(px(100.))
+                    .flex()
+                    .flex_row()
+                    .children((0..10).map(|column| {
+                        let id = SharedString::from(format!("{}, {}", row, column));
+                        let bg = if row % 2 == column % 2 {
+                            color_1
+                        } else {
+                            color_2
+                        };
+                        div().id(id).bg(bg).size(px(100. as f32)).when(
+                            row >= 5 && column >= 5,
+                            |d| {
+                                d.overflow_scroll()
+                                    .child(div().size(px(50.)).bg(color_1))
+                                    .child(div().size(px(50.)).bg(color_2))
+                                    .child(div().size(px(50.)).bg(color_1))
+                                    .child(div().size(px(50.)).bg(color_2))
+                            },
+                        )
+                    }))
+            }))
+    }
 }

crates/storybook2/src/stories/text.rs 🔗

@@ -1,20 +1,21 @@
-use gpui2::{div, white, ParentElement, Styled, View, VisualContext, WindowContext};
+use gpui2::{div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext};
 
-pub struct TextStory {
-    text: View<()>,
-}
+pub struct TextStory;
 
 impl TextStory {
-    pub fn view(cx: &mut WindowContext) -> View<()> {
-        cx.build_view(|cx| (), |_, cx| {
-            div()
-                .size_full()
-                .bg(white())
-                .child(concat!(
-                    "The quick brown fox jumps over the lazy dog. ",
-                    "Meanwhile, the lazy dog decided it was time for a change. ",
-                    "He started daily workout routines, ate healthier and became the fastest dog in town.",
-                ))
-        })
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self)
+    }
+}
+
+impl Render for TextStory {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
+        div().size_full().bg(white()).child(concat!(
+            "The quick brown fox jumps over the lazy dog. ",
+            "Meanwhile, the lazy dog decided it was time for a change. ",
+            "He started daily workout routines, ate healthier and became the fastest dog in town.",
+        ))
     }
 }

crates/storybook2/src/stories/z_index.rs 🔗

@@ -1,15 +1,16 @@
-use gpui2::{px, rgb, Div, Hsla};
+use gpui2::{px, rgb, Div, Hsla, Render};
 use ui::prelude::*;
 
 use crate::story::Story;
 
 /// A reimplementation of the MDN `z-index` example, found here:
 /// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
-#[derive(Component)]
 pub struct ZIndexStory;
 
-impl ZIndexStory {
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+impl Render for ZIndexStory {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         Story::container(cx)
             .child(Story::title(cx, "z-index"))
             .child(

crates/storybook2/src/story_selector.rs 🔗

@@ -7,13 +7,14 @@ use clap::builder::PossibleValue;
 use clap::ValueEnum;
 use gpui2::{AnyView, VisualContext};
 use strum::{EnumIter, EnumString, IntoEnumIterator};
-use ui::prelude::*;
+use ui::{prelude::*, AvatarStory, ButtonStory, DetailsStory, IconStory, InputStory, LabelStory};
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 #[strum(serialize_all = "snake_case")]
 pub enum ElementStory {
     Avatar,
     Button,
+    Colors,
     Details,
     Focus,
     Icon,
@@ -27,18 +28,17 @@ pub enum ElementStory {
 impl ElementStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
-            Self::Avatar => { cx.build_view(|cx| (), |_, _| ui::AvatarStory.render()) }.into_any(),
-            Self::Button => { cx.build_view(|cx| (), |_, _| ui::ButtonStory.render()) }.into_any(),
-            Self::Details => {
-                { cx.build_view(|cx| (), |_, _| ui::DetailsStory.render()) }.into_any()
-            }
+            Self::Colors => cx.build_view(|_| ColorsStory).into_any(),
+            Self::Avatar => cx.build_view(|_| AvatarStory).into_any(),
+            Self::Button => cx.build_view(|_| ButtonStory).into_any(),
+            Self::Details => cx.build_view(|_| DetailsStory).into_any(),
             Self::Focus => FocusStory::view(cx).into_any(),
-            Self::Icon => { cx.build_view(|cx| (), |_, _| ui::IconStory.render()) }.into_any(),
-            Self::Input => { cx.build_view(|cx| (), |_, _| ui::InputStory.render()) }.into_any(),
-            Self::Label => { cx.build_view(|cx| (), |_, _| ui::LabelStory.render()) }.into_any(),
+            Self::Icon => cx.build_view(|_| IconStory).into_any(),
+            Self::Input => cx.build_view(|_| InputStory).into_any(),
+            Self::Label => cx.build_view(|_| LabelStory).into_any(),
             Self::Scroll => ScrollStory::view(cx).into_any(),
             Self::Text => TextStory::view(cx).into_any(),
-            Self::ZIndex => { cx.build_view(|cx| (), |_, _| ZIndexStory.render()) }.into_any(),
+            Self::ZIndex => cx.build_view(|_| ZIndexStory).into_any(),
         }
     }
 }
@@ -77,69 +77,31 @@ pub enum ComponentStory {
 impl ComponentStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
-            Self::AssistantPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::AssistantPanelStory.render()) }.into_any()
-            }
-            Self::Buffer => { cx.build_view(|cx| (), |_, _| ui::BufferStory.render()) }.into_any(),
-            Self::Breadcrumb => {
-                { cx.build_view(|cx| (), |_, _| ui::BreadcrumbStory.render()) }.into_any()
-            }
-            Self::ChatPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::ChatPanelStory.render()) }.into_any()
-            }
-            Self::CollabPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::CollabPanelStory.render()) }.into_any()
-            }
-            Self::CommandPalette => {
-                { cx.build_view(|cx| (), |_, _| ui::CommandPaletteStory.render()) }.into_any()
-            }
-            Self::ContextMenu => {
-                { cx.build_view(|cx| (), |_, _| ui::ContextMenuStory.render()) }.into_any()
-            }
-            Self::Facepile => {
-                { cx.build_view(|cx| (), |_, _| ui::FacepileStory.render()) }.into_any()
-            }
-            Self::Keybinding => {
-                { cx.build_view(|cx| (), |_, _| ui::KeybindingStory.render()) }.into_any()
-            }
-            Self::LanguageSelector => {
-                { cx.build_view(|cx| (), |_, _| ui::LanguageSelectorStory.render()) }.into_any()
-            }
-            Self::MultiBuffer => {
-                { cx.build_view(|cx| (), |_, _| ui::MultiBufferStory.render()) }.into_any()
-            }
-            Self::NotificationsPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::NotificationsPanelStory.render()) }.into_any()
-            }
-            Self::Palette => {
-                { cx.build_view(|cx| (), |_, _| ui::PaletteStory.render()) }.into_any()
-            }
-            Self::Panel => { cx.build_view(|cx| (), |_, _| ui::PanelStory.render()) }.into_any(),
-            Self::ProjectPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::ProjectPanelStory.render()) }.into_any()
-            }
-            Self::RecentProjects => {
-                { cx.build_view(|cx| (), |_, _| ui::RecentProjectsStory.render()) }.into_any()
-            }
-            Self::Tab => { cx.build_view(|cx| (), |_, _| ui::TabStory.render()) }.into_any(),
-            Self::TabBar => { cx.build_view(|cx| (), |_, _| ui::TabBarStory.render()) }.into_any(),
-            Self::Terminal => {
-                { cx.build_view(|cx| (), |_, _| ui::TerminalStory.render()) }.into_any()
-            }
-            Self::ThemeSelector => {
-                { cx.build_view(|cx| (), |_, _| ui::ThemeSelectorStory.render()) }.into_any()
-            }
+            Self::AssistantPanel => cx.build_view(|_| ui::AssistantPanelStory).into_any(),
+            Self::Buffer => cx.build_view(|_| ui::BufferStory).into_any(),
+            Self::Breadcrumb => cx.build_view(|_| ui::BreadcrumbStory).into_any(),
+            Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into_any(),
+            Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into_any(),
+            Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into_any(),
+            Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into_any(),
+            Self::Facepile => cx.build_view(|_| ui::FacepileStory).into_any(),
+            Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into_any(),
+            Self::LanguageSelector => cx.build_view(|_| ui::LanguageSelectorStory).into_any(),
+            Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into_any(),
+            Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into_any(),
+            Self::Palette => cx.build_view(|cx| ui::PaletteStory).into_any(),
+            Self::Panel => cx.build_view(|cx| ui::PanelStory).into_any(),
+            Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into_any(),
+            Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into_any(),
+            Self::Tab => cx.build_view(|_| ui::TabStory).into_any(),
+            Self::TabBar => cx.build_view(|_| ui::TabBarStory).into_any(),
+            Self::Terminal => cx.build_view(|_| ui::TerminalStory).into_any(),
+            Self::ThemeSelector => cx.build_view(|_| ui::ThemeSelectorStory).into_any(),
+            Self::Toast => cx.build_view(|_| ui::ToastStory).into_any(),
+            Self::Toolbar => cx.build_view(|_| ui::ToolbarStory).into_any(),
+            Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into_any(),
+            Self::Copilot => cx.build_view(|_| ui::CopilotModalStory).into_any(),
             Self::TitleBar => ui::TitleBarStory::view(cx).into_any(),
-            Self::Toast => { cx.build_view(|cx| (), |_, _| ui::ToastStory.render()) }.into_any(),
-            Self::Toolbar => {
-                { cx.build_view(|cx| (), |_, _| ui::ToolbarStory.render()) }.into_any()
-            }
-            Self::TrafficLights => {
-                { cx.build_view(|cx| (), |_, _| ui::TrafficLightsStory.render()) }.into_any()
-            }
-            Self::Copilot => {
-                { cx.build_view(|cx| (), |_, _| ui::CopilotModalStory.render()) }.into_any()
-            }
             Self::Workspace => ui::WorkspaceStory::view(cx).into_any(),
         }
     }

crates/storybook2/src/storybook2.rs 🔗

@@ -4,21 +4,20 @@ mod assets;
 mod stories;
 mod story;
 mod story_selector;
-mod themes;
 
 use std::sync::Arc;
 
 use clap::Parser;
 use gpui2::{
-    div, px, size, AnyView, AppContext, Bounds, ViewContext, VisualContext, WindowBounds,
-    WindowOptions,
+    div, px, size, AnyView, AppContext, Bounds, Div, Render, ViewContext, VisualContext,
+    WindowBounds, WindowOptions,
 };
 use log::LevelFilter;
 use settings2::{default_settings, Settings, SettingsStore};
 use simplelog::SimpleLogger;
 use story_selector::ComponentStory;
 use theme2::{ThemeRegistry, ThemeSettings};
-use ui::{prelude::*, themed};
+use ui::prelude::*;
 
 use crate::assets::Assets;
 use crate::story_selector::StorySelector;
@@ -50,7 +49,6 @@ fn main() {
 
     let story_selector = args.story.clone();
     let theme_name = args.theme.unwrap_or("One Dark".to_string());
-    let theme = themes::load_theme(theme_name.clone()).unwrap();
 
     let asset_source = Arc::new(Assets);
     gpui2::App::production(asset_source).run(move |cx| {
@@ -84,12 +82,7 @@ fn main() {
                 }),
                 ..Default::default()
             },
-            move |cx| {
-                cx.build_view(
-                    |cx| StoryWrapper::new(selector.story(cx), theme),
-                    StoryWrapper::render,
-                )
-            },
+            move |cx| cx.build_view(|cx| StoryWrapper::new(selector.story(cx))),
         );
 
         cx.activate(true);
@@ -99,22 +92,23 @@ fn main() {
 #[derive(Clone)]
 pub struct StoryWrapper {
     story: AnyView,
-    theme: Theme,
 }
 
 impl StoryWrapper {
-    pub(crate) fn new(story: AnyView, theme: Theme) -> Self {
-        Self { story, theme }
+    pub(crate) fn new(story: AnyView) -> Self {
+        Self { story }
     }
+}
+
+impl Render for StoryWrapper {
+    type Element = Div<Self>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
-        themed(self.theme.clone(), cx, |cx| {
-            div()
-                .flex()
-                .flex_col()
-                .size_full()
-                .child(self.story.clone())
-        })
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        div()
+            .flex()
+            .flex_col()
+            .size_full()
+            .child(self.story.clone())
     }
 }
 

crates/storybook2/src/themes.rs 🔗

@@ -1,30 +0,0 @@
-mod rose_pine;
-
-pub use rose_pine::*;
-
-use anyhow::{Context, Result};
-use gpui2::serde_json;
-use serde::Deserialize;
-use ui::Theme;
-
-use crate::assets::Assets;
-
-#[derive(Deserialize)]
-struct LegacyTheme {
-    pub base_theme: serde_json::Value,
-}
-
-/// Loads the [`Theme`] with the given name.
-pub fn load_theme(name: String) -> Result<Theme> {
-    let theme_contents = Assets::get(&format!("themes/{name}.json"))
-        .with_context(|| format!("theme file not found: '{name}'"))?;
-
-    let legacy_theme: LegacyTheme =
-        serde_json::from_str(std::str::from_utf8(&theme_contents.data)?)
-            .context("failed to parse legacy theme")?;
-
-    let theme: Theme = serde_json::from_value(legacy_theme.base_theme.clone())
-        .context("failed to parse `base_theme`")?;
-
-    Ok(theme)
-}

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

@@ -1,1686 +0,0 @@
-use gpui2::serde_json::{self, json};
-use ui::Theme;
-
-pub fn rose_pine() -> Theme {
-    serde_json::from_value(json! {
-        {
-          "name": "Rosé Pine",
-          "is_light": false,
-          "ramps": {},
-          "lowest": {
-            "base": {
-              "default": {
-                "background": "#292739",
-                "border": "#423f55",
-                "foreground": "#e0def4"
-              },
-              "hovered": {
-                "background": "#423f55",
-                "border": "#423f55",
-                "foreground": "#e0def4"
-              },
-              "pressed": {
-                "background": "#4e4b63",
-                "border": "#423f55",
-                "foreground": "#e0def4"
-              },
-              "active": {
-                "background": "#47445b",
-                "border": "#36334a",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#292739",
-                "border": "#353347",
-                "foreground": "#2f2b43"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#4b4860"
-              }
-            },
-            "variant": {
-              "default": {
-                "background": "#292739",
-                "border": "#423f55",
-                "foreground": "#75718e"
-              },
-              "hovered": {
-                "background": "#423f55",
-                "border": "#423f55",
-                "foreground": "#75718e"
-              },
-              "pressed": {
-                "background": "#4e4b63",
-                "border": "#423f55",
-                "foreground": "#75718e"
-              },
-              "active": {
-                "background": "#47445b",
-                "border": "#36334a",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#292739",
-                "border": "#353347",
-                "foreground": "#2f2b43"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#4b4860"
-              }
-            },
-            "on": {
-              "default": {
-                "background": "#1d1b2a",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "hovered": {
-                "background": "#232132",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "pressed": {
-                "background": "#2f2d40",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "active": {
-                "background": "#403e53",
-                "border": "#504d65",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#1d1b2a",
-                "border": "#1e1c2c",
-                "foreground": "#3b384f"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#3b394e"
-              }
-            },
-            "accent": {
-              "default": {
-                "background": "#2f3739",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "hovered": {
-                "background": "#435255",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "pressed": {
-                "background": "#4e6164",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "active": {
-                "background": "#5d757a",
-                "border": "#6e8f94",
-                "foreground": "#fbfdfd"
-              },
-              "disabled": {
-                "background": "#2f3739",
-                "border": "#3a4446",
-                "foreground": "#85aeb5"
-              },
-              "inverted": {
-                "background": "#fbfdfd",
-                "border": "#171717",
-                "foreground": "#587074"
-              }
-            },
-            "positive": {
-              "default": {
-                "background": "#182e23",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "hovered": {
-                "background": "#254839",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "pressed": {
-                "background": "#2c5645",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "active": {
-                "background": "#356b57",
-                "border": "#40836c",
-                "foreground": "#f9fdfb"
-              },
-              "disabled": {
-                "background": "#182e23",
-                "border": "#1e3b2e",
-                "foreground": "#4ea287"
-              },
-              "inverted": {
-                "background": "#f9fdfb",
-                "border": "#000e00",
-                "foreground": "#326552"
-              }
-            },
-            "warning": {
-              "default": {
-                "background": "#50341a",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "hovered": {
-                "background": "#6d4d2b",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "pressed": {
-                "background": "#7e5a34",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "active": {
-                "background": "#946e41",
-                "border": "#b0854f",
-                "foreground": "#fffcf9"
-              },
-              "disabled": {
-                "background": "#50341a",
-                "border": "#5e4023",
-                "foreground": "#d2a263"
-              },
-              "inverted": {
-                "background": "#fffcf9",
-                "border": "#2c1600",
-                "foreground": "#8e683c"
-              }
-            },
-            "negative": {
-              "default": {
-                "background": "#431820",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "hovered": {
-                "background": "#612834",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "pressed": {
-                "background": "#71303f",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "active": {
-                "background": "#883c4f",
-                "border": "#a44961",
-                "foreground": "#fff9fa"
-              },
-              "disabled": {
-                "background": "#431820",
-                "border": "#52202a",
-                "foreground": "#c75c79"
-              },
-              "inverted": {
-                "background": "#fff9fa",
-                "border": "#230000",
-                "foreground": "#82384a"
-              }
-            }
-          },
-          "middle": {
-            "base": {
-              "default": {
-                "background": "#1d1b2a",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "hovered": {
-                "background": "#232132",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "pressed": {
-                "background": "#2f2d40",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "active": {
-                "background": "#403e53",
-                "border": "#504d65",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#1d1b2a",
-                "border": "#1e1c2c",
-                "foreground": "#3b384f"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#3b394e"
-              }
-            },
-            "variant": {
-              "default": {
-                "background": "#1d1b2a",
-                "border": "#232132",
-                "foreground": "#75718e"
-              },
-              "hovered": {
-                "background": "#232132",
-                "border": "#232132",
-                "foreground": "#75718e"
-              },
-              "pressed": {
-                "background": "#2f2d40",
-                "border": "#232132",
-                "foreground": "#75718e"
-              },
-              "active": {
-                "background": "#403e53",
-                "border": "#504d65",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#1d1b2a",
-                "border": "#1e1c2c",
-                "foreground": "#3b384f"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#3b394e"
-              }
-            },
-            "on": {
-              "default": {
-                "background": "#191724",
-                "border": "#1c1a29",
-                "foreground": "#e0def4"
-              },
-              "hovered": {
-                "background": "#1c1a29",
-                "border": "#1c1a29",
-                "foreground": "#e0def4"
-              },
-              "pressed": {
-                "background": "#1d1b2b",
-                "border": "#1c1a29",
-                "foreground": "#e0def4"
-              },
-              "active": {
-                "background": "#222031",
-                "border": "#353347",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#191724",
-                "border": "#1a1826",
-                "foreground": "#4e4b63"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#1f1d2e"
-              }
-            },
-            "accent": {
-              "default": {
-                "background": "#2f3739",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "hovered": {
-                "background": "#435255",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "pressed": {
-                "background": "#4e6164",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "active": {
-                "background": "#5d757a",
-                "border": "#6e8f94",
-                "foreground": "#fbfdfd"
-              },
-              "disabled": {
-                "background": "#2f3739",
-                "border": "#3a4446",
-                "foreground": "#85aeb5"
-              },
-              "inverted": {
-                "background": "#fbfdfd",
-                "border": "#171717",
-                "foreground": "#587074"
-              }
-            },
-            "positive": {
-              "default": {
-                "background": "#182e23",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "hovered": {
-                "background": "#254839",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "pressed": {
-                "background": "#2c5645",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "active": {
-                "background": "#356b57",
-                "border": "#40836c",
-                "foreground": "#f9fdfb"
-              },
-              "disabled": {
-                "background": "#182e23",
-                "border": "#1e3b2e",
-                "foreground": "#4ea287"
-              },
-              "inverted": {
-                "background": "#f9fdfb",
-                "border": "#000e00",
-                "foreground": "#326552"
-              }
-            },
-            "warning": {
-              "default": {
-                "background": "#50341a",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "hovered": {
-                "background": "#6d4d2b",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "pressed": {
-                "background": "#7e5a34",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "active": {
-                "background": "#946e41",
-                "border": "#b0854f",
-                "foreground": "#fffcf9"
-              },
-              "disabled": {
-                "background": "#50341a",
-                "border": "#5e4023",
-                "foreground": "#d2a263"
-              },
-              "inverted": {
-                "background": "#fffcf9",
-                "border": "#2c1600",
-                "foreground": "#8e683c"
-              }
-            },
-            "negative": {
-              "default": {
-                "background": "#431820",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "hovered": {
-                "background": "#612834",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "pressed": {
-                "background": "#71303f",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "active": {
-                "background": "#883c4f",
-                "border": "#a44961",
-                "foreground": "#fff9fa"
-              },
-              "disabled": {
-                "background": "#431820",
-                "border": "#52202a",
-                "foreground": "#c75c79"
-              },
-              "inverted": {
-                "background": "#fff9fa",
-                "border": "#230000",
-                "foreground": "#82384a"
-              }
-            }
-          },
-          "highest": {
-            "base": {
-              "default": {
-                "background": "#191724",
-                "border": "#1c1a29",
-                "foreground": "#e0def4"
-              },
-              "hovered": {
-                "background": "#1c1a29",
-                "border": "#1c1a29",
-                "foreground": "#e0def4"
-              },
-              "pressed": {
-                "background": "#1d1b2b",
-                "border": "#1c1a29",
-                "foreground": "#e0def4"
-              },
-              "active": {
-                "background": "#222031",
-                "border": "#353347",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#191724",
-                "border": "#1a1826",
-                "foreground": "#4e4b63"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#1f1d2e"
-              }
-            },
-            "variant": {
-              "default": {
-                "background": "#191724",
-                "border": "#1c1a29",
-                "foreground": "#75718e"
-              },
-              "hovered": {
-                "background": "#1c1a29",
-                "border": "#1c1a29",
-                "foreground": "#75718e"
-              },
-              "pressed": {
-                "background": "#1d1b2b",
-                "border": "#1c1a29",
-                "foreground": "#75718e"
-              },
-              "active": {
-                "background": "#222031",
-                "border": "#353347",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#191724",
-                "border": "#1a1826",
-                "foreground": "#4e4b63"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#1f1d2e"
-              }
-            },
-            "on": {
-              "default": {
-                "background": "#1d1b2a",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "hovered": {
-                "background": "#232132",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "pressed": {
-                "background": "#2f2d40",
-                "border": "#232132",
-                "foreground": "#e0def4"
-              },
-              "active": {
-                "background": "#403e53",
-                "border": "#504d65",
-                "foreground": "#e0def4"
-              },
-              "disabled": {
-                "background": "#1d1b2a",
-                "border": "#1e1c2c",
-                "foreground": "#3b384f"
-              },
-              "inverted": {
-                "background": "#e0def4",
-                "border": "#191724",
-                "foreground": "#3b394e"
-              }
-            },
-            "accent": {
-              "default": {
-                "background": "#2f3739",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "hovered": {
-                "background": "#435255",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "pressed": {
-                "background": "#4e6164",
-                "border": "#435255",
-                "foreground": "#9cced7"
-              },
-              "active": {
-                "background": "#5d757a",
-                "border": "#6e8f94",
-                "foreground": "#fbfdfd"
-              },
-              "disabled": {
-                "background": "#2f3739",
-                "border": "#3a4446",
-                "foreground": "#85aeb5"
-              },
-              "inverted": {
-                "background": "#fbfdfd",
-                "border": "#171717",
-                "foreground": "#587074"
-              }
-            },
-            "positive": {
-              "default": {
-                "background": "#182e23",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "hovered": {
-                "background": "#254839",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "pressed": {
-                "background": "#2c5645",
-                "border": "#254839",
-                "foreground": "#5dc2a3"
-              },
-              "active": {
-                "background": "#356b57",
-                "border": "#40836c",
-                "foreground": "#f9fdfb"
-              },
-              "disabled": {
-                "background": "#182e23",
-                "border": "#1e3b2e",
-                "foreground": "#4ea287"
-              },
-              "inverted": {
-                "background": "#f9fdfb",
-                "border": "#000e00",
-                "foreground": "#326552"
-              }
-            },
-            "warning": {
-              "default": {
-                "background": "#50341a",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "hovered": {
-                "background": "#6d4d2b",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "pressed": {
-                "background": "#7e5a34",
-                "border": "#6d4d2b",
-                "foreground": "#f5c177"
-              },
-              "active": {
-                "background": "#946e41",
-                "border": "#b0854f",
-                "foreground": "#fffcf9"
-              },
-              "disabled": {
-                "background": "#50341a",
-                "border": "#5e4023",
-                "foreground": "#d2a263"
-              },
-              "inverted": {
-                "background": "#fffcf9",
-                "border": "#2c1600",
-                "foreground": "#8e683c"
-              }
-            },
-            "negative": {
-              "default": {
-                "background": "#431820",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "hovered": {
-                "background": "#612834",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "pressed": {
-                "background": "#71303f",
-                "border": "#612834",
-                "foreground": "#ea6f92"
-              },
-              "active": {
-                "background": "#883c4f",
-                "border": "#a44961",
-                "foreground": "#fff9fa"
-              },
-              "disabled": {
-                "background": "#431820",
-                "border": "#52202a",
-                "foreground": "#c75c79"
-              },
-              "inverted": {
-                "background": "#fff9fa",
-                "border": "#230000",
-                "foreground": "#82384a"
-              }
-            }
-          },
-          "popover_shadow": {
-            "blur": 4,
-            "color": "#00000033",
-            "offset": [
-              1,
-              2
-            ]
-          },
-          "modal_shadow": {
-            "blur": 16,
-            "color": "#00000033",
-            "offset": [
-              0,
-              2
-            ]
-          },
-          "players": {
-            "0": {
-              "selection": "#9cced73d",
-              "cursor": "#9cced7"
-            },
-            "1": {
-              "selection": "#5dc2a33d",
-              "cursor": "#5dc2a3"
-            },
-            "2": {
-              "selection": "#9d76913d",
-              "cursor": "#9d7691"
-            },
-            "3": {
-              "selection": "#c4a7e63d",
-              "cursor": "#c4a7e6"
-            },
-            "4": {
-              "selection": "#c4a7e63d",
-              "cursor": "#c4a7e6"
-            },
-            "5": {
-              "selection": "#32748f3d",
-              "cursor": "#32748f"
-            },
-            "6": {
-              "selection": "#ea6f923d",
-              "cursor": "#ea6f92"
-            },
-            "7": {
-              "selection": "#f5c1773d",
-              "cursor": "#f5c177"
-            }
-          },
-          "syntax": {
-            "comment": {
-              "color": "#6e6a86"
-            },
-            "operator": {
-              "color": "#31748f"
-            },
-            "punctuation": {
-              "color": "#908caa"
-            },
-            "variable": {
-              "color": "#e0def4"
-            },
-            "string": {
-              "color": "#f6c177"
-            },
-            "type": {
-              "color": "#9ccfd8"
-            },
-            "type.builtin": {
-              "color": "#9ccfd8"
-            },
-            "boolean": {
-              "color": "#ebbcba"
-            },
-            "function": {
-              "color": "#ebbcba"
-            },
-            "keyword": {
-              "color": "#31748f"
-            },
-            "tag": {
-              "color": "#9ccfd8"
-            },
-            "function.method": {
-              "color": "#ebbcba"
-            },
-            "title": {
-              "color": "#f6c177"
-            },
-            "link_text": {
-              "color": "#9ccfd8",
-              "italic": false
-            },
-            "link_uri": {
-              "color": "#ebbcba"
-            }
-          },
-          "color_family": {
-            "neutral": {
-              "low": 11.568627450980392,
-              "high": 91.37254901960785,
-              "range": 79.80392156862746,
-              "scaling_value": 1.2530712530712529
-            },
-            "red": {
-              "low": 6.862745098039216,
-              "high": 100,
-              "range": 93.13725490196079,
-              "scaling_value": 1.0736842105263158
-            },
-            "orange": {
-              "low": 5.490196078431373,
-              "high": 100,
-              "range": 94.50980392156863,
-              "scaling_value": 1.058091286307054
-            },
-            "yellow": {
-              "low": 8.627450980392156,
-              "high": 100,
-              "range": 91.37254901960785,
-              "scaling_value": 1.094420600858369
-            },
-            "green": {
-              "low": 2.7450980392156863,
-              "high": 100,
-              "range": 97.25490196078431,
-              "scaling_value": 1.028225806451613
-            },
-            "cyan": {
-              "low": 0,
-              "high": 100,
-              "range": 100,
-              "scaling_value": 1
-            },
-            "blue": {
-              "low": 9.019607843137255,
-              "high": 100,
-              "range": 90.98039215686275,
-              "scaling_value": 1.0991379310344827
-            },
-            "violet": {
-              "low": 5.490196078431373,
-              "high": 100,
-              "range": 94.50980392156863,
-              "scaling_value": 1.058091286307054
-            },
-            "magenta": {
-              "low": 0,
-              "high": 100,
-              "range": 100,
-              "scaling_value": 1
-            }
-          }
-        }
-    })
-    .unwrap()
-}
-
-pub fn rose_pine_dawn() -> Theme {
-    serde_json::from_value(json!({
-      "name": "Rosé Pine Dawn",
-      "is_light": true,
-      "ramps": {},
-      "lowest": {
-        "base": {
-          "default": {
-            "background": "#dcd8d8",
-            "border": "#dcd6d5",
-            "foreground": "#575279"
-          },
-          "hovered": {
-            "background": "#dcd6d5",
-            "border": "#dcd6d5",
-            "foreground": "#575279"
-          },
-          "pressed": {
-            "background": "#efe6df",
-            "border": "#dcd6d5",
-            "foreground": "#575279"
-          },
-          "active": {
-            "background": "#c1bac1",
-            "border": "#a9a3b0",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#dcd8d8",
-            "border": "#d0cccf",
-            "foreground": "#938fa3"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#c7c0c5"
-          }
-        },
-        "variant": {
-          "default": {
-            "background": "#dcd8d8",
-            "border": "#dcd6d5",
-            "foreground": "#706c8c"
-          },
-          "hovered": {
-            "background": "#dcd6d5",
-            "border": "#dcd6d5",
-            "foreground": "#706c8c"
-          },
-          "pressed": {
-            "background": "#efe6df",
-            "border": "#dcd6d5",
-            "foreground": "#706c8c"
-          },
-          "active": {
-            "background": "#c1bac1",
-            "border": "#a9a3b0",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#dcd8d8",
-            "border": "#d0cccf",
-            "foreground": "#938fa3"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#c7c0c5"
-          }
-        },
-        "on": {
-          "default": {
-            "background": "#fef9f2",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "hovered": {
-            "background": "#e5e0df",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "pressed": {
-            "background": "#d4d0d2",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "active": {
-            "background": "#dbd5d4",
-            "border": "#dbd3d1",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#fef9f2",
-            "border": "#f6f1eb",
-            "foreground": "#b1abb5"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#d6d1d1"
-          }
-        },
-        "accent": {
-          "default": {
-            "background": "#dde9eb",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "hovered": {
-            "background": "#c3d7db",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "pressed": {
-            "background": "#b6cfd3",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "active": {
-            "background": "#a3c3c9",
-            "border": "#8db6bd",
-            "foreground": "#06090a"
-          },
-          "disabled": {
-            "background": "#dde9eb",
-            "border": "#d0e0e3",
-            "foreground": "#72a5ae"
-          },
-          "inverted": {
-            "background": "#06090a",
-            "border": "#ffffff",
-            "foreground": "#a8c7cd"
-          }
-        },
-        "positive": {
-          "default": {
-            "background": "#dbeee7",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "hovered": {
-            "background": "#bee0d5",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "pressed": {
-            "background": "#b0dacb",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "active": {
-            "background": "#9bd0bf",
-            "border": "#82c6b1",
-            "foreground": "#060a09"
-          },
-          "disabled": {
-            "background": "#dbeee7",
-            "border": "#cde7de",
-            "foreground": "#63b89f"
-          },
-          "inverted": {
-            "background": "#060a09",
-            "border": "#ffffff",
-            "foreground": "#a1d4c3"
-          }
-        },
-        "warning": {
-          "default": {
-            "background": "#ffebd6",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "hovered": {
-            "background": "#ffdab7",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "pressed": {
-            "background": "#fed2a6",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "active": {
-            "background": "#fbc891",
-            "border": "#f7bc77",
-            "foreground": "#330704"
-          },
-          "disabled": {
-            "background": "#ffebd6",
-            "border": "#ffe2c7",
-            "foreground": "#f1ac57"
-          },
-          "inverted": {
-            "background": "#330704",
-            "border": "#ffffff",
-            "foreground": "#fccb97"
-          }
-        },
-        "negative": {
-          "default": {
-            "background": "#f1dfe3",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "hovered": {
-            "background": "#e6c6cd",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "pressed": {
-            "background": "#e0bac2",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "active": {
-            "background": "#d8a8b3",
-            "border": "#ce94a3",
-            "foreground": "#0b0708"
-          },
-          "disabled": {
-            "background": "#f1dfe3",
-            "border": "#ecd2d8",
-            "foreground": "#c17b8e"
-          },
-          "inverted": {
-            "background": "#0b0708",
-            "border": "#ffffff",
-            "foreground": "#dbadb8"
-          }
-        }
-      },
-      "middle": {
-        "base": {
-          "default": {
-            "background": "#fef9f2",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "hovered": {
-            "background": "#e5e0df",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "pressed": {
-            "background": "#d4d0d2",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "active": {
-            "background": "#dbd5d4",
-            "border": "#dbd3d1",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#fef9f2",
-            "border": "#f6f1eb",
-            "foreground": "#b1abb5"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#d6d1d1"
-          }
-        },
-        "variant": {
-          "default": {
-            "background": "#fef9f2",
-            "border": "#e5e0df",
-            "foreground": "#706c8c"
-          },
-          "hovered": {
-            "background": "#e5e0df",
-            "border": "#e5e0df",
-            "foreground": "#706c8c"
-          },
-          "pressed": {
-            "background": "#d4d0d2",
-            "border": "#e5e0df",
-            "foreground": "#706c8c"
-          },
-          "active": {
-            "background": "#dbd5d4",
-            "border": "#dbd3d1",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#fef9f2",
-            "border": "#f6f1eb",
-            "foreground": "#b1abb5"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#d6d1d1"
-          }
-        },
-        "on": {
-          "default": {
-            "background": "#faf4ed",
-            "border": "#fdf8f1",
-            "foreground": "#575279"
-          },
-          "hovered": {
-            "background": "#fdf8f1",
-            "border": "#fdf8f1",
-            "foreground": "#575279"
-          },
-          "pressed": {
-            "background": "#fdf8f2",
-            "border": "#fdf8f1",
-            "foreground": "#575279"
-          },
-          "active": {
-            "background": "#e6e1e0",
-            "border": "#d0cccf",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#faf4ed",
-            "border": "#fcf6ef",
-            "foreground": "#efe6df"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#ede9e5"
-          }
-        },
-        "accent": {
-          "default": {
-            "background": "#dde9eb",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "hovered": {
-            "background": "#c3d7db",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "pressed": {
-            "background": "#b6cfd3",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "active": {
-            "background": "#a3c3c9",
-            "border": "#8db6bd",
-            "foreground": "#06090a"
-          },
-          "disabled": {
-            "background": "#dde9eb",
-            "border": "#d0e0e3",
-            "foreground": "#72a5ae"
-          },
-          "inverted": {
-            "background": "#06090a",
-            "border": "#ffffff",
-            "foreground": "#a8c7cd"
-          }
-        },
-        "positive": {
-          "default": {
-            "background": "#dbeee7",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "hovered": {
-            "background": "#bee0d5",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "pressed": {
-            "background": "#b0dacb",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "active": {
-            "background": "#9bd0bf",
-            "border": "#82c6b1",
-            "foreground": "#060a09"
-          },
-          "disabled": {
-            "background": "#dbeee7",
-            "border": "#cde7de",
-            "foreground": "#63b89f"
-          },
-          "inverted": {
-            "background": "#060a09",
-            "border": "#ffffff",
-            "foreground": "#a1d4c3"
-          }
-        },
-        "warning": {
-          "default": {
-            "background": "#ffebd6",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "hovered": {
-            "background": "#ffdab7",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "pressed": {
-            "background": "#fed2a6",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "active": {
-            "background": "#fbc891",
-            "border": "#f7bc77",
-            "foreground": "#330704"
-          },
-          "disabled": {
-            "background": "#ffebd6",
-            "border": "#ffe2c7",
-            "foreground": "#f1ac57"
-          },
-          "inverted": {
-            "background": "#330704",
-            "border": "#ffffff",
-            "foreground": "#fccb97"
-          }
-        },
-        "negative": {
-          "default": {
-            "background": "#f1dfe3",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "hovered": {
-            "background": "#e6c6cd",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "pressed": {
-            "background": "#e0bac2",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "active": {
-            "background": "#d8a8b3",
-            "border": "#ce94a3",
-            "foreground": "#0b0708"
-          },
-          "disabled": {
-            "background": "#f1dfe3",
-            "border": "#ecd2d8",
-            "foreground": "#c17b8e"
-          },
-          "inverted": {
-            "background": "#0b0708",
-            "border": "#ffffff",
-            "foreground": "#dbadb8"
-          }
-        }
-      },
-      "highest": {
-        "base": {
-          "default": {
-            "background": "#faf4ed",
-            "border": "#fdf8f1",
-            "foreground": "#575279"
-          },
-          "hovered": {
-            "background": "#fdf8f1",
-            "border": "#fdf8f1",
-            "foreground": "#575279"
-          },
-          "pressed": {
-            "background": "#fdf8f2",
-            "border": "#fdf8f1",
-            "foreground": "#575279"
-          },
-          "active": {
-            "background": "#e6e1e0",
-            "border": "#d0cccf",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#faf4ed",
-            "border": "#fcf6ef",
-            "foreground": "#efe6df"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#ede9e5"
-          }
-        },
-        "variant": {
-          "default": {
-            "background": "#faf4ed",
-            "border": "#fdf8f1",
-            "foreground": "#706c8c"
-          },
-          "hovered": {
-            "background": "#fdf8f1",
-            "border": "#fdf8f1",
-            "foreground": "#706c8c"
-          },
-          "pressed": {
-            "background": "#fdf8f2",
-            "border": "#fdf8f1",
-            "foreground": "#706c8c"
-          },
-          "active": {
-            "background": "#e6e1e0",
-            "border": "#d0cccf",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#faf4ed",
-            "border": "#fcf6ef",
-            "foreground": "#efe6df"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#ede9e5"
-          }
-        },
-        "on": {
-          "default": {
-            "background": "#fef9f2",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "hovered": {
-            "background": "#e5e0df",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "pressed": {
-            "background": "#d4d0d2",
-            "border": "#e5e0df",
-            "foreground": "#575279"
-          },
-          "active": {
-            "background": "#dbd5d4",
-            "border": "#dbd3d1",
-            "foreground": "#575279"
-          },
-          "disabled": {
-            "background": "#fef9f2",
-            "border": "#f6f1eb",
-            "foreground": "#b1abb5"
-          },
-          "inverted": {
-            "background": "#575279",
-            "border": "#faf4ed",
-            "foreground": "#d6d1d1"
-          }
-        },
-        "accent": {
-          "default": {
-            "background": "#dde9eb",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "hovered": {
-            "background": "#c3d7db",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "pressed": {
-            "background": "#b6cfd3",
-            "border": "#c3d7db",
-            "foreground": "#57949f"
-          },
-          "active": {
-            "background": "#a3c3c9",
-            "border": "#8db6bd",
-            "foreground": "#06090a"
-          },
-          "disabled": {
-            "background": "#dde9eb",
-            "border": "#d0e0e3",
-            "foreground": "#72a5ae"
-          },
-          "inverted": {
-            "background": "#06090a",
-            "border": "#ffffff",
-            "foreground": "#a8c7cd"
-          }
-        },
-        "positive": {
-          "default": {
-            "background": "#dbeee7",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "hovered": {
-            "background": "#bee0d5",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "pressed": {
-            "background": "#b0dacb",
-            "border": "#bee0d5",
-            "foreground": "#3eaa8e"
-          },
-          "active": {
-            "background": "#9bd0bf",
-            "border": "#82c6b1",
-            "foreground": "#060a09"
-          },
-          "disabled": {
-            "background": "#dbeee7",
-            "border": "#cde7de",
-            "foreground": "#63b89f"
-          },
-          "inverted": {
-            "background": "#060a09",
-            "border": "#ffffff",
-            "foreground": "#a1d4c3"
-          }
-        },
-        "warning": {
-          "default": {
-            "background": "#ffebd6",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "hovered": {
-            "background": "#ffdab7",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "pressed": {
-            "background": "#fed2a6",
-            "border": "#ffdab7",
-            "foreground": "#e99d35"
-          },
-          "active": {
-            "background": "#fbc891",
-            "border": "#f7bc77",
-            "foreground": "#330704"
-          },
-          "disabled": {
-            "background": "#ffebd6",
-            "border": "#ffe2c7",
-            "foreground": "#f1ac57"
-          },
-          "inverted": {
-            "background": "#330704",
-            "border": "#ffffff",
-            "foreground": "#fccb97"
-          }
-        },
-        "negative": {
-          "default": {
-            "background": "#f1dfe3",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "hovered": {
-            "background": "#e6c6cd",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "pressed": {
-            "background": "#e0bac2",
-            "border": "#e6c6cd",
-            "foreground": "#b4647a"
-          },
-          "active": {
-            "background": "#d8a8b3",
-            "border": "#ce94a3",
-            "foreground": "#0b0708"
-          },
-          "disabled": {
-            "background": "#f1dfe3",
-            "border": "#ecd2d8",
-            "foreground": "#c17b8e"
-          },
-          "inverted": {
-            "background": "#0b0708",
-            "border": "#ffffff",
-            "foreground": "#dbadb8"
-          }
-        }
-      },
-      "popover_shadow": {
-        "blur": 4,
-        "color": "#2c2a4d33",
-        "offset": [
-          1,
-          2
-        ]
-      },
-      "modal_shadow": {
-        "blur": 16,
-        "color": "#2c2a4d33",
-        "offset": [
-          0,
-          2
-        ]
-      },
-      "players": {
-        "0": {
-          "selection": "#57949f3d",
-          "cursor": "#57949f"
-        },
-        "1": {
-          "selection": "#3eaa8e3d",
-          "cursor": "#3eaa8e"
-        },
-        "2": {
-          "selection": "#7c697f3d",
-          "cursor": "#7c697f"
-        },
-        "3": {
-          "selection": "#907aa93d",
-          "cursor": "#907aa9"
-        },
-        "4": {
-          "selection": "#907aa93d",
-          "cursor": "#907aa9"
-        },
-        "5": {
-          "selection": "#2a69833d",
-          "cursor": "#2a6983"
-        },
-        "6": {
-          "selection": "#b4647a3d",
-          "cursor": "#b4647a"
-        },
-        "7": {
-          "selection": "#e99d353d",
-          "cursor": "#e99d35"
-        }
-      },
-      "syntax": {
-        "comment": {
-          "color": "#9893a5"
-        },
-        "operator": {
-          "color": "#286983"
-        },
-        "punctuation": {
-          "color": "#797593"
-        },
-        "variable": {
-          "color": "#575279"
-        },
-        "string": {
-          "color": "#ea9d34"
-        },
-        "type": {
-          "color": "#56949f"
-        },
-        "type.builtin": {
-          "color": "#56949f"
-        },
-        "boolean": {
-          "color": "#d7827e"
-        },
-        "function": {
-          "color": "#d7827e"
-        },
-        "keyword": {
-          "color": "#286983"
-        },
-        "tag": {
-          "color": "#56949f"
-        },
-        "function.method": {
-          "color": "#d7827e"
-        },
-        "title": {
-          "color": "#ea9d34"
-        },
-        "link_text": {
-          "color": "#56949f",
-          "italic": false
-        },
-        "link_uri": {
-          "color": "#d7827e"
-        }
-      },
-      "color_family": {
-        "neutral": {
-          "low": 39.80392156862745,
-          "high": 95.49019607843137,
-          "range": 55.686274509803916,
-          "scaling_value": 1.7957746478873242
-        },
-        "red": {
-          "low": 0,
-          "high": 100,
-          "range": 100,
-          "scaling_value": 1
-        },
-        "orange": {
-          "low": 0,
-          "high": 100,
-          "range": 100,
-          "scaling_value": 1
-        },
-        "yellow": {
-          "low": 8.823529411764707,
-          "high": 100,
-          "range": 91.17647058823529,
-          "scaling_value": 1.0967741935483872
-        },
-        "green": {
-          "low": 0,
-          "high": 100,
-          "range": 100,
-          "scaling_value": 1
-        },
-        "cyan": {
-          "low": 0,
-          "high": 100,
-          "range": 100,
-          "scaling_value": 1
-        },
-        "blue": {
-          "low": 0,
-          "high": 100,
-          "range": 100,
-          "scaling_value": 1
-        },
-        "violet": {
-          "low": 0,
-          "high": 100,
-          "range": 100,
-          "scaling_value": 1
-        },
-        "magenta": {
-          "low": 0,
-          "high": 100,
-          "range": 100,
-          "scaling_value": 1
-        }
-      }
-    }))
-    .unwrap()
-}

crates/theme2/src/default.rs 🔗

@@ -0,0 +1,2118 @@
+use gpui2::Rgba;
+use indexmap::IndexMap;
+
+use crate::scale::{ColorScaleName, ColorScaleSet, ColorScales};
+
+struct DefaultColorScaleSet {
+    scale: ColorScaleName,
+    light: [&'static str; 12],
+    light_alpha: [&'static str; 12],
+    dark: [&'static str; 12],
+    dark_alpha: [&'static str; 12],
+}
+
+impl From<DefaultColorScaleSet> for ColorScaleSet {
+    fn from(default: DefaultColorScaleSet) -> Self {
+        Self::new(
+            default.scale,
+            default
+                .light
+                .map(|color| Rgba::try_from(color).unwrap().into()),
+            default
+                .light_alpha
+                .map(|color| Rgba::try_from(color).unwrap().into()),
+            default
+                .dark
+                .map(|color| Rgba::try_from(color).unwrap().into()),
+            default
+                .dark_alpha
+                .map(|color| Rgba::try_from(color).unwrap().into()),
+        )
+    }
+}
+
+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()),
+    ])
+}
+
+fn gray() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Gray,
+        light: [
+            "#fcfcfcff",
+            "#f9f9f9ff",
+            "#f0f0f0ff",
+            "#e8e8e8ff",
+            "#e0e0e0ff",
+            "#d9d9d9ff",
+            "#cececeff",
+            "#bbbbbbff",
+            "#8d8d8dff",
+            "#838383ff",
+            "#646464ff",
+            "#202020ff",
+        ],
+        light_alpha: [
+            "#00000003",
+            "#00000006",
+            "#0000000f",
+            "#00000017",
+            "#0000001f",
+            "#00000026",
+            "#00000031",
+            "#00000044",
+            "#00000072",
+            "#0000007c",
+            "#0000009b",
+            "#000000df",
+        ],
+        dark: [
+            "#111111ff",
+            "#191919ff",
+            "#222222ff",
+            "#2a2a2aff",
+            "#313131ff",
+            "#3a3a3aff",
+            "#484848ff",
+            "#606060ff",
+            "#6e6e6eff",
+            "#7b7b7bff",
+            "#b4b4b4ff",
+            "#eeeeeeff",
+        ],
+        dark_alpha: [
+            "#00000000",
+            "#ffffff09",
+            "#ffffff12",
+            "#ffffff1b",
+            "#ffffff22",
+            "#ffffff2c",
+            "#ffffff3b",
+            "#ffffff55",
+            "#ffffff64",
+            "#ffffff72",
+            "#ffffffaf",
+            "#ffffffed",
+        ],
+    }
+}
+
+fn mauve() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Mauve,
+        light: [
+            "#fdfcfdff",
+            "#faf9fbff",
+            "#f2eff3ff",
+            "#eae7ecff",
+            "#e3dfe6ff",
+            "#dbd8e0ff",
+            "#d0cdd7ff",
+            "#bcbac7ff",
+            "#8e8c99ff",
+            "#84828eff",
+            "#65636dff",
+            "#211f26ff",
+        ],
+        light_alpha: [
+            "#55005503",
+            "#2b005506",
+            "#30004010",
+            "#20003618",
+            "#20003820",
+            "#14003527",
+            "#10003332",
+            "#08003145",
+            "#05001d73",
+            "#0500197d",
+            "#0400119c",
+            "#020008e0",
+        ],
+        dark: [
+            "#121113ff",
+            "#1a191bff",
+            "#232225ff",
+            "#2b292dff",
+            "#323035ff",
+            "#3c393fff",
+            "#49474eff",
+            "#625f69ff",
+            "#6f6d78ff",
+            "#7c7a85ff",
+            "#b5b2bcff",
+            "#eeeef0ff",
+        ],
+        dark_alpha: [
+            "#00000000",
+            "#f5f4f609",
+            "#ebeaf814",
+            "#eee5f81d",
+            "#efe6fe25",
+            "#f1e6fd30",
+            "#eee9ff40",
+            "#eee7ff5d",
+            "#eae6fd6e",
+            "#ece9fd7c",
+            "#f5f1ffb7",
+            "#fdfdffef",
+        ],
+    }
+}
+
+fn slate() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Slate,
+        light: [
+            "#fcfcfdff",
+            "#f9f9fbff",
+            "#f0f0f3ff",
+            "#e8e8ecff",
+            "#e0e1e6ff",
+            "#d9d9e0ff",
+            "#cdced6ff",
+            "#b9bbc6ff",
+            "#8b8d98ff",
+            "#80838dff",
+            "#60646cff",
+            "#1c2024ff",
+        ],
+        light_alpha: [
+            "#00005503",
+            "#00005506",
+            "#0000330f",
+            "#00002d17",
+            "#0009321f",
+            "#00002f26",
+            "#00062e32",
+            "#00083046",
+            "#00051d74",
+            "#00071b7f",
+            "#0007149f",
+            "#000509e3",
+        ],
+        dark: [
+            "#111113ff",
+            "#18191bff",
+            "#212225ff",
+            "#272a2dff",
+            "#2e3135ff",
+            "#363a3fff",
+            "#43484eff",
+            "#5a6169ff",
+            "#696e77ff",
+            "#777b84ff",
+            "#b0b4baff",
+            "#edeef0ff",
+        ],
+        dark_alpha: [
+            "#00000000",
+            "#d8f4f609",
+            "#ddeaf814",
+            "#d3edf81d",
+            "#d9edfe25",
+            "#d6ebfd30",
+            "#d9edff40",
+            "#d9edff5d",
+            "#dfebfd6d",
+            "#e5edfd7b",
+            "#f1f7feb5",
+            "#fcfdffef",
+        ],
+    }
+}
+
+fn sage() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Sage,
+        light: [
+            "#fbfdfcff",
+            "#f7f9f8ff",
+            "#eef1f0ff",
+            "#e6e9e8ff",
+            "#dfe2e0ff",
+            "#d7dad9ff",
+            "#cbcfcdff",
+            "#b8bcbaff",
+            "#868e8bff",
+            "#7c8481ff",
+            "#5f6563ff",
+            "#1a211eff",
+        ],
+        light_alpha: [
+            "#00804004",
+            "#00402008",
+            "#002d1e11",
+            "#001f1519",
+            "#00180820",
+            "#00140d28",
+            "#00140a34",
+            "#000f0847",
+            "#00110b79",
+            "#00100a83",
+            "#000a07a0",
+            "#000805e5",
+        ],
+        dark: [
+            "#101211ff",
+            "#171918ff",
+            "#202221ff",
+            "#272a29ff",
+            "#2e3130ff",
+            "#373b39ff",
+            "#444947ff",
+            "#5b625fff",
+            "#63706bff",
+            "#717d79ff",
+            "#adb5b2ff",
+            "#eceeedff",
+        ],
+        dark_alpha: [
+            "#00000000",
+            "#f0f2f108",
+            "#f3f5f412",
+            "#f2fefd1a",
+            "#f1fbfa22",
+            "#edfbf42d",
+            "#edfcf73c",
+            "#ebfdf657",
+            "#dffdf266",
+            "#e5fdf674",
+            "#f4fefbb0",
+            "#fdfffeed",
+        ],
+    }
+}
+
+fn olive() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Olive,
+        light: [
+            "#fcfdfcff",
+            "#f8faf8ff",
+            "#eff1efff",
+            "#e7e9e7ff",
+            "#dfe2dfff",
+            "#d7dad7ff",
+            "#cccfccff",
+            "#b9bcb8ff",
+            "#898e87ff",
+            "#7f847dff",
+            "#60655fff",
+            "#1d211cff",
+        ],
+        light_alpha: [
+            "#00550003",
+            "#00490007",
+            "#00200010",
+            "#00160018",
+            "#00180020",
+            "#00140028",
+            "#000f0033",
+            "#040f0047",
+            "#050f0078",
+            "#040e0082",
+            "#020a00a0",
+            "#010600e3",
+        ],
+        dark: [
+            "#111210ff",
+            "#181917ff",
+            "#212220ff",
+            "#282a27ff",
+            "#2f312eff",
+            "#383a36ff",
+            "#454843ff",
+            "#5c625bff",
+            "#687066ff",
+            "#767d74ff",
+            "#afb5adff",
+            "#eceeecff",
+        ],
+        dark_alpha: [
+            "#00000000",
+            "#f1f2f008",
+            "#f4f5f312",
+            "#f3fef21a",
+            "#f2fbf122",
+            "#f4faed2c",
+            "#f2fced3b",
+            "#edfdeb57",
+            "#ebfde766",
+            "#f0fdec74",
+            "#f6fef4b0",
+            "#fdfffded",
+        ],
+    }
+}
+
+fn sand() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Sand,
+        light: [
+            "#fdfdfcff",
+            "#f9f9f8ff",
+            "#f1f0efff",
+            "#e9e8e6ff",
+            "#e2e1deff",
+            "#dad9d6ff",
+            "#cfcecaff",
+            "#bcbbb5ff",
+            "#8d8d86ff",
+            "#82827cff",
+            "#63635eff",
+            "#21201cff",
+        ],
+        light_alpha: [
+            "#55550003",
+            "#25250007",
+            "#20100010",
+            "#1f150019",
+            "#1f180021",
+            "#19130029",
+            "#19140035",
+            "#1915014a",
+            "#0f0f0079",
+            "#0c0c0083",
+            "#080800a1",
+            "#060500e3",
+        ],
+        dark: [
+            "#111110ff",
+            "#191918ff",
+            "#222221ff",
+            "#2a2a28ff",
+            "#31312eff",
+            "#3b3a37ff",
+            "#494844ff",
+            "#62605bff",
+            "#6f6d66ff",
+            "#7c7b74ff",
+            "#b5b3adff",
+            "#eeeeecff",
+        ],
+        dark_alpha: [
+            "#00000000",
+            "#f4f4f309",
+            "#f6f6f513",
+            "#fefef31b",
+            "#fbfbeb23",
+            "#fffaed2d",
+            "#fffbed3c",
+            "#fff9eb57",
+            "#fffae965",
+            "#fffdee73",
+            "#fffcf4b0",
+            "#fffffded",
+        ],
+    }
+}
+
+fn gold() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Gold,
+        light: [
+            "#fdfdfcff",
+            "#faf9f2ff",
+            "#f2f0e7ff",
+            "#eae6dbff",
+            "#e1dccfff",
+            "#d8d0bfff",
+            "#cbc0aaff",
+            "#b9a88dff",
+            "#978365ff",
+            "#8c7a5eff",
+            "#71624bff",
+            "#3b352bff",
+        ],
+        light_alpha: [
+            "#55550003",
+            "#9d8a000d",
+            "#75600018",
+            "#6b4e0024",
+            "#60460030",
+            "#64440040",
+            "#63420055",
+            "#633d0072",
+            "#5332009a",
+            "#492d00a1",
+            "#362100b4",
+            "#130c00d4",
+        ],
+        dark: [
+            "#121211ff",
+            "#1b1a17ff",
+            "#24231fff",
+            "#2d2b26ff",
+            "#38352eff",
+            "#444039ff",
+            "#544f46ff",
+            "#696256ff",
+            "#978365ff",
+            "#a39073ff",
+            "#cbb99fff",
+            "#e8e2d9ff",
+        ],
+        dark_alpha: [
+            "#91911102",
+            "#f9e29d0b",
+            "#f8ecbb15",
+            "#ffeec41e",
+            "#feecc22a",
+            "#feebcb37",
+            "#ffedcd48",
+            "#fdeaca5f",
+            "#ffdba690",
+            "#fedfb09d",
+            "#fee7c6c8",
+            "#fef7ede7",
+        ],
+    }
+}
+
+fn bronze() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Bronze,
+        light: [
+            "#fdfcfcff",
+            "#fdf7f5ff",
+            "#f6edeaff",
+            "#efe4dfff",
+            "#e7d9d3ff",
+            "#dfcdc5ff",
+            "#d3bcb3ff",
+            "#c2a499ff",
+            "#a18072ff",
+            "#957468ff",
+            "#7d5e54ff",
+            "#43302bff",
+        ],
+        light_alpha: [
+            "#55000003",
+            "#cc33000a",
+            "#92250015",
+            "#80280020",
+            "#7423002c",
+            "#7324003a",
+            "#6c1f004c",
+            "#671c0066",
+            "#551a008d",
+            "#4c150097",
+            "#3d0f00ab",
+            "#1d0600d4",
+        ],
+        dark: [
+            "#141110ff",
+            "#1c1917ff",
+            "#262220ff",
+            "#302a27ff",
+            "#3b3330ff",
+            "#493e3aff",
+            "#5a4c47ff",
+            "#6f5f58ff",
+            "#a18072ff",
+            "#ae8c7eff",
+            "#d4b3a5ff",
+            "#ede0d9ff",
+        ],
+        dark_alpha: [
+            "#d1110004",
+            "#fbbc910c",
+            "#faceb817",
+            "#facdb622",
+            "#ffd2c12d",
+            "#ffd1c03c",
+            "#fdd0c04f",
+            "#ffd6c565",
+            "#fec7b09b",
+            "#fecab5a9",
+            "#ffd7c6d1",
+            "#fff1e9ec",
+        ],
+    }
+}
+
+fn brown() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Brown,
+        light: [
+            "#fefdfcff",
+            "#fcf9f6ff",
+            "#f6eee7ff",
+            "#f0e4d9ff",
+            "#ebdacaff",
+            "#e4cdb7ff",
+            "#dcbc9fff",
+            "#cea37eff",
+            "#ad7f58ff",
+            "#a07553ff",
+            "#815e46ff",
+            "#3e332eff",
+        ],
+        light_alpha: [
+            "#aa550003",
+            "#aa550009",
+            "#a04b0018",
+            "#9b4a0026",
+            "#9f4d0035",
+            "#a04e0048",
+            "#a34e0060",
+            "#9f4a0081",
+            "#823c00a7",
+            "#723300ac",
+            "#522100b9",
+            "#140600d1",
+        ],
+        dark: [
+            "#12110fff",
+            "#1c1816ff",
+            "#28211dff",
+            "#322922ff",
+            "#3e3128ff",
+            "#4d3c2fff",
+            "#614a39ff",
+            "#7c5f46ff",
+            "#ad7f58ff",
+            "#b88c67ff",
+            "#dbb594ff",
+            "#f2e1caff",
+        ],
+        dark_alpha: [
+            "#91110002",
+            "#fba67c0c",
+            "#fcb58c19",
+            "#fbbb8a24",
+            "#fcb88931",
+            "#fdba8741",
+            "#ffbb8856",
+            "#ffbe8773",
+            "#feb87da8",
+            "#ffc18cb3",
+            "#fed1aad9",
+            "#feecd4f2",
+        ],
+    }
+}
+
+fn yellow() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Yellow,
+        light: [
+            "#fdfdf9ff",
+            "#fefce9ff",
+            "#fffab8ff",
+            "#fff394ff",
+            "#ffe770ff",
+            "#f3d768ff",
+            "#e4c767ff",
+            "#d5ae39ff",
+            "#ffe629ff",
+            "#ffdc00ff",
+            "#9e6c00ff",
+            "#473b1fff",
+        ],
+        light_alpha: [
+            "#aaaa0006",
+            "#f4dd0016",
+            "#ffee0047",
+            "#ffe3016b",
+            "#ffd5008f",
+            "#ebbc0097",
+            "#d2a10098",
+            "#c99700c6",
+            "#ffe100d6",
+            "#ffdc00ff",
+            "#9e6c00ff",
+            "#2e2000e0",
+        ],
+        dark: [
+            "#14120bff",
+            "#1b180fff",
+            "#2d2305ff",
+            "#362b00ff",
+            "#433500ff",
+            "#524202ff",
+            "#665417ff",
+            "#836a21ff",
+            "#ffe629ff",
+            "#ffff57ff",
+            "#f5e147ff",
+            "#f6eeb4ff",
+        ],
+        dark_alpha: [
+            "#d1510004",
+            "#f9b4000b",
+            "#ffaa001e",
+            "#fdb70028",
+            "#febb0036",
+            "#fec40046",
+            "#fdcb225c",
+            "#fdca327b",
+            "#ffe629ff",
+            "#ffff57ff",
+            "#fee949f5",
+            "#fef6baf6",
+        ],
+    }
+}
+
+fn amber() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Amber,
+        light: [
+            "#fefdfbff",
+            "#fefbe9ff",
+            "#fff7c2ff",
+            "#ffee9cff",
+            "#fbe577ff",
+            "#f3d673ff",
+            "#e9c162ff",
+            "#e2a336ff",
+            "#ffc53dff",
+            "#ffba18ff",
+            "#ab6400ff",
+            "#4f3422ff",
+        ],
+        light_alpha: [
+            "#c0800004",
+            "#f4d10016",
+            "#ffde003d",
+            "#ffd40063",
+            "#f8cf0088",
+            "#eab5008c",
+            "#dc9b009d",
+            "#da8a00c9",
+            "#ffb300c2",
+            "#ffb300e7",
+            "#ab6400ff",
+            "#341500dd",
+        ],
+        dark: [
+            "#16120cff",
+            "#1d180fff",
+            "#302008ff",
+            "#3f2700ff",
+            "#4d3000ff",
+            "#5c3d05ff",
+            "#714f19ff",
+            "#8f6424ff",
+            "#ffc53dff",
+            "#ffd60aff",
+            "#ffca16ff",
+            "#ffe7b3ff",
+        ],
+        dark_alpha: [
+            "#e63c0006",
+            "#fd9b000d",
+            "#fa820022",
+            "#fc820032",
+            "#fd8b0041",
+            "#fd9b0051",
+            "#ffab2567",
+            "#ffae3587",
+            "#ffc53dff",
+            "#ffd60aff",
+            "#ffca16ff",
+            "#ffe7b3ff",
+        ],
+    }
+}
+
+fn orange() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Orange,
+        light: [
+            "#fefcfbff",
+            "#fff7edff",
+            "#ffefd6ff",
+            "#ffdfb5ff",
+            "#ffd19aff",
+            "#ffc182ff",
+            "#f5ae73ff",
+            "#ec9455ff",
+            "#f76b15ff",
+            "#ef5f00ff",
+            "#cc4e00ff",
+            "#582d1dff",
+        ],
+        light_alpha: [
+            "#c0400004",
+            "#ff8e0012",
+            "#ff9c0029",
+            "#ff91014a",
+            "#ff8b0065",
+            "#ff81007d",
+            "#ed6c008c",
+            "#e35f00aa",
+            "#f65e00ea",
+            "#ef5f00ff",
+            "#cc4e00ff",
+            "#431200e2",
+        ],
+        dark: [
+            "#17120eff",
+            "#1e160fff",
+            "#331e0bff",
+            "#462100ff",
+            "#562800ff",
+            "#66350cff",
+            "#7e451dff",
+            "#a35829ff",
+            "#f76b15ff",
+            "#ff801fff",
+            "#ffa057ff",
+            "#ffe0c2ff",
+        ],
+        dark_alpha: [
+            "#ec360007",
+            "#fe6d000e",
+            "#fb6a0025",
+            "#ff590039",
+            "#ff61004a",
+            "#fd75045c",
+            "#ff832c75",
+            "#fe84389d",
+            "#fe6d15f7",
+            "#ff801fff",
+            "#ffa057ff",
+            "#ffe0c2ff",
+        ],
+    }
+}
+
+fn tomato() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Tomato,
+        light: [
+            "#fffcfcff",
+            "#fff8f7ff",
+            "#feebe7ff",
+            "#ffdcd3ff",
+            "#ffcdc2ff",
+            "#fdbdafff",
+            "#f5a898ff",
+            "#ec8e7bff",
+            "#e54d2eff",
+            "#dd4425ff",
+            "#d13415ff",
+            "#5c271fff",
+        ],
+        light_alpha: [
+            "#ff000003",
+            "#ff200008",
+            "#f52b0018",
+            "#ff35002c",
+            "#ff2e003d",
+            "#f92d0050",
+            "#e7280067",
+            "#db250084",
+            "#df2600d1",
+            "#d72400da",
+            "#cd2200ea",
+            "#460900e0",
+        ],
+        dark: [
+            "#181111ff",
+            "#1f1513ff",
+            "#391714ff",
+            "#4e1511ff",
+            "#5e1c16ff",
+            "#6e2920ff",
+            "#853a2dff",
+            "#ac4d39ff",
+            "#e54d2eff",
+            "#ec6142ff",
+            "#ff977dff",
+            "#fbd3cbff",
+        ],
+        dark_alpha: [
+            "#f1121208",
+            "#ff55330f",
+            "#ff35232b",
+            "#fd201142",
+            "#fe332153",
+            "#ff4f3864",
+            "#fd644a7d",
+            "#fe6d4ea7",
+            "#fe5431e4",
+            "#ff6847eb",
+            "#ff977dff",
+            "#ffd6cefb",
+        ],
+    }
+}
+
+fn red() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Red,
+        light: [
+            "#fffcfcff",
+            "#fff7f7ff",
+            "#feebecff",
+            "#ffdbdcff",
+            "#ffcdceff",
+            "#fdbdbeff",
+            "#f4a9aaff",
+            "#eb8e90ff",
+            "#e5484dff",
+            "#dc3e42ff",
+            "#ce2c31ff",
+            "#641723ff",
+        ],
+        light_alpha: [
+            "#ff000003",
+            "#ff000008",
+            "#f3000d14",
+            "#ff000824",
+            "#ff000632",
+            "#f8000442",
+            "#df000356",
+            "#d2000571",
+            "#db0007b7",
+            "#d10005c1",
+            "#c40006d3",
+            "#55000de8",
+        ],
+        dark: [
+            "#191111ff",
+            "#201314ff",
+            "#3b1219ff",
+            "#500f1cff",
+            "#611623ff",
+            "#72232dff",
+            "#8c333aff",
+            "#b54548ff",
+            "#e5484dff",
+            "#ec5d5eff",
+            "#ff9592ff",
+            "#ffd1d9ff",
+        ],
+        dark_alpha: [
+            "#f4121209",
+            "#f22f3e11",
+            "#ff173f2d",
+            "#fe0a3b44",
+            "#ff204756",
+            "#ff3e5668",
+            "#ff536184",
+            "#ff5d61b0",
+            "#fe4e54e4",
+            "#ff6465eb",
+            "#ff9592ff",
+            "#ffd1d9ff",
+        ],
+    }
+}
+
+fn ruby() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Ruby,
+        light: [
+            "#fffcfdff",
+            "#fff7f8ff",
+            "#feeaedff",
+            "#ffdce1ff",
+            "#ffced6ff",
+            "#f8bfc8ff",
+            "#efacb8ff",
+            "#e592a3ff",
+            "#e54666ff",
+            "#dc3b5dff",
+            "#ca244dff",
+            "#64172bff",
+        ],
+        light_alpha: [
+            "#ff005503",
+            "#ff002008",
+            "#f3002515",
+            "#ff002523",
+            "#ff002a31",
+            "#e4002440",
+            "#ce002553",
+            "#c300286d",
+            "#db002cb9",
+            "#d2002cc4",
+            "#c10030db",
+            "#550016e8",
+        ],
+        dark: [
+            "#191113ff",
+            "#1e1517ff",
+            "#3a141eff",
+            "#4e1325ff",
+            "#5e1a2eff",
+            "#6f2539ff",
+            "#883447ff",
+            "#b3445aff",
+            "#e54666ff",
+            "#ec5a72ff",
+            "#ff949dff",
+            "#fed2e1ff",
+        ],
+        dark_alpha: [
+            "#f4124a09",
+            "#fe5a7f0e",
+            "#ff235d2c",
+            "#fd195e42",
+            "#fe2d6b53",
+            "#ff447665",
+            "#ff577d80",
+            "#ff5c7cae",
+            "#fe4c70e4",
+            "#ff617beb",
+            "#ff949dff",
+            "#ffd3e2fe",
+        ],
+    }
+}
+
+fn crimson() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Crimson,
+        light: [
+            "#fffcfdff",
+            "#fef7f9ff",
+            "#ffe9f0ff",
+            "#fedce7ff",
+            "#faceddff",
+            "#f3bed1ff",
+            "#eaacc3ff",
+            "#e093b2ff",
+            "#e93d82ff",
+            "#df3478ff",
+            "#cb1d63ff",
+            "#621639ff",
+        ],
+        light_alpha: [
+            "#ff005503",
+            "#e0004008",
+            "#ff005216",
+            "#f8005123",
+            "#e5004f31",
+            "#d0004b41",
+            "#bf004753",
+            "#b6004a6c",
+            "#e2005bc2",
+            "#d70056cb",
+            "#c4004fe2",
+            "#530026e9",
+        ],
+        dark: [
+            "#191114ff",
+            "#201318ff",
+            "#381525ff",
+            "#4d122fff",
+            "#5c1839ff",
+            "#6d2545ff",
+            "#873356ff",
+            "#b0436eff",
+            "#e93d82ff",
+            "#ee518aff",
+            "#ff92adff",
+            "#fdd3e8ff",
+        ],
+        dark_alpha: [
+            "#f4126709",
+            "#f22f7a11",
+            "#fe2a8b2a",
+            "#fd158741",
+            "#fd278f51",
+            "#fe459763",
+            "#fd559b7f",
+            "#fe5b9bab",
+            "#fe418de8",
+            "#ff5693ed",
+            "#ff92adff",
+            "#ffd5eafd",
+        ],
+    }
+}
+
+fn pink() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Pink,
+        light: [
+            "#fffcfeff",
+            "#fef7fbff",
+            "#fee9f5ff",
+            "#fbdcefff",
+            "#f6cee7ff",
+            "#efbfddff",
+            "#e7acd0ff",
+            "#dd93c2ff",
+            "#d6409fff",
+            "#cf3897ff",
+            "#c2298aff",
+            "#651249ff",
+        ],
+        light_alpha: [
+            "#ff00aa03",
+            "#e0008008",
+            "#f4008c16",
+            "#e2008b23",
+            "#d1008331",
+            "#c0007840",
+            "#b6006f53",
+            "#af006f6c",
+            "#c8007fbf",
+            "#c2007ac7",
+            "#b60074d6",
+            "#59003bed",
+        ],
+        dark: [
+            "#191117ff",
+            "#21121dff",
+            "#37172fff",
+            "#4b143dff",
+            "#591c47ff",
+            "#692955ff",
+            "#833869ff",
+            "#a84885ff",
+            "#d6409fff",
+            "#de51a8ff",
+            "#ff8dccff",
+            "#fdd1eaff",
+        ],
+        dark_alpha: [
+            "#f412bc09",
+            "#f420bb12",
+            "#fe37cc29",
+            "#fc1ec43f",
+            "#fd35c24e",
+            "#fd51c75f",
+            "#fd62c87b",
+            "#ff68c8a2",
+            "#fe49bcd4",
+            "#ff5cc0dc",
+            "#ff8dccff",
+            "#ffd3ecfd",
+        ],
+    }
+}
+
+fn plum() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Plum,
+        light: [
+            "#fefcffff",
+            "#fdf7fdff",
+            "#fbebfbff",
+            "#f7def8ff",
+            "#f2d1f3ff",
+            "#e9c2ecff",
+            "#deade3ff",
+            "#cf91d8ff",
+            "#ab4abaff",
+            "#a144afff",
+            "#953ea3ff",
+            "#53195dff",
+        ],
+        light_alpha: [
+            "#aa00ff03",
+            "#c000c008",
+            "#cc00cc14",
+            "#c200c921",
+            "#b700bd2e",
+            "#a400b03d",
+            "#9900a852",
+            "#9000a56e",
+            "#89009eb5",
+            "#7f0092bb",
+            "#730086c1",
+            "#40004be6",
+        ],
+        dark: [
+            "#181118ff",
+            "#201320ff",
+            "#351a35ff",
+            "#451d47ff",
+            "#512454ff",
+            "#5e3061ff",
+            "#734079ff",
+            "#92549cff",
+            "#ab4abaff",
+            "#b658c4ff",
+            "#e796f3ff",
+            "#f4d4f4ff",
+        ],
+        dark_alpha: [
+            "#f112f108",
+            "#f22ff211",
+            "#fd4cfd27",
+            "#f646ff3a",
+            "#f455ff48",
+            "#f66dff56",
+            "#f07cfd70",
+            "#ee84ff95",
+            "#e961feb6",
+            "#ed70ffc0",
+            "#f19cfef3",
+            "#feddfef4",
+        ],
+    }
+}
+
+fn purple() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Purple,
+        light: [
+            "#fefcfeff",
+            "#fbf7feff",
+            "#f7edfeff",
+            "#f2e2fcff",
+            "#ead5f9ff",
+            "#e0c4f4ff",
+            "#d1afecff",
+            "#be93e4ff",
+            "#8e4ec6ff",
+            "#8347b9ff",
+            "#8145b5ff",
+            "#402060ff",
+        ],
+        light_alpha: [
+            "#aa00aa03",
+            "#8000e008",
+            "#8e00f112",
+            "#8d00e51d",
+            "#8000db2a",
+            "#7a01d03b",
+            "#6d00c350",
+            "#6600c06c",
+            "#5c00adb1",
+            "#53009eb8",
+            "#52009aba",
+            "#250049df",
+        ],
+        dark: [
+            "#18111bff",
+            "#1e1523ff",
+            "#301c3bff",
+            "#3d224eff",
+            "#48295cff",
+            "#54346bff",
+            "#664282ff",
+            "#8457aaff",
+            "#8e4ec6ff",
+            "#9a5cd0ff",
+            "#d19dffff",
+            "#ecd9faff",
+        ],
+        dark_alpha: [
+            "#b412f90b",
+            "#b744f714",
+            "#c150ff2d",
+            "#bb53fd42",
+            "#be5cfd51",
+            "#c16dfd61",
+            "#c378fd7a",
+            "#c47effa4",
+            "#b661ffc2",
+            "#bc6fffcd",
+            "#d19dffff",
+            "#f1ddfffa",
+        ],
+    }
+}
+
+fn violet() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Violet,
+        light: [
+            "#fdfcfeff",
+            "#faf8ffff",
+            "#f4f0feff",
+            "#ebe4ffff",
+            "#e1d9ffff",
+            "#d4cafeff",
+            "#c2b5f5ff",
+            "#aa99ecff",
+            "#6e56cfff",
+            "#654dc4ff",
+            "#6550b9ff",
+            "#2f265fff",
+        ],
+        light_alpha: [
+            "#5500aa03",
+            "#4900ff07",
+            "#4400ee0f",
+            "#4300ff1b",
+            "#3600ff26",
+            "#3100fb35",
+            "#2d01dd4a",
+            "#2b00d066",
+            "#2400b7a9",
+            "#2300abb2",
+            "#1f0099af",
+            "#0b0043d9",
+        ],
+        dark: [
+            "#14121fff",
+            "#1b1525ff",
+            "#291f43ff",
+            "#33255bff",
+            "#3c2e69ff",
+            "#473876ff",
+            "#56468bff",
+            "#6958adff",
+            "#6e56cfff",
+            "#7d66d9ff",
+            "#baa7ffff",
+            "#e2ddfeff",
+        ],
+        dark_alpha: [
+            "#4422ff0f",
+            "#853ff916",
+            "#8354fe36",
+            "#7d51fd50",
+            "#845ffd5f",
+            "#8f6cfd6d",
+            "#9879ff83",
+            "#977dfea8",
+            "#8668ffcc",
+            "#9176fed7",
+            "#baa7ffff",
+            "#e3defffe",
+        ],
+    }
+}
+
+fn iris() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Iris,
+        light: [
+            "#fdfdffff",
+            "#f8f8ffff",
+            "#f0f1feff",
+            "#e6e7ffff",
+            "#dadcffff",
+            "#cbcdffff",
+            "#b8baf8ff",
+            "#9b9ef0ff",
+            "#5b5bd6ff",
+            "#5151cdff",
+            "#5753c6ff",
+            "#272962ff",
+        ],
+        light_alpha: [
+            "#0000ff02",
+            "#0000ff07",
+            "#0011ee0f",
+            "#000bff19",
+            "#000eff25",
+            "#000aff34",
+            "#0008e647",
+            "#0008d964",
+            "#0000c0a4",
+            "#0000b6ae",
+            "#0600abac",
+            "#000246d8",
+        ],
+        dark: [
+            "#13131eff",
+            "#171625ff",
+            "#202248ff",
+            "#262a65ff",
+            "#303374ff",
+            "#3d3e82ff",
+            "#4a4a95ff",
+            "#5958b1ff",
+            "#5b5bd6ff",
+            "#6e6adeff",
+            "#b1a9ffff",
+            "#e0dffeff",
+        ],
+        dark_alpha: [
+            "#3636fe0e",
+            "#564bf916",
+            "#525bff3b",
+            "#4d58ff5a",
+            "#5b62fd6b",
+            "#6d6ffd7a",
+            "#7777fe8e",
+            "#7b7afeac",
+            "#6a6afed4",
+            "#7d79ffdc",
+            "#b1a9ffff",
+            "#e1e0fffe",
+        ],
+    }
+}
+
+fn indigo() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Indigo,
+        light: [
+            "#fdfdfeff",
+            "#f7f9ffff",
+            "#edf2feff",
+            "#e1e9ffff",
+            "#d2deffff",
+            "#c1d0ffff",
+            "#abbdf9ff",
+            "#8da4efff",
+            "#3e63ddff",
+            "#3358d4ff",
+            "#3a5bc7ff",
+            "#1f2d5cff",
+        ],
+        light_alpha: [
+            "#00008002",
+            "#0040ff08",
+            "#0047f112",
+            "#0044ff1e",
+            "#0044ff2d",
+            "#003eff3e",
+            "#0037ed54",
+            "#0034dc72",
+            "#0031d2c1",
+            "#002ec9cc",
+            "#002bb7c5",
+            "#001046e0",
+        ],
+        dark: [
+            "#11131fff",
+            "#141726ff",
+            "#182449ff",
+            "#1d2e62ff",
+            "#253974ff",
+            "#304384ff",
+            "#3a4f97ff",
+            "#435db1ff",
+            "#3e63ddff",
+            "#5472e4ff",
+            "#9eb1ffff",
+            "#d6e1ffff",
+        ],
+        dark_alpha: [
+            "#1133ff0f",
+            "#3354fa17",
+            "#2f62ff3c",
+            "#3566ff57",
+            "#4171fd6b",
+            "#5178fd7c",
+            "#5a7fff90",
+            "#5b81feac",
+            "#4671ffdb",
+            "#5c7efee3",
+            "#9eb1ffff",
+            "#d6e1ffff",
+        ],
+    }
+}
+
+fn blue() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Blue,
+        light: [
+            "#fbfdffff",
+            "#f4faffff",
+            "#e6f4feff",
+            "#d5efffff",
+            "#c2e5ffff",
+            "#acd8fcff",
+            "#8ec8f6ff",
+            "#5eb1efff",
+            "#0090ffff",
+            "#0588f0ff",
+            "#0d74ceff",
+            "#113264ff",
+        ],
+        light_alpha: [
+            "#0080ff04",
+            "#008cff0b",
+            "#008ff519",
+            "#009eff2a",
+            "#0093ff3d",
+            "#0088f653",
+            "#0083eb71",
+            "#0084e6a1",
+            "#0090ffff",
+            "#0086f0fa",
+            "#006dcbf2",
+            "#002359ee",
+        ],
+        dark: [
+            "#0d1520ff",
+            "#111927ff",
+            "#0d2847ff",
+            "#003362ff",
+            "#004074ff",
+            "#104d87ff",
+            "#205d9eff",
+            "#2870bdff",
+            "#0090ffff",
+            "#3b9effff",
+            "#70b8ffff",
+            "#c2e6ffff",
+        ],
+        dark_alpha: [
+            "#004df211",
+            "#1166fb18",
+            "#0077ff3a",
+            "#0075ff57",
+            "#0081fd6b",
+            "#0f89fd7f",
+            "#2a91fe98",
+            "#3094feb9",
+            "#0090ffff",
+            "#3b9effff",
+            "#70b8ffff",
+            "#c2e6ffff",
+        ],
+    }
+}
+
+fn cyan() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Cyan,
+        light: [
+            "#fafdfeff",
+            "#f2fafbff",
+            "#def7f9ff",
+            "#caf1f6ff",
+            "#b5e9f0ff",
+            "#9ddde7ff",
+            "#7dcedcff",
+            "#3db9cfff",
+            "#00a2c7ff",
+            "#0797b9ff",
+            "#107d98ff",
+            "#0d3c48ff",
+        ],
+        light_alpha: [
+            "#0099cc05",
+            "#009db10d",
+            "#00c2d121",
+            "#00bcd435",
+            "#01b4cc4a",
+            "#00a7c162",
+            "#009fbb82",
+            "#00a3c0c2",
+            "#00a2c7ff",
+            "#0094b7f8",
+            "#007491ef",
+            "#00323ef2",
+        ],
+        dark: [
+            "#0b161aff",
+            "#101b20ff",
+            "#082c36ff",
+            "#003848ff",
+            "#004558ff",
+            "#045468ff",
+            "#12677eff",
+            "#11809cff",
+            "#00a2c7ff",
+            "#23afd0ff",
+            "#4ccce6ff",
+            "#b6ecf7ff",
+        ],
+        dark_alpha: [
+            "#0091f70a",
+            "#02a7f211",
+            "#00befd28",
+            "#00baff3b",
+            "#00befd4d",
+            "#00c7fd5e",
+            "#14cdff75",
+            "#11cfff95",
+            "#00cfffc3",
+            "#28d6ffcd",
+            "#52e1fee5",
+            "#bbf3fef7",
+        ],
+    }
+}
+
+fn teal() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Teal,
+        light: [
+            "#fafefdff",
+            "#f3fbf9ff",
+            "#e0f8f3ff",
+            "#ccf3eaff",
+            "#b8eae0ff",
+            "#a1ded2ff",
+            "#83cdc1ff",
+            "#53b9abff",
+            "#12a594ff",
+            "#0d9b8aff",
+            "#008573ff",
+            "#0d3d38ff",
+        ],
+        light_alpha: [
+            "#00cc9905",
+            "#00aa800c",
+            "#00c69d1f",
+            "#00c39633",
+            "#00b49047",
+            "#00a6855e",
+            "#0099807c",
+            "#009783ac",
+            "#009e8ced",
+            "#009684f2",
+            "#008573ff",
+            "#00332df2",
+        ],
+        dark: [
+            "#0d1514ff",
+            "#111c1bff",
+            "#0d2d2aff",
+            "#023b37ff",
+            "#084843ff",
+            "#145750ff",
+            "#1c6961ff",
+            "#207e73ff",
+            "#12a594ff",
+            "#0eb39eff",
+            "#0bd8b6ff",
+            "#adf0ddff",
+        ],
+        dark_alpha: [
+            "#00deab05",
+            "#12fbe60c",
+            "#00ffe61e",
+            "#00ffe92d",
+            "#00ffea3b",
+            "#1cffe84b",
+            "#2efde85f",
+            "#32ffe775",
+            "#13ffe49f",
+            "#0dffe0ae",
+            "#0afed5d6",
+            "#b8ffebef",
+        ],
+    }
+}
+
+fn jade() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Jade,
+        light: [
+            "#fbfefdff",
+            "#f4fbf7ff",
+            "#e6f7edff",
+            "#d6f1e3ff",
+            "#c3e9d7ff",
+            "#acdec8ff",
+            "#8bceb6ff",
+            "#56ba9fff",
+            "#29a383ff",
+            "#26997bff",
+            "#208368ff",
+            "#1d3b31ff",
+        ],
+        light_alpha: [
+            "#00c08004",
+            "#00a3460b",
+            "#00ae4819",
+            "#00a85129",
+            "#00a2553c",
+            "#009a5753",
+            "#00945f74",
+            "#00976ea9",
+            "#00916bd6",
+            "#008764d9",
+            "#007152df",
+            "#002217e2",
+        ],
+        dark: [
+            "#0d1512ff",
+            "#121c18ff",
+            "#0f2e22ff",
+            "#0b3b2cff",
+            "#114837ff",
+            "#1b5745ff",
+            "#246854ff",
+            "#2a7e68ff",
+            "#29a383ff",
+            "#27b08bff",
+            "#1fd8a4ff",
+            "#adf0d4ff",
+        ],
+        dark_alpha: [
+            "#00de4505",
+            "#27fba60c",
+            "#02f99920",
+            "#00ffaa2d",
+            "#11ffb63b",
+            "#34ffc24b",
+            "#45fdc75e",
+            "#48ffcf75",
+            "#38feca9d",
+            "#31fec7ab",
+            "#21fec0d6",
+            "#b8ffe1ef",
+        ],
+    }
+}
+
+fn green() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Green,
+        light: [
+            "#fbfefcff",
+            "#f4fbf6ff",
+            "#e6f6ebff",
+            "#d6f1dfff",
+            "#c4e8d1ff",
+            "#adddc0ff",
+            "#8eceaaff",
+            "#5bb98bff",
+            "#30a46cff",
+            "#2b9a66ff",
+            "#218358ff",
+            "#193b2dff",
+        ],
+        light_alpha: [
+            "#00c04004",
+            "#00a32f0b",
+            "#00a43319",
+            "#00a83829",
+            "#019c393b",
+            "#00963c52",
+            "#00914071",
+            "#00924ba4",
+            "#008f4acf",
+            "#008647d4",
+            "#00713fde",
+            "#002616e6",
+        ],
+        dark: [
+            "#0e1512ff",
+            "#121b17ff",
+            "#132d21ff",
+            "#113b29ff",
+            "#174933ff",
+            "#20573eff",
+            "#28684aff",
+            "#2f7c57ff",
+            "#30a46cff",
+            "#33b074ff",
+            "#3dd68cff",
+            "#b1f1cbff",
+        ],
+        dark_alpha: [
+            "#00de4505",
+            "#29f99d0b",
+            "#22ff991e",
+            "#11ff992d",
+            "#2bffa23c",
+            "#44ffaa4b",
+            "#50fdac5e",
+            "#54ffad73",
+            "#44ffa49e",
+            "#43fea4ab",
+            "#46fea5d4",
+            "#bbffd7f0",
+        ],
+    }
+}
+
+fn grass() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Grass,
+        light: [
+            "#fbfefbff",
+            "#f5fbf5ff",
+            "#e9f6e9ff",
+            "#daf1dbff",
+            "#c9e8caff",
+            "#b2ddb5ff",
+            "#94ce9aff",
+            "#65ba74ff",
+            "#46a758ff",
+            "#3e9b4fff",
+            "#2a7e3bff",
+            "#203c25ff",
+        ],
+        light_alpha: [
+            "#00c00004",
+            "#0099000a",
+            "#00970016",
+            "#009f0725",
+            "#00930536",
+            "#008f0a4d",
+            "#018b0f6b",
+            "#008d199a",
+            "#008619b9",
+            "#007b17c1",
+            "#006514d5",
+            "#002006df",
+        ],
+        dark: [
+            "#0e1511ff",
+            "#141a15ff",
+            "#1b2a1eff",
+            "#1d3a24ff",
+            "#25482dff",
+            "#2d5736ff",
+            "#366740ff",
+            "#3e7949ff",
+            "#46a758ff",
+            "#53b365ff",
+            "#71d083ff",
+            "#c2f0c2ff",
+        ],
+        dark_alpha: [
+            "#00de1205",
+            "#5ef7780a",
+            "#70fe8c1b",
+            "#57ff802c",
+            "#68ff8b3b",
+            "#71ff8f4b",
+            "#77fd925d",
+            "#77fd9070",
+            "#65ff82a1",
+            "#72ff8dae",
+            "#89ff9fcd",
+            "#ceffceef",
+        ],
+    }
+}
+
+fn lime() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Lime,
+        light: [
+            "#fcfdfaff",
+            "#f8faf3ff",
+            "#eef6d6ff",
+            "#e2f0bdff",
+            "#d3e7a6ff",
+            "#c2da91ff",
+            "#abc978ff",
+            "#8db654ff",
+            "#bdee63ff",
+            "#b0e64cff",
+            "#5c7c2fff",
+            "#37401cff",
+        ],
+        light_alpha: [
+            "#66990005",
+            "#6b95000c",
+            "#96c80029",
+            "#8fc60042",
+            "#81bb0059",
+            "#72aa006e",
+            "#61990087",
+            "#559200ab",
+            "#93e4009c",
+            "#8fdc00b3",
+            "#375f00d0",
+            "#1e2900e3",
+        ],
+        dark: [
+            "#11130cff",
+            "#151a10ff",
+            "#1f2917ff",
+            "#29371dff",
+            "#334423ff",
+            "#3d522aff",
+            "#496231ff",
+            "#577538ff",
+            "#bdee63ff",
+            "#d4ff70ff",
+            "#bde56cff",
+            "#e3f7baff",
+        ],
+        dark_alpha: [
+            "#11bb0003",
+            "#78f7000a",
+            "#9bfd4c1a",
+            "#a7fe5c29",
+            "#affe6537",
+            "#b2fe6d46",
+            "#b6ff6f57",
+            "#b6fd6d6c",
+            "#caff69ed",
+            "#d4ff70ff",
+            "#d1fe77e4",
+            "#e9febff7",
+        ],
+    }
+}
+
+fn mint() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Mint,
+        light: [
+            "#f9fefdff",
+            "#f2fbf9ff",
+            "#ddf9f2ff",
+            "#c8f4e9ff",
+            "#b3ecdeff",
+            "#9ce0d0ff",
+            "#7ecfbdff",
+            "#4cbba5ff",
+            "#86ead4ff",
+            "#7de0cbff",
+            "#027864ff",
+            "#16433cff",
+        ],
+        light_alpha: [
+            "#00d5aa06",
+            "#00b18a0d",
+            "#00d29e22",
+            "#00cc9937",
+            "#00c0914c",
+            "#00b08663",
+            "#00a17d81",
+            "#009e7fb3",
+            "#00d3a579",
+            "#00c39982",
+            "#007763fd",
+            "#00312ae9",
+        ],
+        dark: [
+            "#0e1515ff",
+            "#0f1b1bff",
+            "#092c2bff",
+            "#003a38ff",
+            "#004744ff",
+            "#105650ff",
+            "#1e685fff",
+            "#277f70ff",
+            "#86ead4ff",
+            "#a8f5e5ff",
+            "#58d5baff",
+            "#c4f5e1ff",
+        ],
+        dark_alpha: [
+            "#00dede05",
+            "#00f9f90b",
+            "#00fff61d",
+            "#00fff42c",
+            "#00fff23a",
+            "#0effeb4a",
+            "#34fde55e",
+            "#41ffdf76",
+            "#92ffe7e9",
+            "#aefeedf5",
+            "#67ffded2",
+            "#cbfee9f5",
+        ],
+    }
+}
+
+fn sky() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Sky,
+        light: [
+            "#f9feffff",
+            "#f1fafdff",
+            "#e1f6fdff",
+            "#d1f0faff",
+            "#bee7f5ff",
+            "#a9daedff",
+            "#8dcae3ff",
+            "#60b3d7ff",
+            "#7ce2feff",
+            "#74daf8ff",
+            "#00749eff",
+            "#1d3e56ff",
+        ],
+        light_alpha: [
+            "#00d5ff06",
+            "#00a4db0e",
+            "#00b3ee1e",
+            "#00ace42e",
+            "#00a1d841",
+            "#0092ca56",
+            "#0089c172",
+            "#0085bf9f",
+            "#00c7fe83",
+            "#00bcf38b",
+            "#00749eff",
+            "#002540e2",
+        ],
+        dark: [
+            "#0d141fff",
+            "#111a27ff",
+            "#112840ff",
+            "#113555ff",
+            "#154467ff",
+            "#1b537bff",
+            "#1f6692ff",
+            "#197caeff",
+            "#7ce2feff",
+            "#a8eeffff",
+            "#75c7f0ff",
+            "#c2f3ffff",
+        ],
+        dark_alpha: [
+            "#0044ff0f",
+            "#1171fb18",
+            "#1184fc33",
+            "#128fff49",
+            "#1c9dfd5d",
+            "#28a5ff72",
+            "#2badfe8b",
+            "#1db2fea9",
+            "#7ce3fffe",
+            "#a8eeffff",
+            "#7cd3ffef",
+            "#c2f3ffff",
+        ],
+    }
+}
+
+fn black() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::Black,
+        light: [
+            "#0000000d",
+            "#0000001a",
+            "#00000026",
+            "#00000033",
+            "#0000004d",
+            "#00000066",
+            "#00000080",
+            "#00000099",
+            "#000000b3",
+            "#000000cc",
+            "#000000e6",
+            "#000000f2",
+        ],
+        light_alpha: [
+            "#0000000d",
+            "#0000001a",
+            "#00000026",
+            "#00000033",
+            "#0000004d",
+            "#00000066",
+            "#00000080",
+            "#00000099",
+            "#000000b3",
+            "#000000cc",
+            "#000000e6",
+            "#000000f2",
+        ],
+        dark: [
+            "#0000000d",
+            "#0000001a",
+            "#00000026",
+            "#00000033",
+            "#0000004d",
+            "#00000066",
+            "#00000080",
+            "#00000099",
+            "#000000b3",
+            "#000000cc",
+            "#000000e6",
+            "#000000f2",
+        ],
+        dark_alpha: [
+            "#0000000d",
+            "#0000001a",
+            "#00000026",
+            "#00000033",
+            "#0000004d",
+            "#00000066",
+            "#00000080",
+            "#00000099",
+            "#000000b3",
+            "#000000cc",
+            "#000000e6",
+            "#000000f2",
+        ],
+    }
+}
+
+fn white() -> DefaultColorScaleSet {
+    DefaultColorScaleSet {
+        scale: ColorScaleName::White,
+        light: [
+            "#ffffff0d",
+            "#ffffff1a",
+            "#ffffff26",
+            "#ffffff33",
+            "#ffffff4d",
+            "#ffffff66",
+            "#ffffff80",
+            "#ffffff99",
+            "#ffffffb3",
+            "#ffffffcc",
+            "#ffffffe6",
+            "#fffffff2",
+        ],
+        light_alpha: [
+            "#ffffff0d",
+            "#ffffff1a",
+            "#ffffff26",
+            "#ffffff33",
+            "#ffffff4d",
+            "#ffffff66",
+            "#ffffff80",
+            "#ffffff99",
+            "#ffffffb3",
+            "#ffffffcc",
+            "#ffffffe6",
+            "#fffffff2",
+        ],
+        dark: [
+            "#ffffff0d",
+            "#ffffff1a",
+            "#ffffff26",
+            "#ffffff33",
+            "#ffffff4d",
+            "#ffffff66",
+            "#ffffff80",
+            "#ffffff99",
+            "#ffffffb3",
+            "#ffffffcc",
+            "#ffffffe6",
+            "#fffffff2",
+        ],
+        dark_alpha: [
+            "#ffffff0d",
+            "#ffffff1a",
+            "#ffffff26",
+            "#ffffff33",
+            "#ffffff4d",
+            "#ffffff66",
+            "#ffffff80",
+            "#ffffff99",
+            "#ffffffb3",
+            "#ffffffcc",
+            "#ffffffe6",
+            "#fffffff2",
+        ],
+    }
+}

crates/theme2/src/registry.rs 🔗

@@ -1,7 +1,4 @@
-use crate::{
-    themes::{one_dark, rose_pine, rose_pine_dawn, rose_pine_moon, sandcastle},
-    Theme, ThemeMetadata,
-};
+use crate::{themes, Theme, ThemeMetadata};
 use anyhow::{anyhow, Result};
 use gpui2::SharedString;
 use std::{collections::HashMap, sync::Arc};
@@ -41,11 +38,45 @@ impl Default for ThemeRegistry {
         };
 
         this.insert_themes([
-            one_dark(),
-            rose_pine(),
-            rose_pine_dawn(),
-            rose_pine_moon(),
-            sandcastle(),
+            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

crates/theme2/src/scale.rs 🔗

@@ -0,0 +1,164 @@
+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,
+}
+
+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",
+            }
+        )
+    }
+}
+
+pub type ColorScale = [Hsla; 12];
+
+pub type ColorScales = IndexMap<ColorScaleName, ColorScaleSet>;
+
+/// A one-based step in a [`ColorScale`].
+pub type ColorScaleStep = usize;
+
+pub struct ColorScaleSet {
+    name: ColorScaleName,
+    light: ColorScale,
+    dark: ColorScale,
+    light_alpha: ColorScale,
+    dark_alpha: ColorScale,
+}
+
+impl ColorScaleSet {
+    pub fn new(
+        name: ColorScaleName,
+        light: ColorScale,
+        light_alpha: ColorScale,
+        dark: ColorScale,
+        dark_alpha: ColorScale,
+    ) -> Self {
+        Self {
+            name,
+            light,
+            light_alpha,
+            dark,
+            dark_alpha,
+        }
+    }
+
+    pub fn name(&self) -> String {
+        self.name.to_string()
+    }
+
+    pub fn light(&self, step: ColorScaleStep) -> Hsla {
+        self.light[step - 1]
+    }
+
+    pub fn light_alpha(&self, step: ColorScaleStep) -> Hsla {
+        self.light_alpha[step - 1]
+    }
+
+    pub fn dark(&self, step: ColorScaleStep) -> Hsla {
+        self.dark[step - 1]
+    }
+
+    pub fn dark_alpha(&self, step: ColorScaleStep) -> Hsla {
+        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 {
+            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 {
+            Appearance::Light => self.light_alpha(step),
+            Appearance::Dark => self.dark_alpha(step),
+        }
+    }
+}

crates/theme2/src/theme2.rs 🔗

@@ -1,14 +1,24 @@
+mod default;
 mod registry;
+mod scale;
 mod settings;
 mod themes;
 
+pub use default::*;
 pub use registry::*;
+pub use scale::*;
 pub use settings::*;
 
 use gpui2::{AppContext, HighlightStyle, Hsla, SharedString};
 use settings2::Settings;
 use std::sync::Arc;
 
+#[derive(Debug, Clone, PartialEq)]
+pub enum Appearance {
+    Light,
+    Dark,
+}
+
 pub fn init(cx: &mut AppContext) {
     cx.set_global(ThemeRegistry::default());
     ThemeSettings::register(cx);
@@ -18,6 +28,10 @@ pub fn active_theme<'a>(cx: &'a AppContext) -> &'a Arc<Theme> {
     &ThemeSettings::get_global(cx).active_theme
 }
 
+pub fn theme(cx: &AppContext) -> Arc<Theme> {
+    active_theme(cx).clone()
+}
+
 pub struct Theme {
     pub metadata: ThemeMetadata,
 

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

@@ -0,0 +1,130 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,136 @@
+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 🔗

@@ -0,0 +1,130 @@
+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 🔗

@@ -0,0 +1,130 @@
+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 🔗

@@ -0,0 +1,130 @@
+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 🔗

@@ -0,0 +1,131 @@
+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 🔗

@@ -0,0 +1,131 @@
+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 🔗

@@ -0,0 +1,131 @@
+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 🔗

@@ -0,0 +1,131 @@
+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 🔗

@@ -0,0 +1,131 @@
+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 🔗

@@ -0,0 +1,131 @@
+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,7 +1,79 @@
+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 🔗

@@ -37,45 +37,45 @@ pub fn one_dark() -> Theme {
         icon_muted: rgba(0x838994ff).into(),
         syntax: SyntaxTheme {
             highlights: vec![
-                ("link_uri".into(), rgba(0x6eb4bfff).into()),
-                ("number".into(), rgba(0xbf956aff).into()),
-                ("property".into(), rgba(0xd07277ff).into()),
-                ("boolean".into(), rgba(0xbf956aff).into()),
-                ("label".into(), rgba(0x74ade8ff).into()),
-                ("punctuation.list_marker".into(), rgba(0xd07277ff).into()),
                 ("keyword".into(), rgba(0xb477cfff).into()),
-                ("punctuation.delimiter".into(), rgba(0xb2b9c6ff).into()),
-                ("string.special".into(), rgba(0xbf956aff).into()),
-                ("constant".into(), rgba(0xdfc184ff).into()),
-                ("punctuation".into(), rgba(0xacb2beff).into()),
-                ("variable.special".into(), rgba(0xbf956aff).into()),
-                ("preproc".into(), rgba(0xc8ccd4ff).into()),
-                ("enum".into(), rgba(0xd07277ff).into()),
-                ("attribute".into(), rgba(0x74ade8ff).into()),
-                ("emphasis.strong".into(), rgba(0xbf956aff).into()),
-                ("title".into(), rgba(0xd07277ff).into()),
-                ("hint".into(), rgba(0x5a6f89ff).into()),
-                ("emphasis".into(), rgba(0x74ade8ff).into()),
-                ("string.regex".into(), rgba(0xbf956aff).into()),
-                ("link_text".into(), rgba(0x73ade9ff).into()),
-                ("string".into(), rgba(0xa1c181ff).into()),
                 ("comment.doc".into(), rgba(0x878e98ff).into()),
-                ("punctuation.special".into(), rgba(0xb1574bff).into()),
-                ("primary".into(), rgba(0xacb2beff).into()),
-                ("operator".into(), rgba(0x6eb4bfff).into()),
+                ("variant".into(), rgba(0x73ade9ff).into()),
+                ("property".into(), rgba(0xd07277ff).into()),
                 ("function".into(), rgba(0x73ade9ff).into()),
-                ("string.special.symbol".into(), rgba(0xbf956aff).into()),
                 ("type".into(), rgba(0x6eb4bfff).into()),
-                ("variant".into(), rgba(0x73ade9ff).into()),
                 ("tag".into(), rgba(0x74ade8ff).into()),
-                ("punctuation.bracket".into(), rgba(0xb2b9c6ff).into()),
-                ("embedded".into(), rgba(0xc8ccd4ff).into()),
                 ("string.escape".into(), rgba(0x878e98ff).into()),
-                ("variable".into(), rgba(0xc8ccd4ff).into()),
-                ("predictive".into(), rgba(0x5a6a87ff).into()),
+                ("punctuation.bracket".into(), rgba(0xb2b9c6ff).into()),
+                ("hint".into(), rgba(0x5a6f89ff).into()),
+                ("punctuation".into(), rgba(0xacb2beff).into()),
                 ("comment".into(), rgba(0x5d636fff).into()),
-                ("text.literal".into(), rgba(0xa1c181ff).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(),

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

@@ -0,0 +1,131 @@
+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 🔗

@@ -37,46 +37,46 @@ pub fn rose_pine() -> Theme {
         icon_muted: rgba(0x74708dff).into(),
         syntax: SyntaxTheme {
             highlights: vec![
-                ("text.literal".into(), rgba(0xc4a7e6ff).into()),
-                ("string".into(), rgba(0xf5c177ff).into()),
-                ("enum".into(), rgba(0xc4a7e6ff).into()),
-                ("number".into(), rgba(0x5cc1a3ff).into()),
-                ("attribute".into(), rgba(0x9bced6ff).into()),
-                ("property".into(), rgba(0x9bced6ff).into()),
-                ("function".into(), rgba(0xebbcbaff).into()),
-                ("embedded".into(), rgba(0xe0def4ff).into()),
                 ("punctuation.delimiter".into(), rgba(0x9d99b6ff).into()),
-                ("variant".into(), rgba(0x9bced6ff).into()),
-                ("operator".into(), rgba(0x30738fff).into()),
-                ("comment".into(), rgba(0x6e6a86ff).into()),
-                ("type.builtin".into(), rgba(0x9ccfd8ff).into()),
-                ("label".into(), rgba(0x9bced6ff).into()),
+                ("number".into(), rgba(0x5cc1a3ff).into()),
+                ("punctuation.special".into(), rgba(0x9d99b6ff).into()),
                 ("string.escape".into(), rgba(0x76728fff).into()),
-                ("type".into(), rgba(0x9ccfd8ff).into()),
-                ("constructor".into(), rgba(0x9bced6ff).into()),
-                ("punctuation.bracket".into(), rgba(0x9d99b6ff).into()),
-                ("function.method".into(), rgba(0xebbcbaff).into()),
-                ("tag".into(), rgba(0x9ccfd8ff).into()),
-                ("link_text".into(), rgba(0x9ccfd8ff).into()),
-                ("string.special".into(), rgba(0xc4a7e6ff).into()),
+                ("title".into(), rgba(0xf5c177ff).into()),
+                ("constant".into(), rgba(0x5cc1a3ff).into()),
                 ("string.regex".into(), rgba(0xc4a7e6ff).into()),
-                ("preproc".into(), rgba(0xe0def4ff).into()),
-                ("emphasis.strong".into(), rgba(0x9bced6ff).into()),
-                ("emphasis".into(), rgba(0x9bced6ff).into()),
+                ("type.builtin".into(), rgba(0x9ccfd8ff).into()),
                 ("comment.doc".into(), rgba(0x76728fff).into()),
-                ("boolean".into(), rgba(0xebbcbaff).into()),
-                ("punctuation.list_marker".into(), rgba(0x9d99b6ff).into()),
-                ("hint".into(), rgba(0x5e768cff).into()),
-                ("title".into(), rgba(0xf5c177ff).into()),
-                ("variable".into(), rgba(0xe0def4ff).into()),
-                ("string.special.symbol".into(), rgba(0xc4a7e6ff).into()),
                 ("primary".into(), rgba(0xe0def4ff).into()),
-                ("predictive".into(), rgba(0x556b81ff).into()),
+                ("string.special".into(), rgba(0xc4a7e6ff).into()),
                 ("punctuation".into(), rgba(0x908caaff).into()),
-                ("constant".into(), rgba(0x5cc1a3ff).into()),
-                ("punctuation.special".into(), rgba(0x9d99b6ff).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(),
@@ -130,261 +130,3 @@ pub fn rose_pine() -> Theme {
         ],
     }
 }
-
-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![
-                ("type".into(), rgba(0x55949fff).into()),
-                ("keyword".into(), rgba(0x276983ff).into()),
-                ("link_text".into(), rgba(0x55949fff).into()),
-                ("embedded".into(), rgba(0x575279ff).into()),
-                ("type.builtin".into(), rgba(0x55949fff).into()),
-                ("punctuation.delimiter".into(), rgba(0x635e82ff).into()),
-                ("text.literal".into(), rgba(0x9079a9ff).into()),
-                ("variant".into(), rgba(0x57949fff).into()),
-                ("string".into(), rgba(0xea9d34ff).into()),
-                ("hint".into(), rgba(0x7a92aaff).into()),
-                ("punctuation.special".into(), rgba(0x635e82ff).into()),
-                ("string.special".into(), rgba(0x9079a9ff).into()),
-                ("string.regex".into(), rgba(0x9079a9ff).into()),
-                ("operator".into(), rgba(0x276983ff).into()),
-                ("boolean".into(), rgba(0xd7827dff).into()),
-                ("constructor".into(), rgba(0x57949fff).into()),
-                ("punctuation".into(), rgba(0x797593ff).into()),
-                ("label".into(), rgba(0x57949fff).into()),
-                ("variable".into(), rgba(0x575279ff).into()),
-                ("tag".into(), rgba(0x55949fff).into()),
-                ("primary".into(), rgba(0x575279ff).into()),
-                ("link_uri".into(), rgba(0xd7827dff).into()),
-                ("punctuation.list_marker".into(), rgba(0x635e82ff).into()),
-                ("string.escape".into(), rgba(0x6e6a8bff).into()),
-                ("punctuation.bracket".into(), rgba(0x635e82ff).into()),
-                ("function".into(), rgba(0xd7827dff).into()),
-                ("preproc".into(), rgba(0x575279ff).into()),
-                ("function.method".into(), rgba(0xd7827dff).into()),
-                ("predictive".into(), rgba(0xa2acbeff).into()),
-                ("comment.doc".into(), rgba(0x6e6a8bff).into()),
-                ("comment".into(), rgba(0x9893a5ff).into()),
-                ("number".into(), rgba(0x3daa8eff).into()),
-                ("emphasis".into(), rgba(0x57949fff).into()),
-                ("title".into(), rgba(0xea9d34ff).into()),
-                ("enum".into(), rgba(0x9079a9ff).into()),
-                ("string.special.symbol".into(), rgba(0x9079a9ff).into()),
-                ("constant".into(), rgba(0x3daa8eff).into()),
-                ("emphasis.strong".into(), rgba(0x57949fff).into()),
-                ("property".into(), rgba(0x57949fff).into()),
-                ("attribute".into(), rgba(0x57949fff).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(),
-            },
-        ],
-    }
-}
-
-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![
-                ("embedded".into(), rgba(0xe0def4ff).into()),
-                ("link_uri".into(), rgba(0xea9a97ff).into()),
-                ("primary".into(), rgba(0xe0def4ff).into()),
-                ("punctuation.delimiter".into(), rgba(0xaeabc6ff).into()),
-                ("string.escape".into(), rgba(0x8682a0ff).into()),
-                ("attribute".into(), rgba(0x9bced6ff).into()),
-                ("constant".into(), rgba(0x5cc1a3ff).into()),
-                ("keyword".into(), rgba(0x3d8fb0ff).into()),
-                ("predictive".into(), rgba(0x516b83ff).into()),
-                ("label".into(), rgba(0x9bced6ff).into()),
-                ("comment.doc".into(), rgba(0x8682a0ff).into()),
-                ("emphasis".into(), rgba(0x9bced6ff).into()),
-                ("string".into(), rgba(0xf5c177ff).into()),
-                ("type".into(), rgba(0x9ccfd8ff).into()),
-                ("string.special".into(), rgba(0xc4a7e6ff).into()),
-                ("function".into(), rgba(0xea9a97ff).into()),
-                ("constructor".into(), rgba(0x9bced6ff).into()),
-                ("comment".into(), rgba(0x6e6a86ff).into()),
-                ("preproc".into(), rgba(0xe0def4ff).into()),
-                ("enum".into(), rgba(0xc4a7e6ff).into()),
-                ("punctuation.bracket".into(), rgba(0xaeabc6ff).into()),
-                ("number".into(), rgba(0x5cc1a3ff).into()),
-                ("hint".into(), rgba(0x728aa2ff).into()),
-                ("variant".into(), rgba(0x9bced6ff).into()),
-                ("link_text".into(), rgba(0x9ccfd8ff).into()),
-                ("property".into(), rgba(0x9bced6ff).into()),
-                ("punctuation.list_marker".into(), rgba(0xaeabc6ff).into()),
-                ("operator".into(), rgba(0x3d8fb0ff).into()),
-                ("title".into(), rgba(0xf5c177ff).into()),
-                ("punctuation".into(), rgba(0x908caaff).into()),
-                ("string.regex".into(), rgba(0xc4a7e6ff).into()),
-                ("tag".into(), rgba(0x9ccfd8ff).into()),
-                ("emphasis.strong".into(), rgba(0x9bced6ff).into()),
-                ("text.literal".into(), rgba(0xc4a7e6ff).into()),
-                ("punctuation.special".into(), rgba(0xaeabc6ff).into()),
-                ("boolean".into(), rgba(0xea9a97ff).into()),
-                ("type.builtin".into(), rgba(0x9ccfd8ff).into()),
-                ("function.method".into(), rgba(0xea9a97ff).into()),
-                ("variable".into(), rgba(0xe0def4ff).into()),
-                ("string.special.symbol".into(), rgba(0xc4a7e6ff).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/rose_pine_dawn.rs 🔗

@@ -0,0 +1,132 @@
+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 🔗

@@ -0,0 +1,132 @@
+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 🔗

@@ -37,44 +37,44 @@ pub fn sandcastle() -> Theme {
         icon_muted: rgba(0xa69782ff).into(),
         syntax: SyntaxTheme {
             highlights: vec![
-                ("string.special.symbol".into(), rgba(0xa07d3aff).into()),
-                ("enum".into(), rgba(0xa07d3aff).into()),
+                ("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()),
-                ("punctuation.delimiter".into(), rgba(0xd5c5a1ff).into()),
-                ("comment".into(), rgba(0xa89984ff).into()),
-                ("embedded".into(), rgba(0xfdf4c1ff).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()),
-                ("string.escape".into(), rgba(0xa89984ff).into()),
-                ("comment.doc".into(), rgba(0xa89984ff).into()),
-                ("variant".into(), rgba(0x518b8bff).into()),
+                ("punctuation.special".into(), rgba(0xd5c5a1ff).into()),
+                ("keyword".into(), rgba(0x518b8bff).into()),
+                ("constructor".into(), rgba(0x518b8bff).into()),
                 ("predictive".into(), rgba(0x5c6152ff).into()),
-                ("link_text".into(), rgba(0xa07d3aff).into()),
-                ("attribute".into(), rgba(0x518b8bff).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()),
-                ("punctuation.special".into(), rgba(0xd5c5a1ff).into()),
-                ("string.special".into(), rgba(0xa07d3aff).into()),
-                ("string.regex".into(), rgba(0xa07d3aff).into()),
-                ("tag".into(), rgba(0x518b8bff).into()),
-                ("keyword".into(), rgba(0x518b8bff).into()),
-                ("type".into(), rgba(0x83a598ff).into()),
-                ("text.literal".into(), rgba(0xa07d3aff).into()),
-                ("link_uri".into(), rgba(0x83a598ff).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()),
-                ("number".into(), rgba(0x83a598ff).into()),
-                ("constructor".into(), rgba(0x518b8bff).into()),
-                ("preproc".into(), rgba(0xfdf4c1ff).into()),
-                ("emphasis".into(), rgba(0x518b8bff).into()),
-                ("variable".into(), rgba(0xfdf4c1ff).into()),
-                ("operator".into(), rgba(0xa07d3aff).into()),
-                ("punctuation".into(), rgba(0xd5c5a1ff).into()),
-                ("constant".into(), rgba(0x83a598ff).into()),
+                ("variant".into(), rgba(0x518b8bff).into()),
             ],
         },
         status_bar: rgba(0x333944ff).into(),

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

@@ -0,0 +1,130 @@
+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 🔗

@@ -0,0 +1,130 @@
+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 🔗

@@ -0,0 +1,130 @@
+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 🔗

@@ -9,6 +9,7 @@ publish = false
 [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

crates/theme_converter/src/main.rs 🔗

@@ -1,16 +1,25 @@
+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 gpui2::{hsla, rgb, serde_json, AssetSource, Hsla, Rgba, SharedString};
+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, ThemeMetadata};
+use theme2::{PlayerTheme, SyntaxTheme};
+
+use crate::theme_printer::ThemePrinter;
 
 #[derive(Parser)]
 #[command(author, version, about, long_about = None)]
@@ -22,13 +31,71 @@ struct Args {
 fn main() -> Result<()> {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
-    let args = Args::parse();
+    // 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;
 
-    let (json_theme, legacy_theme) = load_theme(args.theme)?;
+                use crate::{{PlayerTheme, SyntaxTheme, Theme, ThemeMetadata}};
 
-    let theme = convert_theme(json_theme, legacy_theme)?;
+                pub fn {theme_slug}() -> Theme {{
+                    {theme_definition}
+                }}
+            "#,
+            theme_definition = format!("{:#?}", ThemePrinter::new(theme))
+        );
 
-    println!("{:#?}", ThemePrinter(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(())
 }
@@ -184,9 +251,9 @@ struct JsonSyntaxStyle {
 }
 
 /// Loads the [`Theme`] with the given name.
-fn load_theme(name: String) -> Result<(JsonTheme, LegacyTheme)> {
-    let theme_contents = Assets::get(&format!("themes/{name}.json"))
-        .with_context(|| format!("theme file not found: '{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")?;
@@ -321,167 +388,3 @@ where
     }
     deserializer.deserialize_map(SyntaxVisitor)
 }
-
-pub struct ThemePrinter(theme2::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/theme_converter/src/theme_printer.rs 🔗

@@ -0,0 +1,174 @@
+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/src/components/assistant_panel.rs 🔗

@@ -1,7 +1,6 @@
-use gpui2::{rems, AbsoluteLength};
-
 use crate::prelude::*;
 use crate::{Icon, IconButton, Label, Panel, PanelSide};
+use gpui2::{rems, AbsoluteLength};
 
 #[derive(Component)]
 pub struct AssistantPanel {
@@ -76,15 +75,15 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
-
-    #[derive(Component)]
+    use crate::Story;
+    use gpui2::{Div, Render};
     pub struct AssistantPanelStory;
 
-    impl AssistantPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for AssistantPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, AssistantPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -73,21 +73,17 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use std::str::FromStr;
-
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::Render;
+    use std::str::FromStr;
 
-    #[derive(Component)]
     pub struct BreadcrumbStory;
 
-    impl BreadcrumbStory {
-        fn render<V: 'static>(
-            self,
-            view_state: &mut V,
-            cx: &mut ViewContext<V>,
-        ) -> impl Component<V> {
+    impl Render for BreadcrumbStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -233,20 +233,19 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::rems;
-
+    use super::*;
     use crate::{
         empty_buffer_example, hello_world_rust_buffer_example,
         hello_world_rust_buffer_with_status_example, Story,
     };
+    use gpui2::{rems, Div, Render};
 
-    use super::*;
-
-    #[derive(Component)]
     pub struct BufferStory;
 
-    impl BufferStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for BufferStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -1,4 +1,4 @@
-use gpui2::{AppContext, Context, View};
+use gpui2::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::{h_stack, Icon, IconButton, IconColor, Input};
@@ -21,15 +21,15 @@ impl BufferSearch {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.entity(|cx| Self::new());
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self::new())
     }
+}
+
+impl Render for BufferSearch {
+    type Element = Div<Self>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
         let theme = theme(cx);
 
         h_stack().bg(theme.toolbar).p_2().child(

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

@@ -108,16 +108,18 @@ pub use stories::*;
 #[cfg(feature = "stories")]
 mod stories {
     use chrono::DateTime;
+    use gpui2::{Div, Render};
 
     use crate::{Panel, Story};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ChatPanelStory;
 
-    impl ChatPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ChatPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ChatPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -89,15 +89,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct CollabPanelStory;
 
-    impl CollabPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for CollabPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, CollabPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -27,15 +27,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct CommandPaletteStory;
 
-    impl CommandPaletteStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for CommandPaletteStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, CommandPalette>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -68,15 +68,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::story::Story;
-
     use super::*;
+    use crate::story::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct ContextMenuStory;
 
-    impl ContextMenuStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ContextMenuStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ContextMenu>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -25,15 +25,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct CopilotModalStory;
 
-    impl CopilotModalStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for CopilotModalStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, CopilotModal>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,6 +1,6 @@
 use std::path::PathBuf;
 
-use gpui2::{AppContext, Context, View};
+use gpui2::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::{
@@ -20,7 +20,7 @@ pub struct EditorPane {
 
 impl EditorPane {
     pub fn new(
-        cx: &mut AppContext,
+        cx: &mut ViewContext<Self>,
         tabs: Vec<Tab>,
         path: PathBuf,
         symbols: Vec<Symbol>,
@@ -42,15 +42,15 @@ impl EditorPane {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.entity(|cx| hello_world_rust_editor_with_status_example(cx));
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| hello_world_rust_editor_with_status_example(cx))
     }
+}
+
+impl Render for EditorPane {
+    type Element = Div<Self>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
         v_stack()
             .w_full()
             .h_full()

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

@@ -31,15 +31,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{static_players, Story};
-
     use super::*;
+    use crate::{static_players, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct FacepileStory;
 
-    impl FacepileStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for FacepileStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let players = static_players();
 
             Story::container(cx)

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

@@ -158,17 +158,17 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use itertools::Itertools;
-
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
+    use itertools::Itertools;
 
-    #[derive(Component)]
     pub struct KeybindingStory;
 
-    impl KeybindingStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for KeybindingStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let all_modifier_permutations = ModifierKey::iter().permutations(2);
 
             Story::container(cx)

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

@@ -38,15 +38,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct LanguageSelectorStory;
 
-    impl LanguageSelectorStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for LanguageSelectorStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, LanguageSelector>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -40,15 +40,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{hello_world_rust_buffer_example, Story};
-
     use super::*;
+    use crate::{hello_world_rust_buffer_example, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct MultiBufferStory;
 
-    impl MultiBufferStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for MultiBufferStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -48,15 +48,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Panel, Story};
-
     use super::*;
+    use crate::{Panel, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct NotificationsPanelStory;
 
-    impl NotificationsPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for NotificationsPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, NotificationsPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -152,58 +152,71 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::{ModifierKeys, Story};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct PaletteStory;
 
-    impl PaletteStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-            Story::container(cx)
-                .child(Story::title_for::<_, Palette>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Palette::new("palette-1"))
-                .child(Story::label(cx, "With Items"))
-                .child(
-                    Palette::new("palette-2")
-                        .placeholder("Execute a command...")
-                        .items(vec![
-                            PaletteItem::new("theme selector: toggle").keybinding(
-                                Keybinding::new_chord(
-                                    ("k".to_string(), ModifierKeys::new().command(true)),
-                                    ("t".to_string(), ModifierKeys::new().command(true)),
+    impl Render for PaletteStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            {
+                Story::container(cx)
+                    .child(Story::title_for::<_, Palette>(cx))
+                    .child(Story::label(cx, "Default"))
+                    .child(Palette::new("palette-1"))
+                    .child(Story::label(cx, "With Items"))
+                    .child(
+                        Palette::new("palette-2")
+                            .placeholder("Execute a command...")
+                            .items(vec![
+                                PaletteItem::new("theme selector: toggle").keybinding(
+                                    Keybinding::new_chord(
+                                        ("k".to_string(), ModifierKeys::new().command(true)),
+                                        ("t".to_string(), ModifierKeys::new().command(true)),
+                                    ),
+                                ),
+                                PaletteItem::new("assistant: inline assist").keybinding(
+                                    Keybinding::new(
+                                        "enter".to_string(),
+                                        ModifierKeys::new().command(true),
+                                    ),
+                                ),
+                                PaletteItem::new("assistant: quote selection").keybinding(
+                                    Keybinding::new(
+                                        ">".to_string(),
+                                        ModifierKeys::new().command(true),
+                                    ),
+                                ),
+                                PaletteItem::new("assistant: toggle focus").keybinding(
+                                    Keybinding::new(
+                                        "?".to_string(),
+                                        ModifierKeys::new().command(true),
+                                    ),
                                 ),
-                            ),
-                            PaletteItem::new("assistant: inline assist").keybinding(
-                                Keybinding::new(
-                                    "enter".to_string(),
-                                    ModifierKeys::new().command(true),
+                                PaletteItem::new("auto update: check"),
+                                PaletteItem::new("auto update: view release notes"),
+                                PaletteItem::new("branches: open recent").keybinding(
+                                    Keybinding::new(
+                                        "b".to_string(),
+                                        ModifierKeys::new().command(true).alt(true),
+                                    ),
                                 ),
-                            ),
-                            PaletteItem::new("assistant: quote selection").keybinding(
-                                Keybinding::new(">".to_string(), ModifierKeys::new().command(true)),
-                            ),
-                            PaletteItem::new("assistant: toggle focus").keybinding(
-                                Keybinding::new("?".to_string(), ModifierKeys::new().command(true)),
-                            ),
-                            PaletteItem::new("auto update: check"),
-                            PaletteItem::new("auto update: view release notes"),
-                            PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
-                                "b".to_string(),
-                                ModifierKeys::new().command(true).alt(true),
-                            )),
-                            PaletteItem::new("chat panel: toggle focus"),
-                            PaletteItem::new("cli: install"),
-                            PaletteItem::new("client: sign in"),
-                            PaletteItem::new("client: sign out"),
-                            PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
-                                "escape".to_string(),
-                                ModifierKeys::new(),
-                            )),
-                        ]),
-                )
+                                PaletteItem::new("chat panel: toggle focus"),
+                                PaletteItem::new("cli: install"),
+                                PaletteItem::new("client: sign in"),
+                                PaletteItem::new("client: sign out"),
+                                PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
+                                    "escape".to_string(),
+                                    ModifierKeys::new(),
+                                )),
+                            ]),
+                    )
+            }
         }
     }
 }

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

@@ -128,17 +128,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Label, Story};
-
     use super::*;
+    use crate::{Label, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct PanelStory;
 
-    impl PanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for PanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
-                .child(Story::title_for::<_, Panel<V>>(cx))
+                .child(Story::title_for::<_, Panel<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
                     Panel::new("panel", cx).child(

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

@@ -1,4 +1,4 @@
-use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size};
+use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -18,13 +18,6 @@ pub struct Pane<V: 'static> {
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
-// impl<V: 'static> IntoAnyElement<V> for Pane<V> {
-//     fn into_any(self) -> AnyElement<V> {
-//         (move |view_state: &mut V, cx: &mut ViewContext<'_, '_, V>| self.render(view_state, cx))
-//             .into_any()
-//     }
-// }
-
 impl<V: 'static> Pane<V> {
     pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
         // Fill is only here for debugging purposes, remove before release
@@ -57,8 +50,8 @@ impl<V: 'static> Pane<V> {
                     .z_index(1)
                     .id("drag-target")
                     .drag_over::<ExternalPaths>(|d| d.bg(red()))
-                    .on_drop(|_, files: ExternalPaths, _| {
-                        dbg!("dropped files!", files);
+                    .on_drop(|_, files: View<ExternalPaths>, cx| {
+                        dbg!("dropped files!", files.read(cx));
                     })
                     .absolute()
                     .inset_0(),

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

@@ -57,15 +57,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Panel, Story};
-
     use super::*;
+    use crate::{Panel, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct ProjectPanelStory;
 
-    impl ProjectPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ProjectPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ProjectPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -34,15 +34,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct RecentProjectsStory;
 
-    impl RecentProjectsStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for RecentProjectsStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, RecentProjects>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,5 +1,6 @@
 use crate::prelude::*;
 use crate::{Icon, IconColor, IconElement, Label, LabelColor};
+use gpui2::{black, red, Div, ElementId, Render, View, VisualContext};
 
 #[derive(Component, Clone)]
 pub struct Tab {
@@ -19,6 +20,14 @@ struct TabDragState {
     title: String,
 }
 
+impl Render for TabDragState {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        div().w_8().h_4().bg(red())
+    }
+}
+
 impl Tab {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
@@ -118,12 +127,10 @@ impl Tab {
 
         div()
             .id(self.id.clone())
-            .on_drag(move |_view, _cx| {
-                Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red()))
-            })
+            .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone()))
             .drag_over::<TabDragState>(|d| d.bg(black()))
-            .on_drop(|_view, state: TabDragState, cx| {
-                dbg!(state);
+            .on_drop(|_view, state: View<TabDragState>, cx| {
+                dbg!(state.read(cx));
             })
             .px_2()
             .py_0p5()
@@ -160,23 +167,21 @@ impl Tab {
     }
 }
 
-use gpui2::{black, red, Drag, ElementId};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use strum::IntoEnumIterator;
-
-    use crate::{h_stack, v_stack, Icon, Story};
-
     use super::*;
+    use crate::{h_stack, v_stack, Icon, Story};
+    use strum::IntoEnumIterator;
 
-    #[derive(Component)]
     pub struct TabStory;
 
-    impl TabStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TabStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let git_statuses = GitStatus::iter();
             let fs_statuses = FileSystemStatus::iter();
 

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

@@ -92,15 +92,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct TabBarStory;
 
-    impl TabBarStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TabBarStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, TabBar>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -83,15 +83,15 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
-
-    #[derive(Component)]
+    use crate::Story;
+    use gpui2::{Div, Render};
     pub struct TerminalStory;
 
-    impl TerminalStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TerminalStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Terminal>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -39,15 +39,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ThemeSelectorStory;
 
-    impl ThemeSelectorStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ThemeSelectorStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ThemeSelector>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,7 +1,7 @@
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 
-use gpui2::{AppContext, Context, ModelContext, View};
+use gpui2::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::settings::user_settings;
@@ -28,7 +28,7 @@ pub struct TitleBar {
 }
 
 impl TitleBar {
-    pub fn new(cx: &mut ModelContext<Self>) -> Self {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let is_active = Arc::new(AtomicBool::new(true));
         let active = is_active.clone();
 
@@ -80,15 +80,15 @@ impl TitleBar {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext, livestream: Option<Livestream>) -> View<Self> {
-        {
-            let state = cx.entity(|cx| Self::new(cx).set_livestream(livestream));
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext, livestream: Option<Livestream>) -> View<Self> {
+        cx.build_view(|cx| Self::new(cx).set_livestream(livestream))
     }
+}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+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);
 
@@ -187,26 +187,25 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
 
     pub struct TitleBarStory {
         title_bar: View<TitleBar>,
     }
 
     impl TitleBarStory {
-        pub fn view(cx: &mut AppContext) -> View<Self> {
-            {
-                let state = cx.entity(|cx| Self {
-                    title_bar: TitleBar::view(cx, None),
-                });
-                let render = Self::render;
-                View::for_handle(state, render)
-            }
+        pub fn view(cx: &mut WindowContext) -> View<Self> {
+            cx.build_view(|cx| Self {
+                title_bar: TitleBar::view(cx, None),
+            })
         }
+    }
+
+    impl Render for TitleBarStory {
+        type Element = Div<Self>;
 
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
             Story::container(cx)
                 .child(Story::title_for::<_, TitleBar>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -72,17 +72,20 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::{Label, Story};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ToastStory;
 
-    impl ToastStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ToastStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
-                .child(Story::title_for::<_, Toast<V>>(cx))
+                .child(Story::title_for::<_, Toast<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(Toast::new(ToastOrigin::Bottom).child(Label::new("label")))
         }

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

@@ -75,19 +75,22 @@ mod stories {
     use std::path::PathBuf;
     use std::str::FromStr;
 
+    use gpui2::{Div, Render};
+
     use crate::{Breadcrumb, HighlightedText, Icon, IconButton, Story, Symbol};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ToolbarStory;
 
-    impl ToolbarStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ToolbarStory {
+        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<V>>(cx))
+                .child(Story::title_for::<_, Toolbar<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
                     Toolbar::new()

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

@@ -77,15 +77,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct TrafficLightsStory;
 
-    impl TrafficLightsStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TrafficLightsStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, TrafficLights>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,15 +1,15 @@
 use std::sync::Arc;
 
 use chrono::DateTime;
-use gpui2::{px, relative, rems, AppContext, Context, Size, View};
+use gpui2::{px, relative, rems, Div, Render, Size, View, VisualContext};
 
+use crate::{prelude::*, NotificationsPanel};
 use crate::{
-    old_theme, 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, 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,
 };
-use crate::{prelude::*, NotificationsPanel};
 
 #[derive(Clone)]
 pub struct Gpui2UiDebug {
@@ -44,7 +44,7 @@ pub struct Workspace {
 }
 
 impl Workspace {
-    pub fn new(cx: &mut AppContext) -> Self {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
         Self {
             title_bar: TitleBar::view(cx, None),
             editor_1: EditorPane::view(cx),
@@ -170,16 +170,16 @@ impl Workspace {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.entity(|cx| Self::new(cx));
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self::new(cx))
     }
+}
+
+impl Render for Workspace {
+    type Element = Div<Self>;
 
-    pub fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
-        let theme = old_theme(cx).clone();
+    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.
@@ -216,8 +216,8 @@ impl Workspace {
             .gap_0()
             .justify_start()
             .items_start()
-            .text_color(theme.lowest.base.default.foreground)
-            .bg(theme.lowest.base.default.background)
+            .text_color(theme.text)
+            .bg(theme.background)
             .child(self.title_bar.clone())
             .child(
                 div()
@@ -228,7 +228,7 @@ impl Workspace {
                     .overflow_hidden()
                     .border_t()
                     .border_b()
-                    .border_color(theme.lowest.base.default.border)
+                    .border_color(theme.border)
                     .children(
                         Some(
                             Panel::new("project-panel-outer", cx)
@@ -355,9 +355,8 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::VisualContext;
-
     use super::*;
+    use gpui2::VisualContext;
 
     pub struct WorkspaceStory {
         workspace: View<Workspace>,
@@ -365,12 +364,17 @@ mod stories {
 
     impl WorkspaceStory {
         pub fn view(cx: &mut WindowContext) -> View<Self> {
-            cx.build_view(
-                |cx| Self {
-                    workspace: Workspace::view(cx),
-                },
-                |view, cx| view.workspace.clone(),
-            )
+            cx.build_view(|cx| Self {
+                workspace: Workspace::view(cx),
+            })
+        }
+    }
+
+    impl Render for WorkspaceStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            div().child(self.workspace.clone())
         }
     }
 }

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

@@ -43,15 +43,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct AvatarStory;
 
-    impl AvatarStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for AvatarStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Avatar>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -219,22 +219,21 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::rems;
-    use strum::IntoEnumIterator;
-
-    use crate::{h_stack, v_stack, LabelColor, Story};
-
     use super::*;
+    use crate::{h_stack, v_stack, LabelColor, Story};
+    use gpui2::{rems, Div, Render};
+    use strum::IntoEnumIterator;
 
-    #[derive(Component)]
     pub struct ButtonStory;
 
-    impl ButtonStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ButtonStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let states = InteractionState::iter();
 
             Story::container(cx)
-                .child(Story::title_for::<_, Button<V>>(cx))
+                .child(Story::title_for::<_, Button<Self>>(cx))
                 .child(
                     div()
                         .flex()

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

@@ -46,17 +46,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Button, Story};
-
     use super::*;
+    use crate::{Button, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct DetailsStory;
 
-    impl DetailsStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for DetailsStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
-                .child(Story::title_for::<_, Details<V>>(cx))
+                .child(Story::title_for::<_, Details<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(Details::new("The quick brown fox jumps over the lazy dog"))
                 .child(Story::label(cx, "With meta"))

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

@@ -2,7 +2,6 @@ use gpui2::{svg, Hsla};
 use strum::EnumIter;
 
 use crate::prelude::*;
-use crate::theme::old_theme;
 
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum IconSize {
@@ -27,17 +26,17 @@ pub enum IconColor {
 
 impl IconColor {
     pub fn color(self, cx: &WindowContext) -> Hsla {
-        let theme = old_theme(cx);
+        let theme = theme(cx);
         match self {
-            IconColor::Default => theme.lowest.base.default.foreground,
-            IconColor::Muted => theme.lowest.variant.default.foreground,
-            IconColor::Disabled => theme.lowest.base.disabled.foreground,
-            IconColor::Placeholder => theme.lowest.base.disabled.foreground,
-            IconColor::Accent => theme.lowest.accent.default.foreground,
-            IconColor::Error => theme.lowest.negative.default.foreground,
-            IconColor::Warning => theme.lowest.warning.default.foreground,
-            IconColor::Success => theme.lowest.positive.default.foreground,
-            IconColor::Info => theme.lowest.accent.default.foreground,
+            IconColor::Default => gpui2::red(),
+            IconColor::Muted => gpui2::red(),
+            IconColor::Disabled => gpui2::red(),
+            IconColor::Placeholder => gpui2::red(),
+            IconColor::Accent => gpui2::red(),
+            IconColor::Error => gpui2::red(),
+            IconColor::Warning => gpui2::red(),
+            IconColor::Success => gpui2::red(),
+            IconColor::Info => gpui2::red(),
         }
     }
 }
@@ -192,17 +191,19 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
     use strum::IntoEnumIterator;
 
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct IconStory;
 
-    impl IconStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for IconStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let icons = Icon::iter();
 
             Story::container(cx)

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

@@ -112,15 +112,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct InputStory;
 
-    impl InputStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for InputStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Input>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -2,8 +2,6 @@ use gpui2::{relative, Hsla, WindowContext};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
-use crate::theme::old_theme;
-
 #[derive(Default, PartialEq, Copy, Clone)]
 pub enum LabelColor {
     #[default]
@@ -21,19 +19,17 @@ pub enum LabelColor {
 impl LabelColor {
     pub fn hsla(&self, cx: &WindowContext) -> Hsla {
         let theme = theme(cx);
-        // TODO: Remove
-        let old_theme = old_theme(cx);
 
         match self {
             Self::Default => theme.text,
             Self::Muted => theme.text_muted,
-            Self::Created => old_theme.middle.positive.default.foreground,
-            Self::Modified => old_theme.middle.warning.default.foreground,
-            Self::Deleted => old_theme.middle.negative.default.foreground,
+            Self::Created => gpui2::red(),
+            Self::Modified => gpui2::red(),
+            Self::Deleted => gpui2::red(),
             Self::Disabled => theme.text_disabled,
-            Self::Hidden => old_theme.middle.variant.default.foreground,
+            Self::Hidden => gpui2::red(),
             Self::Placeholder => theme.text_placeholder,
-            Self::Accent => old_theme.middle.accent.default.foreground,
+            Self::Accent => gpui2::red(),
         }
     }
 }
@@ -201,15 +197,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct LabelStory;
 
-    impl LabelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for LabelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Label>(cx))
                 .child(Story::label(cx, "Default"))

crates/ui2/src/lib.rs 🔗

@@ -23,7 +23,6 @@ mod elevation;
 pub mod prelude;
 pub mod settings;
 mod static_data;
-mod theme;
 
 pub use components::*;
 pub use elements::*;
@@ -38,7 +37,6 @@ pub use static_data::*;
 // AFAICT this is something to do with conflicting names between crates and modules that
 // interfaces with declaring the `ClassDecl`.
 pub use crate::settings::*;
-pub use crate::theme::*;
 
 #[cfg(feature = "stories")]
 mod story;

crates/ui2/src/prelude.rs 🔗

@@ -5,7 +5,8 @@ pub use gpui2::{
 
 pub use crate::elevation::*;
 use crate::settings::user_settings;
-pub use crate::{old_theme, theme, ButtonVariant, Theme};
+pub use crate::ButtonVariant;
+pub use theme2::theme;
 
 use gpui2::{rems, Hsla, Rems};
 use strum::EnumIter;

crates/ui2/src/static_data.rs 🔗

@@ -1,7 +1,7 @@
 use std::path::PathBuf;
 use std::str::FromStr;
 
-use gpui2::{AppContext, WindowContext};
+use gpui2::ViewContext;
 use rand::Rng;
 use theme2::Theme;
 
@@ -628,7 +628,7 @@ pub fn example_editor_actions() -> Vec<PaletteItem> {
     ]
 }
 
-pub fn empty_editor_example(cx: &mut WindowContext) -> EditorPane {
+pub fn empty_editor_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
     EditorPane::new(
         cx,
         static_tabs_example(),
@@ -642,7 +642,7 @@ pub fn empty_buffer_example() -> Buffer {
     Buffer::new("empty-buffer").set_rows(Some(BufferRows::default()))
 }
 
-pub fn hello_world_rust_editor_example(cx: &mut WindowContext) -> EditorPane {
+pub fn hello_world_rust_editor_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
     let theme = theme(cx);
 
     EditorPane::new(
@@ -781,7 +781,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
     ]
 }
 
-pub fn hello_world_rust_editor_with_status_example(cx: &mut AppContext) -> EditorPane {
+pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
     let theme = theme(cx);
 
     EditorPane::new(

crates/ui2/src/theme.rs 🔗

@@ -1,225 +0,0 @@
-use gpui2::{
-    AnyElement, AppContext, Bounds, Component, Element, Hsla, LayoutId, Pixels, Result,
-    ViewContext, WindowContext,
-};
-use serde::{de::Visitor, Deserialize, Deserializer};
-use std::collections::HashMap;
-use std::fmt;
-use std::sync::Arc;
-
-#[derive(Deserialize, Clone, Default, Debug)]
-pub struct Theme {
-    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)
-}
-
-pub fn themed<V, E, F>(theme: Theme, cx: &mut ViewContext<V>, build_child: F) -> Themed<E>
-where
-    V: 'static,
-    E: Element<V>,
-    F: FnOnce(&mut ViewContext<V>) -> E,
-{
-    cx.default_global::<ThemeStack>().0.push(theme.clone());
-    let child = build_child(cx);
-    cx.default_global::<ThemeStack>().0.pop();
-    Themed { theme, child }
-}
-
-pub struct Themed<E> {
-    pub(crate) theme: Theme,
-    pub(crate) child: E,
-}
-
-impl<V, E> Component<V> for Themed<E>
-where
-    V: 'static,
-    E: 'static + Element<V> + Send,
-    E::ElementState: Send,
-{
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
-#[derive(Default)]
-struct ThemeStack(Vec<Theme>);
-
-impl<V, E: 'static + Element<V> + Send> Element<V> for Themed<E>
-where
-    V: 'static,
-    E::ElementState: Send,
-{
-    type ElementState = E::ElementState;
-
-    fn id(&self) -> Option<gpui2::ElementId> {
-        None
-    }
-
-    fn initialize(
-        &mut self,
-        view_state: &mut V,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<V>,
-    ) -> Self::ElementState {
-        cx.default_global::<ThemeStack>().0.push(self.theme.clone());
-        let element_state = self.child.initialize(view_state, element_state, cx);
-        cx.default_global::<ThemeStack>().0.pop();
-        element_state
-    }
-
-    fn layout(
-        &mut self,
-        view_state: &mut V,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<V>,
-    ) -> LayoutId
-    where
-        Self: Sized,
-    {
-        cx.default_global::<ThemeStack>().0.push(self.theme.clone());
-        let layout_id = self.child.layout(view_state, element_state, cx);
-        cx.default_global::<ThemeStack>().0.pop();
-        layout_id
-    }
-
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        view_state: &mut V,
-        frame_state: &mut Self::ElementState,
-        cx: &mut ViewContext<V>,
-    ) where
-        Self: Sized,
-    {
-        cx.default_global::<ThemeStack>().0.push(self.theme.clone());
-        self.child.paint(bounds, view_state, frame_state, cx);
-        cx.default_global::<ThemeStack>().0.pop();
-    }
-}
-
-pub fn old_theme(cx: &WindowContext) -> Arc<Theme> {
-    Arc::new(cx.global::<Theme>().clone())
-}
-
-pub fn theme(cx: &AppContext) -> Arc<theme2::Theme> {
-    theme2::active_theme(cx).clone()
-}

crates/workspace2/src/item.rs 🔗

@@ -17,7 +17,7 @@ use theme2::Theme;
 // use gpui2::geometry::vector::Vector2F;
 // use gpui2::AnyWindowHandle;
 // use gpui2::{
-//     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Handle, Task, View,
+//     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Model, Task, View,
 //     ViewContext, View, WeakViewHandle, WindowContext,
 // };
 // use project2::{Project, ProjectEntryId, ProjectPath};
@@ -135,14 +135,14 @@ pub trait Item: EventEmitter + Sized {
     //     }
     //     fn save(
     //         &mut self,
-    //         _project: Handle<Project>,
+    //         _project: Model<Project>,
     //         _cx: &mut ViewContext<Self>,
     //     ) -> Task<Result<()>> {
     //         unimplemented!("save() must be implemented if can_save() returns true")
     //     }
     //     fn save_as(
     //         &mut self,
-    //         _project: Handle<Project>,
+    //         _project: Model<Project>,
     //         _abs_path: PathBuf,
     //         _cx: &mut ViewContext<Self>,
     //     ) -> Task<Result<()>> {
@@ -150,7 +150,7 @@ pub trait Item: EventEmitter + Sized {
     //     }
     //     fn reload(
     //         &mut self,
-    //         _project: Handle<Project>,
+    //         _project: Model<Project>,
     //         _cx: &mut ViewContext<Self>,
     //     ) -> Task<Result<()>> {
     //         unimplemented!("reload() must be implemented if can_save() returns true")
@@ -197,7 +197,7 @@ pub trait Item: EventEmitter + Sized {
     //     }
 
     //     fn deserialize(
-    //         _project: Handle<Project>,
+    //         _project: Model<Project>,
     //         _workspace: WeakViewHandle<Workspace>,
     //         _workspace_id: WorkspaceId,
     //         _item_id: ItemId,
@@ -229,8 +229,8 @@ use std::{
 };
 
 use gpui2::{
-    AnyElement, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, Point,
-    SharedString, Task, View, ViewContext, VisualContext, WindowContext,
+    AnyElement, AnyWindowHandle, AppContext, EventEmitter, HighlightStyle, Model, Pixels, Point,
+    SharedString, Task, View, ViewContext, WindowContext,
 };
 use project2::{Project, ProjectEntryId, ProjectPath};
 use smallvec::SmallVec;
@@ -279,14 +279,14 @@ pub trait ItemHandle: 'static + Send {
     fn is_dirty(&self, cx: &AppContext) -> bool;
     fn has_conflict(&self, cx: &AppContext) -> bool;
     fn can_save(&self, cx: &AppContext) -> bool;
-    fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
     fn save_as(
         &self,
-        project: Handle<Project>,
+        project: Model<Project>,
         abs_path: PathBuf,
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
-    fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
     // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!()
     fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
     fn on_release(
@@ -587,20 +587,20 @@ impl<T: Item> ItemHandle for View<T> {
         self.read(cx).can_save(cx)
     }
 
-    fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+    fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
         self.update(cx, |item, cx| item.save(project, cx))
     }
 
     fn save_as(
         &self,
-        project: Handle<Project>,
+        project: Model<Project>,
         abs_path: PathBuf,
         cx: &mut WindowContext,
     ) -> Task<anyhow::Result<()>> {
         self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
     }
 
-    fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+    fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
         self.update(cx, |item, cx| item.reload(project, cx))
     }
 
@@ -688,8 +688,8 @@ pub trait ProjectItem: Item {
     type Item: project2::Item;
 
     fn for_project_item(
-        project: Handle<Project>,
-        item: Handle<Self::Item>,
+        project: Model<Project>,
+        item: Model<Self::Item>,
         cx: &mut ViewContext<Self>,
     ) -> Self
     where
@@ -714,7 +714,7 @@ pub trait FollowableItem: Item {
     ) -> bool;
     fn apply_update_proto(
         &mut self,
-        project: &Handle<Project>,
+        project: &Model<Project>,
         message: proto::update_view::Variant,
         cx: &mut ViewContext<Self>,
     ) -> Task<Result<()>>;
@@ -736,7 +736,7 @@ pub trait FollowableItemHandle: ItemHandle {
     ) -> bool;
     fn apply_update_proto(
         &self,
-        project: &Handle<Project>,
+        project: &Model<Project>,
         message: proto::update_view::Variant,
         cx: &mut WindowContext,
     ) -> Task<Result<()>>;
@@ -777,7 +777,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //     fn apply_update_proto(
 //         &self,
-//         project: &Handle<Project>,
+//         project: &Model<Project>,
 //         message: proto::update_view::Variant,
 //         cx: &mut WindowContext,
 //     ) -> Task<Result<()>> {
@@ -802,7 +802,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //     use super::{Item, ItemEvent};
 //     use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
 //     use gpui2::{
-//         elements::Empty, AnyElement, AppContext, Element, Entity, Handle, Task, View,
+//         elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
 //         ViewContext, View, WeakViewHandle,
 //     };
 //     use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
@@ -824,7 +824,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //         pub is_dirty: bool,
 //         pub is_singleton: bool,
 //         pub has_conflict: bool,
-//         pub project_items: Vec<Handle<TestProjectItem>>,
+//         pub project_items: Vec<Model<TestProjectItem>>,
 //         pub nav_history: Option<ItemNavHistory>,
 //         pub tab_descriptions: Option<Vec<&'static str>>,
 //         pub tab_detail: Cell<Option<usize>>,
@@ -869,7 +869,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //     }
 
 //     impl TestProjectItem {
-//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Handle<Self> {
+//         pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
 //             let entry_id = Some(ProjectEntryId::from_proto(id));
 //             let project_path = Some(ProjectPath {
 //                 worktree_id: WorktreeId::from_usize(0),
@@ -881,7 +881,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //             })
 //         }
 
-//         pub fn new_untitled(cx: &mut AppContext) -> Handle<Self> {
+//         pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
 //             cx.add_model(|_| Self {
 //                 project_path: None,
 //                 entry_id: None,
@@ -934,7 +934,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //             self
 //         }
 
-//         pub fn with_project_items(mut self, items: &[Handle<TestProjectItem>]) -> Self {
+//         pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
 //             self.project_items.clear();
 //             self.project_items.extend(items.iter().cloned());
 //             self
@@ -1045,7 +1045,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //         fn save(
 //             &mut self,
-//             _: Handle<Project>,
+//             _: Model<Project>,
 //             _: &mut ViewContext<Self>,
 //         ) -> Task<anyhow::Result<()>> {
 //             self.save_count += 1;
@@ -1055,7 +1055,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //         fn save_as(
 //             &mut self,
-//             _: Handle<Project>,
+//             _: Model<Project>,
 //             _: std::path::PathBuf,
 //             _: &mut ViewContext<Self>,
 //         ) -> Task<anyhow::Result<()>> {
@@ -1066,7 +1066,7 @@ pub trait FollowableItemHandle: ItemHandle {
 
 //         fn reload(
 //             &mut self,
-//             _: Handle<Project>,
+//             _: Model<Project>,
 //             _: &mut ViewContext<Self>,
 //         ) -> Task<anyhow::Result<()>> {
 //             self.reload_count += 1;
@@ -1083,7 +1083,7 @@ pub trait FollowableItemHandle: ItemHandle {
 //         }
 
 //         fn deserialize(
-//             _project: Handle<Project>,
+//             _project: Model<Project>,
 //             _workspace: WeakViewHandle<Workspace>,
 //             workspace_id: WorkspaceId,
 //             _item_id: ItemId,

crates/zed/examples/semantic_index_eval.rs 🔗

@@ -1,4 +1,4 @@
-use ai::embedding::OpenAIEmbeddings;
+use ai::providers::open_ai::OpenAIEmbeddingProvider;
 use anyhow::{anyhow, Result};
 use client::{self, UserStore};
 use gpui::{AsyncAppContext, ModelHandle, Task};
@@ -475,7 +475,7 @@ fn main() {
             let semantic_index = SemanticIndex::new(
                 fs.clone(),
                 db_file_path,
-                Arc::new(OpenAIEmbeddings::new(http_client, cx.background())),
+                Arc::new(OpenAIEmbeddingProvider::new(http_client, cx.background())),
                 languages.clone(),
                 cx.clone(),
             )

crates/zed/src/languages/elixir.rs 🔗

@@ -321,8 +321,8 @@ impl LspAdapter for NextLspAdapter {
             latest_github_release("elixir-tools/next-ls", false, delegate.http_client()).await?;
         let version = release.name.clone();
         let platform = match consts::ARCH {
-            "x86_64" => "darwin_arm64",
-            "aarch64" => "darwin_amd64",
+            "x86_64" => "darwin_amd64",
+            "aarch64" => "darwin_arm64",
             other => bail!("Running on unsupported platform: {other}"),
         };
         let asset_name = format!("next_ls_{}", platform);

crates/zed2/Cargo.toml 🔗

@@ -15,6 +15,7 @@ name = "Zed"
 path = "src/main.rs"
 
 [dependencies]
+ai2 = { path = "../ai2"}
 # audio = { path = "../audio" }
 # activity_indicator = { path = "../activity_indicator" }
 # auto_update = { path = "../auto_update" }

crates/zed2/src/main.rs 🔗

@@ -120,7 +120,7 @@ fn main() {
         let node_runtime = RealNodeRuntime::new(http.clone());
 
         language2::init(cx);
-        let user_store = cx.entity(|cx| UserStore::new(client.clone(), http.clone(), cx));
+        let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
         // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
 
         cx.set_global(client.clone());

crates/zed2/src/zed2.rs 🔗

@@ -5,7 +5,7 @@ mod open_listener;
 pub use assets::*;
 use client2::{Client, UserStore};
 use collections::HashMap;
-use gpui2::{AsyncAppContext, Handle, Point};
+use gpui2::{AsyncAppContext, Model};
 pub use only_instance::*;
 pub use open_listener::*;
 
@@ -52,7 +52,7 @@ pub fn connect_to_cli(
 
 pub struct AppState {
     pub client: Arc<Client>,
-    pub user_store: Handle<UserStore>,
+    pub user_store: Model<UserStore>,
 }
 
 pub async fn handle_cli_connection(

script/zed-2-progress-report.py 🔗

@@ -0,0 +1,27 @@
+import os
+from pathlib import Path
+
+THIS_SCRIPT_PATH: Path = Path(__file__)
+CRATES_DIR: Path = THIS_SCRIPT_PATH.parent.parent / "crates"
+
+zed_1_crate_count: int = 0
+zed_2_crate_count: int = 0
+
+for child in os.listdir(CRATES_DIR):
+    child_path: str = os.path.join(CRATES_DIR, child)
+
+    if not os.path.isdir(child_path):
+        continue
+
+    if child.endswith("2"):
+        zed_2_crate_count += 1
+    else:
+        zed_1_crate_count += 1
+
+print(f"crates ported: {zed_2_crate_count}")
+print(f"crates in total: {zed_1_crate_count}")
+
+percent_complete: float = (zed_2_crate_count / zed_1_crate_count) * 100
+percent_complete_rounded: float = round(percent_complete, 2)
+
+print(f"progress: {percent_complete_rounded}%")