WIP

Antonio Scandurra created

Change summary

Cargo.lock                                     |  118 +
Cargo.toml                                     |    1 
crates/gpui2/Cargo.toml                        |    2 
crates/gpui2/src/action.rs                     |   38 
crates/gpui2/src/app.rs                        |   83 +
crates/gpui2/src/app/async_context.rs          |   42 
crates/gpui2/src/app/model_context.rs          |    8 
crates/gpui2/src/executor.rs                   |   14 
crates/gpui2/src/gpui2.rs                      |   37 
crates/gpui2/src/window.rs                     |   27 
crates/settings2/Cargo.toml                    |   42 
crates/settings2/src/keymap_file.rs            |  163 ++
crates/settings2/src/settings2.rs              |   38 
crates/settings2/src/settings_file.rs          |  135 ++
crates/settings2/src/settings_store.rs         | 1268 ++++++++++++++++++++
crates/storybook2/Cargo.toml                   |    2 
crates/storybook2/src/stories/focus.rs         |    9 
crates/zed2/Cargo.toml                         |  180 ++
crates/zed2/resources/app-icon-preview.png     |    0 
crates/zed2/resources/app-icon-preview@2x.png  |    0 
crates/zed2/resources/app-icon.png             |    0 
crates/zed2/resources/app-icon@2x.png          |    0 
crates/zed2/resources/info/DocumentTypes.plist |   62 
crates/zed2/resources/info/Permissions.plist   |   24 
crates/zed2/resources/zed.entitlements         |   24 
crates/zed2/src/assets.rs                      |   33 
crates/zed2/src/main.rs                        |  912 ++++++++++++++
crates/zed2/src/only_instance.rs               |  104 +
crates/zed2/src/open_listener.rs               |   98 +
crates/zed2/src/zed2.rs                        |  203 +++
30 files changed, 3,641 insertions(+), 26 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -7212,6 +7212,35 @@ dependencies = [
  "util",
 ]
 
+[[package]]
+name = "settings2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "feature_flags",
+ "fs",
+ "futures 0.3.28",
+ "gpui2",
+ "indoc",
+ "lazy_static",
+ "postage",
+ "pretty_assertions",
+ "rust-embed",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "serde_json_lenient",
+ "smallvec",
+ "sqlez",
+ "toml 0.5.11",
+ "tree-sitter",
+ "tree-sitter-json 0.19.0",
+ "unindent",
+ "util",
+]
+
 [[package]]
 name = "sha-1"
 version = "0.9.8"
@@ -10338,6 +10367,95 @@ dependencies = [
  "gpui",
 ]
 
+[[package]]
+name = "zed2"
+version = "0.109.0"
+dependencies = [
+ "anyhow",
+ "async-compression",
+ "async-recursion 0.3.2",
+ "async-tar",
+ "async-trait",
+ "backtrace",
+ "chrono",
+ "cli",
+ "collections",
+ "ctor",
+ "env_logger 0.9.3",
+ "feature_flags",
+ "fs",
+ "fsevent",
+ "futures 0.3.28",
+ "fuzzy",
+ "gpui2",
+ "ignore",
+ "image",
+ "indexmap 1.9.3",
+ "install_cli",
+ "isahc",
+ "language_tools",
+ "lazy_static",
+ "libc",
+ "log",
+ "lsp",
+ "node_runtime",
+ "num_cpus",
+ "parking_lot 0.11.2",
+ "postage",
+ "rand 0.8.5",
+ "regex",
+ "rpc",
+ "rsa 0.4.0",
+ "rust-embed",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "settings2",
+ "shellexpand",
+ "simplelog",
+ "smallvec",
+ "smol",
+ "sum_tree",
+ "tempdir",
+ "text",
+ "thiserror",
+ "tiny_http",
+ "toml 0.5.11",
+ "tree-sitter",
+ "tree-sitter-bash",
+ "tree-sitter-c",
+ "tree-sitter-cpp",
+ "tree-sitter-css",
+ "tree-sitter-elixir",
+ "tree-sitter-elm",
+ "tree-sitter-embedded-template",
+ "tree-sitter-glsl",
+ "tree-sitter-go",
+ "tree-sitter-heex",
+ "tree-sitter-html",
+ "tree-sitter-json 0.20.0",
+ "tree-sitter-lua",
+ "tree-sitter-markdown",
+ "tree-sitter-nix",
+ "tree-sitter-nu",
+ "tree-sitter-php",
+ "tree-sitter-python",
+ "tree-sitter-racket",
+ "tree-sitter-ruby",
+ "tree-sitter-rust",
+ "tree-sitter-scheme",
+ "tree-sitter-svelte",
+ "tree-sitter-toml",
+ "tree-sitter-typescript",
+ "tree-sitter-yaml",
+ "unindent",
+ "url",
+ "urlencoding",
+ "util",
+ "uuid 1.4.1",
+]
+
 [[package]]
 name = "zeroize"
 version = "1.6.0"

Cargo.toml 🔗

@@ -81,6 +81,7 @@ members = [
     "crates/welcome",
     "crates/xtask",
     "crates/zed",
+    "crates/zed2",
     "crates/zed-actions"
 ]
 default-members = ["crates/zed"]

crates/gpui2/Cargo.toml 🔗

@@ -7,7 +7,7 @@ description = "The next version of Zed's GPU-accelerated UI framework"
 publish = false
 
 [features]
-test = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
+test-support = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"]
 
 [lib]
 path = "src/gpui2.rs"

crates/gpui2/src/action.rs 🔗

@@ -1,18 +1,42 @@
 use crate::SharedString;
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, Context, Result};
 use collections::{HashMap, HashSet};
-use std::any::Any;
+use serde::Deserialize;
+use std::any::{type_name, Any};
 
 pub trait Action: Any + Send + Sync {
+    fn qualified_name() -> SharedString
+    where
+        Self: Sized;
+    fn build(value: Option<serde_json::Value>) -> Result<Box<dyn Action>>
+    where
+        Self: Sized;
+
     fn partial_eq(&self, action: &dyn Action) -> bool;
     fn boxed_clone(&self) -> Box<dyn Action>;
     fn as_any(&self) -> &dyn Any;
 }
 
-impl<T> Action for T
+impl<A> Action for A
 where
-    T: Any + PartialEq + Clone + Send + Sync,
+    A: for<'a> Deserialize<'a> + Any + PartialEq + Clone + Default + Send + Sync,
 {
+    fn qualified_name() -> SharedString {
+        type_name::<A>().into()
+    }
+
+    fn build(params: Option<serde_json::Value>) -> Result<Box<dyn Action>>
+    where
+        Self: Sized,
+    {
+        let action = if let Some(params) = params {
+            serde_json::from_value(params).context("failed to deserialize action")?
+        } else {
+            Self::default()
+        };
+        Ok(Box::new(action))
+    }
+
     fn partial_eq(&self, action: &dyn Action) -> bool {
         action
             .as_any()
@@ -130,15 +154,15 @@ impl DispatchContextPredicate {
             return false;
         };
         match self {
-            Self::Identifier(name) => context.set.contains(&name),
+            Self::Identifier(name) => context.set.contains(name),
             Self::Equal(left, right) => context
                 .map
-                .get(&left)
+                .get(left)
                 .map(|value| value == right)
                 .unwrap_or(false),
             Self::NotEqual(left, right) => context
                 .map
-                .get(&left)
+                .get(left)
                 .map(|value| value != right)
                 .unwrap_or(true),
             Self::Not(pred) => !pred.eval(contexts),

crates/gpui2/src/app.rs 🔗

@@ -9,10 +9,10 @@ use refineable::Refineable;
 use smallvec::SmallVec;
 
 use crate::{
-    current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor,
+    current_platform, image_cache::ImageCache, Action, AssetSource, Context, DisplayId, Executor,
     FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly,
-    Platform, SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View,
-    Window, WindowContext, WindowHandle, WindowId,
+    Platform, SharedString, SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement,
+    TextSystem, View, Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -55,10 +55,10 @@ impl App {
             Mutex::new(AppContext {
                 this: this.clone(),
                 text_system: Arc::new(TextSystem::new(platform.text_system())),
-                pending_updates: 0,
+                platform: MainThreadOnly::new(platform, executor.clone()),
                 flushing_effects: false,
+                pending_updates: 0,
                 next_frame_callbacks: Default::default(),
-                platform: MainThreadOnly::new(platform, executor.clone()),
                 executor,
                 svg_renderer: SvgRenderer::new(asset_source),
                 image_cache: ImageCache::new(http_client),
@@ -68,6 +68,7 @@ impl App {
                 entities,
                 windows: SlotMap::with_key(),
                 keymap: Arc::new(RwLock::new(Keymap::default())),
+                action_builders: HashMap::default(),
                 pending_notifications: Default::default(),
                 pending_effects: Default::default(),
                 observers: SubscriberSet::new(),
@@ -90,12 +91,17 @@ impl App {
             on_finish_launching(cx);
         }));
     }
+
+    pub fn executor(&self) -> Executor {
+        self.0.lock().executor.clone()
+    }
 }
 
 type Handler = Box<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>;
 type EventHandler = Box<dyn Fn(&dyn Any, &mut AppContext) -> bool + Send + Sync + 'static>;
 type ReleaseHandler = Box<dyn Fn(&mut dyn Any, &mut AppContext) + Send + Sync + 'static>;
 type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
+type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
 
 pub struct AppContext {
     this: Weak<Mutex<AppContext>>,
@@ -113,6 +119,7 @@ pub struct AppContext {
     pub(crate) entities: EntityMap,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     pub(crate) keymap: Arc<RwLock<Keymap>>,
+    action_builders: HashMap<SharedString, ActionBuilder>,
     pub(crate) pending_notifications: HashSet<EntityId>,
     pending_effects: VecDeque<Effect>,
     pub(crate) observers: SubscriberSet<EntityId, Handler>,
@@ -134,6 +141,20 @@ impl AppContext {
         result
     }
 
+    pub(crate) fn read_window<R>(
+        &mut self,
+        id: WindowId,
+        read: impl FnOnce(&WindowContext) -> R,
+    ) -> Result<R> {
+        let window = self
+            .windows
+            .get(id)
+            .ok_or_else(|| anyhow!("window not found"))?
+            .as_ref()
+            .unwrap();
+        Ok(read(&WindowContext::immutable(self, &window)))
+    }
+
     pub(crate) fn update_window<R>(
         &mut self,
         id: WindowId,
@@ -385,6 +406,24 @@ impl AppContext {
             .unwrap()
     }
 
+    pub fn update_global<G, R>(&mut self, f: impl FnOnce(&mut G, &mut Self) -> R) -> R
+    where
+        G: 'static + Send + Sync,
+    {
+        let mut global = self
+            .global_stacks_by_type
+            .get_mut(&TypeId::of::<G>())
+            .and_then(|stack| stack.pop())
+            .ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
+            .unwrap();
+        let result = f(global.downcast_mut().unwrap(), self);
+        self.global_stacks_by_type
+            .get_mut(&TypeId::of::<G>())
+            .unwrap()
+            .push(global);
+        result
+    }
+
     pub fn default_global<G: 'static + Default + Sync + Send>(&mut self) -> &mut G {
         let stack = self
             .global_stacks_by_type
@@ -396,6 +435,19 @@ impl AppContext {
         stack.last_mut().unwrap().downcast_mut::<G>().unwrap()
     }
 
+    pub fn set_global<T: Send + Sync + 'static>(&mut self, global: T) {
+        let global = Box::new(global);
+        let stack = self
+            .global_stacks_by_type
+            .entry(TypeId::of::<T>())
+            .or_default();
+        if let Some(last) = stack.last_mut() {
+            *last = global;
+        } else {
+            stack.push(global)
+        }
+    }
+
     pub(crate) fn push_global<T: Send + Sync + 'static>(&mut self, state: T) {
         self.global_stacks_by_type
             .entry(TypeId::of::<T>())
@@ -422,9 +474,26 @@ impl AppContext {
         self.keymap.write().add_bindings(bindings);
         self.push_effect(Effect::Refresh);
     }
+
+    pub fn register_action_type<A: Action>(&mut self) {
+        self.action_builders.insert(A::qualified_name(), A::build);
+    }
+
+    pub fn build_action(
+        &mut self,
+        name: &str,
+        params: Option<serde_json::Value>,
+    ) -> Result<Box<dyn Action>> {
+        let build = self
+            .action_builders
+            .get(name)
+            .ok_or_else(|| anyhow!("no action type registered for {}", name))?;
+        (build)(params)
+    }
 }
 
 impl Context for AppContext {
+    type BorrowedContext<'a, 'w> = Self;
     type EntityContext<'a, 'w, T: Send + Sync + 'static> = ModelContext<'a, T>;
     type Result<T> = T;
 
@@ -451,6 +520,10 @@ impl Context for AppContext {
             result
         })
     }
+
+    fn read_global<G: 'static + Send + Sync, R>(&self, read: impl FnOnce(&G, &Self) -> R) -> R {
+        read(self.global(), self)
+    }
 }
 
 impl MainThread<AppContext> {

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

@@ -9,18 +9,19 @@ use std::sync::Weak;
 pub struct AsyncAppContext(pub(crate) Weak<Mutex<AppContext>>);
 
 impl Context for AsyncAppContext {
+    type BorrowedContext<'a, 'w> = AppContext;
     type EntityContext<'a, 'w, T: 'static + Send + Sync> = ModelContext<'a, T>;
     type Result<T> = Result<T>;
 
     fn entity<T: Send + Sync + 'static>(
         &mut self,
         build_entity: impl FnOnce(&mut Self::EntityContext<'_, '_, T>) -> T,
-    ) -> Result<Handle<T>> {
+    ) -> Self::Result<Handle<T>> {
         let app = self
             .0
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock(); // Does not compile without this variable.
+        let mut lock = app.lock();
         Ok(lock.entity(build_entity))
     }
 
@@ -28,17 +29,42 @@ impl Context for AsyncAppContext {
         &mut self,
         handle: &Handle<T>,
         update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
-    ) -> Result<R> {
+    ) -> Self::Result<R> {
         let app = self
             .0
             .upgrade()
             .ok_or_else(|| anyhow!("app was released"))?;
-        let mut lock = app.lock(); // Does not compile without this variable.
+        let mut lock = app.lock();
         Ok(lock.update_entity(handle, update))
     }
+
+    fn read_global<G: 'static + Send + Sync, R>(
+        &self,
+        read: impl FnOnce(&G, &Self::BorrowedContext<'_, '_>) -> R,
+    ) -> Self::Result<R> {
+        let app = self
+            .0
+            .upgrade()
+            .ok_or_else(|| anyhow!("app was released"))?;
+        let mut lock = app.lock();
+        Ok(lock.read_global(read))
+    }
 }
 
 impl AsyncAppContext {
+    pub fn read_window<R>(
+        &self,
+        handle: AnyWindowHandle,
+        update: impl FnOnce(&WindowContext) -> R,
+    ) -> Result<R> {
+        let app = self
+            .0
+            .upgrade()
+            .ok_or_else(|| anyhow!("app was released"))?;
+        let mut app_context = app.lock();
+        app_context.read_window(handle.id, update)
+    }
+
     pub fn update_window<R>(
         &self,
         handle: AnyWindowHandle,
@@ -76,6 +102,7 @@ impl AsyncWindowContext {
 }
 
 impl Context for AsyncWindowContext {
+    type BorrowedContext<'a, 'w> = WindowContext<'a, 'w>;
     type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
     type Result<T> = Result<T>;
 
@@ -95,4 +122,11 @@ impl Context for AsyncWindowContext {
         self.app
             .update_window(self.window, |cx| cx.update_entity(handle, update))
     }
+
+    fn read_global<G: 'static + Send + Sync, R>(
+        &self,
+        read: impl FnOnce(&G, &Self::BorrowedContext<'_, '_>) -> R,
+    ) -> Result<R> {
+        self.app.read_window(self.window, |cx| cx.read_global(read))
+    }
 }

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

@@ -132,6 +132,7 @@ impl<'a, T: EventEmitter + Send + Sync + 'static> ModelContext<'a, T> {
 }
 
 impl<'a, T: 'static> Context for ModelContext<'a, T> {
+    type BorrowedContext<'b, 'c> = ModelContext<'b, T>;
     type EntityContext<'b, 'c, U: Send + Sync + 'static> = ModelContext<'b, U>;
     type Result<U> = U;
 
@@ -149,4 +150,11 @@ impl<'a, T: 'static> Context for ModelContext<'a, T> {
     ) -> R {
         self.app.update_entity(handle, update)
     }
+
+    fn read_global<G: 'static + Send + Sync, R>(
+        &self,
+        read: impl FnOnce(&G, &Self::BorrowedContext<'_, '_>) -> R,
+    ) -> R {
+        read(self.app.global(), self)
+    }
 }

crates/gpui2/src/executor.rs 🔗

@@ -1,10 +1,12 @@
-use crate::PlatformDispatcher;
+use crate::{AppContext, PlatformDispatcher};
 use smol::prelude::*;
 use std::{
+    fmt::Debug,
     pin::Pin,
     sync::Arc,
     task::{Context, Poll},
 };
+use util::TryFutureExt;
 
 #[derive(Clone)]
 pub struct Executor {
@@ -30,6 +32,16 @@ impl<T> Task<T> {
     }
 }
 
+impl<E, T> Task<Result<T, E>>
+where
+    T: 'static + Send,
+    E: 'static + Send + Debug,
+{
+    pub fn detach_and_log_err(self, cx: &mut AppContext) {
+        cx.executor().spawn(self.log_err()).detach();
+    }
+}
+
 impl<T> Future for Task<T> {
     type Output = T;
 

crates/gpui2/src/gpui2.rs 🔗

@@ -56,6 +56,7 @@ pub use window::*;
 use derive_more::{Deref, DerefMut};
 use std::{
     any::{Any, TypeId},
+    borrow::Borrow,
     mem,
     ops::{Deref, DerefMut},
     sync::Arc,
@@ -65,6 +66,7 @@ use taffy::TaffyLayoutEngine;
 type AnyBox = Box<dyn Any + Send + Sync>;
 
 pub trait Context {
+    type BorrowedContext<'a, 'w>: Context;
     type EntityContext<'a, 'w, T: 'static + Send + Sync>;
     type Result<T>;
 
@@ -78,6 +80,11 @@ pub trait Context {
         handle: &Handle<T>,
         update: impl FnOnce(&mut T, &mut Self::EntityContext<'_, '_, T>) -> R,
     ) -> Self::Result<R>;
+
+    fn read_global<G: 'static + Send + Sync, R>(
+        &self,
+        read: impl FnOnce(&G, &Self::BorrowedContext<'_, '_>) -> R,
+    ) -> Self::Result<R>;
 }
 
 pub enum GlobalKey {
@@ -104,6 +111,7 @@ impl<T> DerefMut for MainThread<T> {
 }
 
 impl<C: Context> Context for MainThread<C> {
+    type BorrowedContext<'a, 'w> = MainThread<C::BorrowedContext<'a, 'w>>;
     type EntityContext<'a, 'w, T: 'static + Send + Sync> = MainThread<C::EntityContext<'a, 'w, T>>;
     type Result<T> = C::Result<T>;
 
@@ -137,6 +145,21 @@ impl<C: Context> Context for MainThread<C> {
             update(entity, cx)
         })
     }
+
+    fn read_global<G: 'static + Send + Sync, R>(
+        &self,
+        read: impl FnOnce(&G, &Self::BorrowedContext<'_, '_>) -> R,
+    ) -> Self::Result<R> {
+        self.0.read_global(|global, cx| {
+            let cx = unsafe {
+                mem::transmute::<
+                    &C::BorrowedContext<'_, '_>,
+                    &MainThread<C::BorrowedContext<'_, '_>>,
+                >(cx)
+            };
+            read(global, cx)
+        })
+    }
 }
 
 pub trait BorrowAppContext {
@@ -152,15 +175,19 @@ pub trait BorrowAppContext {
         result
     }
 
-    fn with_global<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
+    fn with_global<T: Send + Sync + 'static, F, R>(&mut self, global: T, f: F) -> R
     where
         F: FnOnce(&mut Self) -> R,
     {
-        self.app_mut().push_global(state);
+        self.app_mut().push_global(global);
         let result = f(self);
         self.app_mut().pop_global::<T>();
         result
     }
+
+    fn set_global<T: Send + Sync + 'static>(&mut self, global: T) {
+        self.app_mut().set_global(global)
+    }
 }
 
 pub trait EventEmitter {
@@ -198,6 +225,12 @@ impl AsRef<str> for SharedString {
     }
 }
 
+impl Borrow<str> for SharedString {
+    fn borrow(&self) -> &str {
+        self.as_ref()
+    }
+}
+
 impl std::fmt::Debug for SharedString {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         self.0.fmt(f)

crates/gpui2/src/window.rs 🔗

@@ -288,6 +288,13 @@ pub struct WindowContext<'a, 'w> {
 }
 
 impl<'a, 'w> WindowContext<'a, 'w> {
+    pub(crate) fn immutable(app: &'a AppContext, window: &'w Window) -> Self {
+        Self {
+            app: Reference::Immutable(app),
+            window: Reference::Immutable(window),
+        }
+    }
+
     pub(crate) fn mutable(app: &'a mut AppContext, window: &'w mut Window) -> Self {
         Self {
             app: Reference::Mutable(app),
@@ -1049,6 +1056,7 @@ impl<'a, 'w> MainThread<WindowContext<'a, 'w>> {
 }
 
 impl Context for WindowContext<'_, '_> {
+    type BorrowedContext<'a, 'w> = WindowContext<'a, 'w>;
     type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
     type Result<T> = T;
 
@@ -1078,6 +1086,10 @@ impl Context for WindowContext<'_, '_> {
         self.entities.end_lease(entity);
         result
     }
+
+    fn read_global<G: 'static + Send + Sync, R>(&self, read: impl FnOnce(&G, &Self) -> R) -> R {
+        read(self.app.global(), self)
+    }
 }
 
 impl<'a, 'w> std::ops::Deref for WindowContext<'a, 'w> {
@@ -1520,7 +1532,11 @@ impl<'a, 'w, S: EventEmitter + Send + Sync + 'static> ViewContext<'a, 'w, S> {
     }
 }
 
-impl<'a, 'w, S> Context for ViewContext<'a, 'w, S> {
+impl<'a, 'w, V> Context for ViewContext<'a, 'w, V>
+where
+    V: 'static + Send + Sync,
+{
+    type BorrowedContext<'b, 'c> = ViewContext<'b, 'c, V>;
     type EntityContext<'b, 'c, U: 'static + Send + Sync> = ViewContext<'b, 'c, U>;
     type Result<U> = U;
 
@@ -1531,13 +1547,20 @@ impl<'a, 'w, S> Context for ViewContext<'a, 'w, S> {
         self.window_cx.entity(build_entity)
     }
 
-    fn update_entity<U: Send + Sync + 'static, R>(
+    fn update_entity<U: 'static + Send + Sync, R>(
         &mut self,
         handle: &Handle<U>,
         update: impl FnOnce(&mut U, &mut Self::EntityContext<'_, '_, U>) -> R,
     ) -> R {
         self.window_cx.update_entity(handle, update)
     }
+
+    fn read_global<G: 'static + Send + Sync, R>(
+        &self,
+        read: impl FnOnce(&G, &Self::BorrowedContext<'_, '_>) -> R,
+    ) -> R {
+        read(self.global(), self)
+    }
 }
 
 impl<'a, 'w, S: 'static> std::ops::Deref for ViewContext<'a, 'w, S> {

crates/settings2/Cargo.toml 🔗

@@ -0,0 +1,42 @@
+[package]
+name = "settings2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/settings2.rs"
+doctest = false
+
+[features]
+test-support = ["gpui2/test-support", "fs/test-support"]
+
+[dependencies]
+collections = { path = "../collections" }
+gpui2 = { path = "../gpui2" }
+sqlez = { path = "../sqlez" }
+fs = { path = "../fs" }
+feature_flags = { path = "../feature_flags" }
+util = { path = "../util" }
+
+anyhow.workspace = true
+futures.workspace = true
+serde_json_lenient = {version = "0.1", features = ["preserve_order", "raw_value"]}
+lazy_static.workspace = true
+postage.workspace = true
+rust-embed.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smallvec.workspace = true
+toml.workspace = true
+tree-sitter.workspace = true
+tree-sitter-json = "*"
+
+[dev-dependencies]
+gpui2 = { path = "../gpui2", features = ["test-support"] }
+fs = { path = "../fs", features = ["test-support"] }
+indoc.workspace = true
+pretty_assertions.workspace = true
+unindent.workspace = true

crates/settings2/src/keymap_file.rs 🔗

@@ -0,0 +1,163 @@
+use crate::{settings_store::parse_json_with_comments, SettingsAssets};
+use anyhow::{anyhow, Context, Result};
+use collections::BTreeMap;
+use gpui2::{AppContext, KeyBinding};
+use schemars::{
+    gen::{SchemaGenerator, SchemaSettings},
+    schema::{InstanceType, Schema, SchemaObject, SingleOrVec, SubschemaValidation},
+    JsonSchema,
+};
+use serde::Deserialize;
+use serde_json::Value;
+use util::{asset_str, ResultExt};
+
+#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
+#[serde(transparent)]
+pub struct KeymapFile(Vec<KeymapBlock>);
+
+#[derive(Debug, Deserialize, Default, Clone, JsonSchema)]
+pub struct KeymapBlock {
+    #[serde(default)]
+    context: Option<String>,
+    bindings: BTreeMap<String, KeymapAction>,
+}
+
+#[derive(Debug, Deserialize, Default, Clone)]
+#[serde(transparent)]
+pub struct KeymapAction(Value);
+
+impl JsonSchema for KeymapAction {
+    fn schema_name() -> String {
+        "KeymapAction".into()
+    }
+
+    fn json_schema(_: &mut SchemaGenerator) -> Schema {
+        Schema::Bool(true)
+    }
+}
+
+#[derive(Deserialize)]
+struct ActionWithData(Box<str>, Value);
+
+impl KeymapFile {
+    pub fn load_asset(asset_path: &str, cx: &mut AppContext) -> Result<()> {
+        let content = asset_str::<SettingsAssets>(asset_path);
+
+        Self::parse(content.as_ref())?.add_to_cx(cx)
+    }
+
+    pub fn parse(content: &str) -> Result<Self> {
+        parse_json_with_comments::<Self>(content)
+    }
+
+    pub fn add_to_cx(self, cx: &mut AppContext) -> Result<()> {
+        for KeymapBlock { context, bindings } in self.0 {
+            let bindings = bindings
+                .into_iter()
+                .filter_map(|(keystroke, action)| {
+                    let action = action.0;
+
+                    // This is a workaround for a limitation in serde: serde-rs/json#497
+                    // We want to deserialize the action data as a `RawValue` so that we can
+                    // deserialize the action itself dynamically directly from the JSON
+                    // string. But `RawValue` currently does not work inside of an untagged enum.
+                    match action {
+                        Value::Array(items) => {
+                            let Ok([name, data]): Result<[serde_json::Value; 2], _> =
+                                items.try_into()
+                            else {
+                                return Some(Err(anyhow!("Expected array of length 2")));
+                            };
+                            let serde_json::Value::String(name) = name else {
+                                return Some(Err(anyhow!(
+                                    "Expected first item in array to be a string."
+                                )));
+                            };
+                            cx.build_action(&name, Some(data))
+                        }
+                        Value::String(name) => cx.build_action(&name, None),
+                        Value::Null => Ok(no_action()),
+                        _ => {
+                            return Some(Err(anyhow!("Expected two-element array, got {action:?}")))
+                        }
+                    }
+                    .with_context(|| {
+                        format!(
+                            "invalid binding value for keystroke {keystroke}, context {context:?}"
+                        )
+                    })
+                    .log_err()
+                    .map(|action| KeyBinding::load(&keystroke, action, context.as_deref()))
+                })
+                .collect::<Result<Vec<_>>>()?;
+
+            cx.bind_keys(bindings);
+        }
+        Ok(())
+    }
+
+    pub fn generate_json_schema(action_names: &[&'static str]) -> serde_json::Value {
+        let mut root_schema = SchemaSettings::draft07()
+            .with(|settings| settings.option_add_null_type = false)
+            .into_generator()
+            .into_root_schema_for::<KeymapFile>();
+
+        let action_schema = Schema::Object(SchemaObject {
+            subschemas: Some(Box::new(SubschemaValidation {
+                one_of: Some(vec![
+                    Schema::Object(SchemaObject {
+                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
+                        enum_values: Some(
+                            action_names
+                                .iter()
+                                .map(|name| Value::String(name.to_string()))
+                                .collect(),
+                        ),
+                        ..Default::default()
+                    }),
+                    Schema::Object(SchemaObject {
+                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Array))),
+                        ..Default::default()
+                    }),
+                    Schema::Object(SchemaObject {
+                        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Null))),
+                        ..Default::default()
+                    }),
+                ]),
+                ..Default::default()
+            })),
+            ..Default::default()
+        });
+
+        root_schema
+            .definitions
+            .insert("KeymapAction".to_owned(), action_schema);
+
+        serde_json::to_value(root_schema).unwrap()
+    }
+}
+
+fn no_action() -> Box<dyn gpui2::Action> {
+    todo!()
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::KeymapFile;
+
+    #[test]
+    fn can_deserialize_keymap_with_trailing_comma() {
+        let json = indoc::indoc! {"[
+              // Standard macOS bindings
+              {
+                \"bindings\": {
+                  \"up\": \"menu::SelectPrev\",
+                },
+              },
+            ]
+                  "
+
+        };
+        KeymapFile::parse(json).unwrap();
+    }
+}

crates/settings2/src/settings2.rs 🔗

@@ -0,0 +1,38 @@
+mod keymap_file;
+mod settings_file;
+mod settings_store;
+
+use rust_embed::RustEmbed;
+use std::{borrow::Cow, str};
+use util::asset_str;
+
+pub use keymap_file::KeymapFile;
+pub use settings_file::*;
+pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
+
+#[derive(RustEmbed)]
+#[folder = "../../assets"]
+#[include = "settings/*"]
+#[include = "keymaps/*"]
+#[exclude = "*.DS_Store"]
+pub struct SettingsAssets;
+
+pub fn default_settings() -> Cow<'static, str> {
+    asset_str::<SettingsAssets>("settings/default.json")
+}
+
+pub fn default_keymap() -> Cow<'static, str> {
+    asset_str::<SettingsAssets>("keymaps/default.json")
+}
+
+pub fn vim_keymap() -> Cow<'static, str> {
+    asset_str::<SettingsAssets>("keymaps/vim.json")
+}
+
+pub fn initial_user_settings_content() -> Cow<'static, str> {
+    asset_str::<SettingsAssets>("settings/initial_user_settings.json")
+}
+
+pub fn initial_local_settings_content() -> Cow<'static, str> {
+    asset_str::<SettingsAssets>("settings/initial_local_settings.json")
+}

crates/settings2/src/settings_file.rs 🔗

@@ -0,0 +1,135 @@
+use crate::{settings_store::SettingsStore, Setting};
+use anyhow::Result;
+use fs::Fs;
+use futures::{channel::mpsc, StreamExt};
+use gpui2::{AppContext, Context};
+use std::{
+    io::ErrorKind,
+    path::{Path, PathBuf},
+    str,
+    sync::Arc,
+    time::Duration,
+};
+use util::{paths, ResultExt};
+
+pub fn register<T: Setting>(cx: &mut AppContext) {
+    cx.update_global(|store: &mut SettingsStore, cx| {
+        store.register_setting::<T>(cx);
+    });
+}
+
+pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T {
+    cx.global::<SettingsStore>().get(None)
+}
+
+pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a T {
+    cx.global::<SettingsStore>().get(location)
+}
+
+pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
+
+#[cfg(any(test, feature = "test-support"))]
+pub fn test_settings() -> String {
+    let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
+        crate::default_settings().as_ref(),
+    )
+    .unwrap();
+    util::merge_non_null_json_value_into(
+        serde_json::json!({
+            "buffer_font_family": "Courier",
+            "buffer_font_features": {},
+            "buffer_font_size": 14,
+            "theme": EMPTY_THEME_NAME,
+        }),
+        &mut value,
+    );
+    value.as_object_mut().unwrap().remove("languages");
+    serde_json::to_string(&value).unwrap()
+}
+
+pub fn watch_config_file(
+    executor: Arc<Background>,
+    fs: Arc<dyn Fs>,
+    path: PathBuf,
+) -> mpsc::UnboundedReceiver<String> {
+    let (tx, rx) = mpsc::unbounded();
+    executor
+        .spawn(async move {
+            let events = fs.watch(&path, Duration::from_millis(100)).await;
+            futures::pin_mut!(events);
+
+            let contents = fs.load(&path).await.unwrap_or_default();
+            if tx.unbounded_send(contents).is_err() {
+                return;
+            }
+
+            loop {
+                if events.next().await.is_none() {
+                    break;
+                }
+
+                if let Ok(contents) = fs.load(&path).await {
+                    if !tx.unbounded_send(contents).is_ok() {
+                        break;
+                    }
+                }
+            }
+        })
+        .detach();
+    rx
+}
+
+pub fn handle_settings_file_changes(
+    mut user_settings_file_rx: mpsc::UnboundedReceiver<String>,
+    cx: &mut AppContext,
+) {
+    let user_settings_content = cx.background().block(user_settings_file_rx.next()).unwrap();
+    cx.update_global(|store: &mut SettingsStore, cx| {
+        store
+            .set_user_settings(&user_settings_content, cx)
+            .log_err();
+    });
+    cx.spawn(move |mut cx| async move {
+        while let Some(user_settings_content) = user_settings_file_rx.next().await {
+            cx.update(|cx| {
+                cx.update_global(|store: &mut SettingsStore, cx| {
+                    store
+                        .set_user_settings(&user_settings_content, cx)
+                        .log_err();
+                });
+                cx.refresh_windows();
+            });
+        }
+    })
+    .detach();
+}
+
+async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
+    match fs.load(&paths::SETTINGS).await {
+        result @ Ok(_) => result,
+        Err(err) => {
+            if let Some(e) = err.downcast_ref::<std::io::Error>() {
+                if e.kind() == ErrorKind::NotFound {
+                    return Ok(crate::initial_user_settings_content().to_string());
+                }
+            }
+            return Err(err);
+        }
+    }
+}
+
+pub fn update_settings_file<T: Setting>(
+    fs: Arc<dyn Fs>,
+    cx: &mut AppContext,
+    update: impl 'static + Send + FnOnce(&mut T::FileContent),
+) {
+    cx.spawn(|cx| async move {
+        let old_text = load_settings(&fs).await;
+        let new_text = cx.read_global(|store: &SettingsStore, cx| {
+            store.new_text_for_update::<T>(old_text, update)
+        });
+        fs.atomic_write(paths::SETTINGS.clone(), new_text).await?;
+        anyhow::Ok(())
+    })
+    .detach_and_log_err(cx);
+}

crates/settings2/src/settings_store.rs 🔗

@@ -0,0 +1,1268 @@
+use anyhow::{anyhow, Context, Result};
+use collections::{btree_map, hash_map, BTreeMap, HashMap};
+use gpui2::AppContext;
+use lazy_static::lazy_static;
+use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
+use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
+use smallvec::SmallVec;
+use std::{
+    any::{type_name, Any, TypeId},
+    fmt::Debug,
+    ops::Range,
+    path::Path,
+    str,
+    sync::Arc,
+};
+use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _};
+
+/// A value that can be defined as a user setting.
+///
+/// Settings can be loaded from a combination of multiple JSON files.
+pub trait Setting: 'static {
+    /// The name of a key within the JSON file from which this setting should
+    /// be deserialized. If this is `None`, then the setting will be deserialized
+    /// from the root object.
+    const KEY: Option<&'static str>;
+
+    /// The type that is stored in an individual JSON file.
+    type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema;
+
+    /// The logic for combining together values from one or more JSON files into the
+    /// final value for this setting.
+    ///
+    /// The user values are ordered from least specific (the global settings file)
+    /// to most specific (the innermost local settings file).
+    fn load(
+        default_value: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+        cx: &AppContext,
+    ) -> Result<Self>
+    where
+        Self: Sized;
+
+    fn json_schema(
+        generator: &mut SchemaGenerator,
+        _: &SettingsJsonSchemaParams,
+        _: &AppContext,
+    ) -> RootSchema {
+        generator.root_schema_for::<Self::FileContent>()
+    }
+
+    fn json_merge(
+        default_value: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+    ) -> Result<Self::FileContent> {
+        let mut merged = serde_json::Value::Null;
+        for value in [default_value].iter().chain(user_values) {
+            merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
+        }
+        Ok(serde_json::from_value(merged)?)
+    }
+
+    fn load_via_json_merge(
+        default_value: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+    ) -> Result<Self>
+    where
+        Self: DeserializeOwned,
+    {
+        let mut merged = serde_json::Value::Null;
+        for value in [default_value].iter().chain(user_values) {
+            merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
+        }
+        Ok(serde_json::from_value(merged)?)
+    }
+
+    fn missing_default() -> anyhow::Error {
+        anyhow::anyhow!("missing default")
+    }
+}
+
+pub struct SettingsJsonSchemaParams<'a> {
+    pub staff_mode: bool,
+    pub language_names: &'a [String],
+}
+
+/// A set of strongly-typed setting values defined via multiple JSON files.
+pub struct SettingsStore {
+    setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
+    raw_default_settings: serde_json::Value,
+    raw_user_settings: serde_json::Value,
+    raw_local_settings: BTreeMap<(usize, Arc<Path>), serde_json::Value>,
+    tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
+}
+
+impl Default for SettingsStore {
+    fn default() -> Self {
+        SettingsStore {
+            setting_values: Default::default(),
+            raw_default_settings: serde_json::json!({}),
+            raw_user_settings: serde_json::json!({}),
+            raw_local_settings: Default::default(),
+            tab_size_callback: Default::default(),
+        }
+    }
+}
+
+#[derive(Debug)]
+struct SettingValue<T> {
+    global_value: Option<T>,
+    local_values: Vec<(usize, Arc<Path>, T)>,
+}
+
+trait AnySettingValue {
+    fn key(&self) -> Option<&'static str>;
+    fn setting_type_name(&self) -> &'static str;
+    fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>;
+    fn load_setting(
+        &self,
+        default_value: &DeserializedSetting,
+        custom: &[DeserializedSetting],
+        cx: &AppContext,
+    ) -> Result<Box<dyn Any>>;
+    fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any;
+    fn set_global_value(&mut self, value: Box<dyn Any>);
+    fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>);
+    fn json_schema(
+        &self,
+        generator: &mut SchemaGenerator,
+        _: &SettingsJsonSchemaParams,
+        cx: &AppContext,
+    ) -> RootSchema;
+}
+
+struct DeserializedSetting(Box<dyn Any>);
+
+impl SettingsStore {
+    /// Add a new type of setting to the store.
+    pub fn register_setting<T: Setting>(&mut self, cx: &AppContext) {
+        let setting_type_id = TypeId::of::<T>();
+        let entry = self.setting_values.entry(setting_type_id);
+        if matches!(entry, hash_map::Entry::Occupied(_)) {
+            return;
+        }
+
+        let setting_value = entry.or_insert(Box::new(SettingValue::<T> {
+            global_value: None,
+            local_values: Vec::new(),
+        }));
+
+        if let Some(default_settings) = setting_value
+            .deserialize_setting(&self.raw_default_settings)
+            .log_err()
+        {
+            let mut user_values_stack = Vec::new();
+
+            if let Some(user_settings) = setting_value
+                .deserialize_setting(&self.raw_user_settings)
+                .log_err()
+            {
+                user_values_stack = vec![user_settings];
+            }
+
+            if let Some(setting) = setting_value
+                .load_setting(&default_settings, &user_values_stack, cx)
+                .context("A default setting must be added to the `default.json` file")
+                .log_err()
+            {
+                setting_value.set_global_value(setting);
+            }
+        }
+    }
+
+    /// Get the value of a setting.
+    ///
+    /// Panics if the given setting type has not been registered, or if there is no
+    /// value for this setting.
+    pub fn get<T: Setting>(&self, path: Option<(usize, &Path)>) -> &T {
+        self.setting_values
+            .get(&TypeId::of::<T>())
+            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
+            .value_for_path(path)
+            .downcast_ref::<T>()
+            .expect("no default value for setting type")
+    }
+
+    /// Override the global value for a setting.
+    ///
+    /// The given value will be overwritten if the user settings file changes.
+    pub fn override_global<T: Setting>(&mut self, value: T) {
+        self.setting_values
+            .get_mut(&TypeId::of::<T>())
+            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
+            .set_global_value(Box::new(value))
+    }
+
+    /// Get the user's settings as a raw JSON value.
+    ///
+    /// This is only for debugging and reporting. For user-facing functionality,
+    /// use the typed setting interface.
+    pub fn raw_user_settings(&self) -> &serde_json::Value {
+        &self.raw_user_settings
+    }
+
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn test(cx: &AppContext) -> Self {
+        let mut this = Self::default();
+        this.set_default_settings(&crate::test_settings(), cx)
+            .unwrap();
+        this.set_user_settings("{}", cx).unwrap();
+        this
+    }
+
+    /// Update the value of a setting in the user's global configuration.
+    ///
+    /// This is only for tests. Normally, settings are only loaded from
+    /// JSON files.
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn update_user_settings<T: Setting>(
+        &mut self,
+        cx: &AppContext,
+        update: impl FnOnce(&mut T::FileContent),
+    ) {
+        let old_text = serde_json::to_string(&self.raw_user_settings).unwrap();
+        let new_text = self.new_text_for_update::<T>(old_text, update);
+        self.set_user_settings(&new_text, cx).unwrap();
+    }
+
+    /// Update the value of a setting in a JSON file, returning the new text
+    /// for that JSON file.
+    pub fn new_text_for_update<T: Setting>(
+        &self,
+        old_text: String,
+        update: impl FnOnce(&mut T::FileContent),
+    ) -> String {
+        let edits = self.edits_for_update::<T>(&old_text, update);
+        let mut new_text = old_text;
+        for (range, replacement) in edits.into_iter() {
+            new_text.replace_range(range, &replacement);
+        }
+        new_text
+    }
+
+    /// Update the value of a setting in a JSON file, returning a list
+    /// of edits to apply to the JSON file.
+    pub fn edits_for_update<T: Setting>(
+        &self,
+        text: &str,
+        update: impl FnOnce(&mut T::FileContent),
+    ) -> Vec<(Range<usize>, String)> {
+        let setting_type_id = TypeId::of::<T>();
+
+        let setting = self
+            .setting_values
+            .get(&setting_type_id)
+            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()));
+        let raw_settings = parse_json_with_comments::<serde_json::Value>(text).unwrap_or_default();
+        let old_content = match setting.deserialize_setting(&raw_settings) {
+            Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
+            Err(_) => Box::new(T::FileContent::default()),
+        };
+        let mut new_content = old_content.clone();
+        update(&mut new_content);
+
+        let old_value = serde_json::to_value(&old_content).unwrap();
+        let new_value = serde_json::to_value(new_content).unwrap();
+
+        let mut key_path = Vec::new();
+        if let Some(key) = T::KEY {
+            key_path.push(key);
+        }
+
+        let mut edits = Vec::new();
+        let tab_size = self.json_tab_size();
+        let mut text = text.to_string();
+        update_value_in_json_text(
+            &mut text,
+            &mut key_path,
+            tab_size,
+            &old_value,
+            &new_value,
+            &mut edits,
+        );
+        return edits;
+    }
+
+    /// Configure the tab sized when updating JSON files.
+    pub fn set_json_tab_size_callback<T: Setting>(
+        &mut self,
+        get_tab_size: fn(&T) -> Option<usize>,
+    ) {
+        self.tab_size_callback = Some((
+            TypeId::of::<T>(),
+            Box::new(move |value| get_tab_size(value.downcast_ref::<T>().unwrap())),
+        ));
+    }
+
+    fn json_tab_size(&self) -> usize {
+        const DEFAULT_JSON_TAB_SIZE: usize = 2;
+
+        if let Some((setting_type_id, callback)) = &self.tab_size_callback {
+            let setting_value = self.setting_values.get(setting_type_id).unwrap();
+            let value = setting_value.value_for_path(None);
+            if let Some(value) = callback(value) {
+                return value;
+            }
+        }
+
+        DEFAULT_JSON_TAB_SIZE
+    }
+
+    /// Set the default settings via a JSON string.
+    ///
+    /// The string should contain a JSON object with a default value for every setting.
+    pub fn set_default_settings(
+        &mut self,
+        default_settings_content: &str,
+        cx: &AppContext,
+    ) -> Result<()> {
+        let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?;
+        if settings.is_object() {
+            self.raw_default_settings = settings;
+            self.recompute_values(None, cx)?;
+            Ok(())
+        } else {
+            Err(anyhow!("settings must be an object"))
+        }
+    }
+
+    /// Set the user settings via a JSON string.
+    pub fn set_user_settings(
+        &mut self,
+        user_settings_content: &str,
+        cx: &AppContext,
+    ) -> Result<()> {
+        let settings: serde_json::Value = parse_json_with_comments(user_settings_content)?;
+        if settings.is_object() {
+            self.raw_user_settings = settings;
+            self.recompute_values(None, cx)?;
+            Ok(())
+        } else {
+            Err(anyhow!("settings must be an object"))
+        }
+    }
+
+    /// Add or remove a set of local settings via a JSON string.
+    pub fn set_local_settings(
+        &mut self,
+        root_id: usize,
+        path: Arc<Path>,
+        settings_content: Option<&str>,
+        cx: &AppContext,
+    ) -> Result<()> {
+        if let Some(content) = settings_content {
+            self.raw_local_settings
+                .insert((root_id, path.clone()), parse_json_with_comments(content)?);
+        } else {
+            self.raw_local_settings.remove(&(root_id, path.clone()));
+        }
+        self.recompute_values(Some((root_id, &path)), cx)?;
+        Ok(())
+    }
+
+    /// Add or remove a set of local settings via a JSON string.
+    pub fn clear_local_settings(&mut self, root_id: usize, cx: &AppContext) -> Result<()> {
+        self.raw_local_settings.retain(|k, _| k.0 != root_id);
+        self.recompute_values(Some((root_id, "".as_ref())), cx)?;
+        Ok(())
+    }
+
+    pub fn local_settings(&self, root_id: usize) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
+        self.raw_local_settings
+            .range((root_id, Path::new("").into())..(root_id + 1, Path::new("").into()))
+            .map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
+    }
+
+    pub fn json_schema(
+        &self,
+        schema_params: &SettingsJsonSchemaParams,
+        cx: &AppContext,
+    ) -> serde_json::Value {
+        use schemars::{
+            gen::SchemaSettings,
+            schema::{Schema, SchemaObject},
+        };
+
+        let settings = SchemaSettings::draft07().with(|settings| {
+            settings.option_add_null_type = false;
+        });
+        let mut generator = SchemaGenerator::new(settings);
+        let mut combined_schema = RootSchema::default();
+
+        for setting_value in self.setting_values.values() {
+            let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx);
+            combined_schema
+                .definitions
+                .extend(setting_schema.definitions);
+
+            let target_schema = if let Some(key) = setting_value.key() {
+                let key_schema = combined_schema
+                    .schema
+                    .object()
+                    .properties
+                    .entry(key.to_string())
+                    .or_insert_with(|| Schema::Object(SchemaObject::default()));
+                if let Schema::Object(key_schema) = key_schema {
+                    key_schema
+                } else {
+                    continue;
+                }
+            } else {
+                &mut combined_schema.schema
+            };
+
+            merge_schema(target_schema, setting_schema.schema);
+        }
+
+        fn merge_schema(target: &mut SchemaObject, source: SchemaObject) {
+            if let Some(source) = source.object {
+                let target_properties = &mut target.object().properties;
+                for (key, value) in source.properties {
+                    match target_properties.entry(key) {
+                        btree_map::Entry::Vacant(e) => {
+                            e.insert(value);
+                        }
+                        btree_map::Entry::Occupied(e) => {
+                            if let (Schema::Object(target), Schema::Object(src)) =
+                                (e.into_mut(), value)
+                            {
+                                merge_schema(target, src);
+                            }
+                        }
+                    }
+                }
+            }
+
+            overwrite(&mut target.instance_type, source.instance_type);
+            overwrite(&mut target.string, source.string);
+            overwrite(&mut target.number, source.number);
+            overwrite(&mut target.reference, source.reference);
+            overwrite(&mut target.array, source.array);
+            overwrite(&mut target.enum_values, source.enum_values);
+
+            fn overwrite<T>(target: &mut Option<T>, source: Option<T>) {
+                if let Some(source) = source {
+                    *target = Some(source);
+                }
+            }
+        }
+
+        serde_json::to_value(&combined_schema).unwrap()
+    }
+
+    fn recompute_values(
+        &mut self,
+        changed_local_path: Option<(usize, &Path)>,
+        cx: &AppContext,
+    ) -> Result<()> {
+        // Reload the global and local values for every setting.
+        let mut user_settings_stack = Vec::<DeserializedSetting>::new();
+        let mut paths_stack = Vec::<Option<(usize, &Path)>>::new();
+        for setting_value in self.setting_values.values_mut() {
+            let default_settings = setting_value.deserialize_setting(&self.raw_default_settings)?;
+
+            user_settings_stack.clear();
+            paths_stack.clear();
+
+            if let Some(user_settings) = setting_value
+                .deserialize_setting(&self.raw_user_settings)
+                .log_err()
+            {
+                user_settings_stack.push(user_settings);
+                paths_stack.push(None);
+            }
+
+            // If the global settings file changed, reload the global value for the field.
+            if changed_local_path.is_none() {
+                if let Some(value) = setting_value
+                    .load_setting(&default_settings, &user_settings_stack, cx)
+                    .log_err()
+                {
+                    setting_value.set_global_value(value);
+                }
+            }
+
+            // Reload the local values for the setting.
+            for ((root_id, path), local_settings) in &self.raw_local_settings {
+                // Build a stack of all of the local values for that setting.
+                while let Some(prev_entry) = paths_stack.last() {
+                    if let Some((prev_root_id, prev_path)) = prev_entry {
+                        if root_id != prev_root_id || !path.starts_with(prev_path) {
+                            paths_stack.pop();
+                            user_settings_stack.pop();
+                            continue;
+                        }
+                    }
+                    break;
+                }
+
+                if let Some(local_settings) =
+                    setting_value.deserialize_setting(&local_settings).log_err()
+                {
+                    paths_stack.push(Some((*root_id, path.as_ref())));
+                    user_settings_stack.push(local_settings);
+
+                    // If a local settings file changed, then avoid recomputing local
+                    // settings for any path outside of that directory.
+                    if changed_local_path.map_or(false, |(changed_root_id, changed_local_path)| {
+                        *root_id != changed_root_id || !path.starts_with(changed_local_path)
+                    }) {
+                        continue;
+                    }
+
+                    if let Some(value) = setting_value
+                        .load_setting(&default_settings, &user_settings_stack, cx)
+                        .log_err()
+                    {
+                        setting_value.set_local_value(*root_id, path.clone(), value);
+                    }
+                }
+            }
+        }
+        Ok(())
+    }
+}
+
+impl Debug for SettingsStore {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("SettingsStore")
+            .field(
+                "types",
+                &self
+                    .setting_values
+                    .values()
+                    .map(|value| value.setting_type_name())
+                    .collect::<Vec<_>>(),
+            )
+            .field("default_settings", &self.raw_default_settings)
+            .field("user_settings", &self.raw_user_settings)
+            .field("local_settings", &self.raw_local_settings)
+            .finish_non_exhaustive()
+    }
+}
+
+impl<T: Setting> AnySettingValue for SettingValue<T> {
+    fn key(&self) -> Option<&'static str> {
+        T::KEY
+    }
+
+    fn setting_type_name(&self) -> &'static str {
+        type_name::<T>()
+    }
+
+    fn load_setting(
+        &self,
+        default_value: &DeserializedSetting,
+        user_values: &[DeserializedSetting],
+        cx: &AppContext,
+    ) -> Result<Box<dyn Any>> {
+        let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap();
+        let values: SmallVec<[&T::FileContent; 6]> = user_values
+            .iter()
+            .map(|value| value.0.downcast_ref().unwrap())
+            .collect();
+        Ok(Box::new(T::load(default_value, &values, cx)?))
+    }
+
+    fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> {
+        if let Some(key) = T::KEY {
+            if let Some(value) = json.get(key) {
+                json = value;
+            } else {
+                let value = T::FileContent::default();
+                return Ok(DeserializedSetting(Box::new(value)));
+            }
+        }
+        let value = T::FileContent::deserialize(json)?;
+        Ok(DeserializedSetting(Box::new(value)))
+    }
+
+    fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any {
+        if let Some((root_id, path)) = path {
+            for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
+                if root_id == *settings_root_id && path.starts_with(&settings_path) {
+                    return value;
+                }
+            }
+        }
+        self.global_value
+            .as_ref()
+            .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
+    }
+
+    fn set_global_value(&mut self, value: Box<dyn Any>) {
+        self.global_value = Some(*value.downcast().unwrap());
+    }
+
+    fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>) {
+        let value = *value.downcast().unwrap();
+        match self
+            .local_values
+            .binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
+        {
+            Ok(ix) => self.local_values[ix].2 = value,
+            Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
+        }
+    }
+
+    fn json_schema(
+        &self,
+        generator: &mut SchemaGenerator,
+        params: &SettingsJsonSchemaParams,
+        cx: &AppContext,
+    ) -> RootSchema {
+        T::json_schema(generator, params, cx)
+    }
+}
+
+fn update_value_in_json_text<'a>(
+    text: &mut String,
+    key_path: &mut Vec<&'a str>,
+    tab_size: usize,
+    old_value: &'a serde_json::Value,
+    new_value: &'a serde_json::Value,
+    edits: &mut Vec<(Range<usize>, String)>,
+) {
+    // If the old and new values are both objects, then compare them key by key,
+    // preserving the comments and formatting of the unchanged parts. Otherwise,
+    // replace the old value with the new value.
+    if let (serde_json::Value::Object(old_object), serde_json::Value::Object(new_object)) =
+        (old_value, new_value)
+    {
+        for (key, old_sub_value) in old_object.iter() {
+            key_path.push(key);
+            let new_sub_value = new_object.get(key).unwrap_or(&serde_json::Value::Null);
+            update_value_in_json_text(
+                text,
+                key_path,
+                tab_size,
+                old_sub_value,
+                new_sub_value,
+                edits,
+            );
+            key_path.pop();
+        }
+        for (key, new_sub_value) in new_object.iter() {
+            key_path.push(key);
+            if !old_object.contains_key(key) {
+                update_value_in_json_text(
+                    text,
+                    key_path,
+                    tab_size,
+                    &serde_json::Value::Null,
+                    new_sub_value,
+                    edits,
+                );
+            }
+            key_path.pop();
+        }
+    } else if old_value != new_value {
+        let mut new_value = new_value.clone();
+        if let Some(new_object) = new_value.as_object_mut() {
+            new_object.retain(|_, v| !v.is_null());
+        }
+        let (range, replacement) =
+            replace_value_in_json_text(text, &key_path, tab_size, &new_value);
+        text.replace_range(range.clone(), &replacement);
+        edits.push((range, replacement));
+    }
+}
+
+fn replace_value_in_json_text(
+    text: &str,
+    key_path: &[&str],
+    tab_size: usize,
+    new_value: &serde_json::Value,
+) -> (Range<usize>, String) {
+    const LANGUAGE_OVERRIDES: &'static str = "language_overrides";
+    const LANGUAGES: &'static str = "languages";
+
+    lazy_static! {
+        static ref PAIR_QUERY: tree_sitter::Query = tree_sitter::Query::new(
+            tree_sitter_json::language(),
+            "(pair key: (string) @key value: (_) @value)",
+        )
+        .unwrap();
+    }
+
+    let mut parser = tree_sitter::Parser::new();
+    parser.set_language(tree_sitter_json::language()).unwrap();
+    let syntax_tree = parser.parse(text, None).unwrap();
+
+    let mut cursor = tree_sitter::QueryCursor::new();
+
+    let has_language_overrides = text.contains(LANGUAGE_OVERRIDES);
+
+    let mut depth = 0;
+    let mut last_value_range = 0..0;
+    let mut first_key_start = None;
+    let mut existing_value_range = 0..text.len();
+    let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes());
+    for mat in matches {
+        if mat.captures.len() != 2 {
+            continue;
+        }
+
+        let key_range = mat.captures[0].node.byte_range();
+        let value_range = mat.captures[1].node.byte_range();
+
+        // Don't enter sub objects until we find an exact
+        // match for the current keypath
+        if last_value_range.contains_inclusive(&value_range) {
+            continue;
+        }
+
+        last_value_range = value_range.clone();
+
+        if key_range.start > existing_value_range.end {
+            break;
+        }
+
+        first_key_start.get_or_insert_with(|| key_range.start);
+
+        let found_key = text
+            .get(key_range.clone())
+            .map(|key_text| {
+                if key_path[depth] == LANGUAGES && has_language_overrides {
+                    return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES);
+                } else {
+                    return key_text == format!("\"{}\"", key_path[depth]);
+                }
+            })
+            .unwrap_or(false);
+
+        if found_key {
+            existing_value_range = value_range;
+            // Reset last value range when increasing in depth
+            last_value_range = existing_value_range.start..existing_value_range.start;
+            depth += 1;
+
+            if depth == key_path.len() {
+                break;
+            } else {
+                first_key_start = None;
+            }
+        }
+    }
+
+    // We found the exact key we want, insert the new value
+    if depth == key_path.len() {
+        let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth);
+        (existing_value_range, new_val)
+    } else {
+        // We have key paths, construct the sub objects
+        let new_key = if has_language_overrides && key_path[depth] == LANGUAGES {
+            LANGUAGE_OVERRIDES
+        } else {
+            key_path[depth]
+        };
+
+        // We don't have the key, construct the nested objects
+        let mut new_value = serde_json::to_value(new_value).unwrap();
+        for key in key_path[(depth + 1)..].iter().rev() {
+            if has_language_overrides && key == &LANGUAGES {
+                new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value });
+            } else {
+                new_value = serde_json::json!({ key.to_string(): new_value });
+            }
+        }
+
+        if let Some(first_key_start) = first_key_start {
+            let mut row = 0;
+            let mut column = 0;
+            for (ix, char) in text.char_indices() {
+                if ix == first_key_start {
+                    break;
+                }
+                if char == '\n' {
+                    row += 1;
+                    column = 0;
+                } else {
+                    column += char.len_utf8();
+                }
+            }
+
+            if row > 0 {
+                // depth is 0 based, but division needs to be 1 based.
+                let new_val = to_pretty_json(&new_value, column / (depth + 1), column);
+                let space = ' ';
+                let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column);
+                (first_key_start..first_key_start, content)
+            } else {
+                let new_val = serde_json::to_string(&new_value).unwrap();
+                let mut content = format!(r#""{new_key}": {new_val},"#);
+                content.push(' ');
+                (first_key_start..first_key_start, content)
+            }
+        } else {
+            new_value = serde_json::json!({ new_key.to_string(): new_value });
+            let indent_prefix_len = 4 * depth;
+            let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
+            if depth == 0 {
+                new_val.push('\n');
+            }
+
+            (existing_value_range, new_val)
+        }
+    }
+}
+
+fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String {
+    const SPACES: [u8; 32] = [b' '; 32];
+
+    debug_assert!(indent_size <= SPACES.len());
+    debug_assert!(indent_prefix_len <= SPACES.len());
+
+    let mut output = Vec::new();
+    let mut ser = serde_json::Serializer::with_formatter(
+        &mut output,
+        serde_json::ser::PrettyFormatter::with_indent(&SPACES[0..indent_size.min(SPACES.len())]),
+    );
+
+    value.serialize(&mut ser).unwrap();
+    let text = String::from_utf8(output).unwrap();
+
+    let mut adjusted_text = String::new();
+    for (i, line) in text.split('\n').enumerate() {
+        if i > 0 {
+            adjusted_text.push_str(str::from_utf8(&SPACES[0..indent_prefix_len]).unwrap());
+        }
+        adjusted_text.push_str(line);
+        adjusted_text.push('\n');
+    }
+    adjusted_text.pop();
+    adjusted_text
+}
+
+pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
+    Ok(serde_json_lenient::from_str(content)?)
+}
+
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use serde_derive::Deserialize;
+//     use unindent::Unindent;
+
+//     #[gpui::test]
+//     fn test_settings_store_basic(cx: &mut AppContext) {
+//         let mut store = SettingsStore::default();
+//         store.register_setting::<UserSettings>(cx);
+//         store.register_setting::<TurboSetting>(cx);
+//         store.register_setting::<MultiKeySettings>(cx);
+//         store
+//             .set_default_settings(
+//                 r#"{
+//                     "turbo": false,
+//                     "user": {
+//                         "name": "John Doe",
+//                         "age": 30,
+//                         "staff": false
+//                     }
+//                 }"#,
+//                 cx,
+//             )
+//             .unwrap();
+
+//         assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
+//         assert_eq!(
+//             store.get::<UserSettings>(None),
+//             &UserSettings {
+//                 name: "John Doe".to_string(),
+//                 age: 30,
+//                 staff: false,
+//             }
+//         );
+//         assert_eq!(
+//             store.get::<MultiKeySettings>(None),
+//             &MultiKeySettings {
+//                 key1: String::new(),
+//                 key2: String::new(),
+//             }
+//         );
+
+//         store
+//             .set_user_settings(
+//                 r#"{
+//                     "turbo": true,
+//                     "user": { "age": 31 },
+//                     "key1": "a"
+//                 }"#,
+//                 cx,
+//             )
+//             .unwrap();
+
+//         assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
+//         assert_eq!(
+//             store.get::<UserSettings>(None),
+//             &UserSettings {
+//                 name: "John Doe".to_string(),
+//                 age: 31,
+//                 staff: false
+//             }
+//         );
+
+//         store
+//             .set_local_settings(
+//                 1,
+//                 Path::new("/root1").into(),
+//                 Some(r#"{ "user": { "staff": true } }"#),
+//                 cx,
+//             )
+//             .unwrap();
+//         store
+//             .set_local_settings(
+//                 1,
+//                 Path::new("/root1/subdir").into(),
+//                 Some(r#"{ "user": { "name": "Jane Doe" } }"#),
+//                 cx,
+//             )
+//             .unwrap();
+
+//         store
+//             .set_local_settings(
+//                 1,
+//                 Path::new("/root2").into(),
+//                 Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
+//                 cx,
+//             )
+//             .unwrap();
+
+//         assert_eq!(
+//             store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
+//             &UserSettings {
+//                 name: "John Doe".to_string(),
+//                 age: 31,
+//                 staff: true
+//             }
+//         );
+//         assert_eq!(
+//             store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
+//             &UserSettings {
+//                 name: "Jane Doe".to_string(),
+//                 age: 31,
+//                 staff: true
+//             }
+//         );
+//         assert_eq!(
+//             store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
+//             &UserSettings {
+//                 name: "John Doe".to_string(),
+//                 age: 42,
+//                 staff: false
+//             }
+//         );
+//         assert_eq!(
+//             store.get::<MultiKeySettings>(Some((1, Path::new("/root2/something")))),
+//             &MultiKeySettings {
+//                 key1: "a".to_string(),
+//                 key2: "b".to_string(),
+//             }
+//         );
+//     }
+
+//     #[gpui::test]
+//     fn test_setting_store_assign_json_before_register(cx: &mut AppContext) {
+//         let mut store = SettingsStore::default();
+//         store
+//             .set_default_settings(
+//                 r#"{
+//                     "turbo": true,
+//                     "user": {
+//                         "name": "John Doe",
+//                         "age": 30,
+//                         "staff": false
+//                     },
+//                     "key1": "x"
+//                 }"#,
+//                 cx,
+//             )
+//             .unwrap();
+//         store
+//             .set_user_settings(r#"{ "turbo": false }"#, cx)
+//             .unwrap();
+//         store.register_setting::<UserSettings>(cx);
+//         store.register_setting::<TurboSetting>(cx);
+
+//         assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
+//         assert_eq!(
+//             store.get::<UserSettings>(None),
+//             &UserSettings {
+//                 name: "John Doe".to_string(),
+//                 age: 30,
+//                 staff: false,
+//             }
+//         );
+
+//         store.register_setting::<MultiKeySettings>(cx);
+//         assert_eq!(
+//             store.get::<MultiKeySettings>(None),
+//             &MultiKeySettings {
+//                 key1: "x".into(),
+//                 key2: String::new(),
+//             }
+//         );
+//     }
+
+//     #[gpui::test]
+//     fn test_setting_store_update(cx: &mut AppContext) {
+//         let mut store = SettingsStore::default();
+//         store.register_setting::<MultiKeySettings>(cx);
+//         store.register_setting::<UserSettings>(cx);
+//         store.register_setting::<LanguageSettings>(cx);
+
+//         // entries added and updated
+//         check_settings_update::<LanguageSettings>(
+//             &mut store,
+//             r#"{
+//                 "languages": {
+//                     "JSON": {
+//                         "language_setting_1": true
+//                     }
+//                 }
+//             }"#
+//             .unindent(),
+//             |settings| {
+//                 settings
+//                     .languages
+//                     .get_mut("JSON")
+//                     .unwrap()
+//                     .language_setting_1 = Some(false);
+//                 settings.languages.insert(
+//                     "Rust".into(),
+//                     LanguageSettingEntry {
+//                         language_setting_2: Some(true),
+//                         ..Default::default()
+//                     },
+//                 );
+//             },
+//             r#"{
+//                 "languages": {
+//                     "Rust": {
+//                         "language_setting_2": true
+//                     },
+//                     "JSON": {
+//                         "language_setting_1": false
+//                     }
+//                 }
+//             }"#
+//             .unindent(),
+//             cx,
+//         );
+
+//         // weird formatting
+//         check_settings_update::<UserSettings>(
+//             &mut store,
+//             r#"{
+//                 "user":   { "age": 36, "name": "Max", "staff": true }
+//             }"#
+//             .unindent(),
+//             |settings| settings.age = Some(37),
+//             r#"{
+//                 "user":   { "age": 37, "name": "Max", "staff": true }
+//             }"#
+//             .unindent(),
+//             cx,
+//         );
+
+//         // single-line formatting, other keys
+//         check_settings_update::<MultiKeySettings>(
+//             &mut store,
+//             r#"{ "one": 1, "two": 2 }"#.unindent(),
+//             |settings| settings.key1 = Some("x".into()),
+//             r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
+//             cx,
+//         );
+
+//         // empty object
+//         check_settings_update::<UserSettings>(
+//             &mut store,
+//             r#"{
+//                 "user": {}
+//             }"#
+//             .unindent(),
+//             |settings| settings.age = Some(37),
+//             r#"{
+//                 "user": {
+//                     "age": 37
+//                 }
+//             }"#
+//             .unindent(),
+//             cx,
+//         );
+
+//         // no content
+//         check_settings_update::<UserSettings>(
+//             &mut store,
+//             r#""#.unindent(),
+//             |settings| settings.age = Some(37),
+//             r#"{
+//                 "user": {
+//                     "age": 37
+//                 }
+//             }
+//             "#
+//             .unindent(),
+//             cx,
+//         );
+
+//         check_settings_update::<UserSettings>(
+//             &mut store,
+//             r#"{
+//             }
+//             "#
+//             .unindent(),
+//             |settings| settings.age = Some(37),
+//             r#"{
+//                 "user": {
+//                     "age": 37
+//                 }
+//             }
+//             "#
+//             .unindent(),
+//             cx,
+//         );
+//     }
+
+//     fn check_settings_update<T: Setting>(
+//         store: &mut SettingsStore,
+//         old_json: String,
+//         update: fn(&mut T::FileContent),
+//         expected_new_json: String,
+//         cx: &mut AppContext,
+//     ) {
+//         store.set_user_settings(&old_json, cx).ok();
+//         let edits = store.edits_for_update::<T>(&old_json, update);
+//         let mut new_json = old_json;
+//         for (range, replacement) in edits.into_iter() {
+//             new_json.replace_range(range, &replacement);
+//         }
+//         pretty_assertions::assert_eq!(new_json, expected_new_json);
+//     }
+
+//     #[derive(Debug, PartialEq, Deserialize)]
+//     struct UserSettings {
+//         name: String,
+//         age: u32,
+//         staff: bool,
+//     }
+
+//     #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
+//     struct UserSettingsJson {
+//         name: Option<String>,
+//         age: Option<u32>,
+//         staff: Option<bool>,
+//     }
+
+//     impl Setting for UserSettings {
+//         const KEY: Option<&'static str> = Some("user");
+//         type FileContent = UserSettingsJson;
+
+//         fn load(
+//             default_value: &UserSettingsJson,
+//             user_values: &[&UserSettingsJson],
+//             _: &AppContext,
+//         ) -> Result<Self> {
+//             Self::load_via_json_merge(default_value, user_values)
+//         }
+//     }
+
+//     #[derive(Debug, Deserialize, PartialEq)]
+//     struct TurboSetting(bool);
+
+//     impl Setting for TurboSetting {
+//         const KEY: Option<&'static str> = Some("turbo");
+//         type FileContent = Option<bool>;
+
+//         fn load(
+//             default_value: &Option<bool>,
+//             user_values: &[&Option<bool>],
+//             _: &AppContext,
+//         ) -> Result<Self> {
+//             Self::load_via_json_merge(default_value, user_values)
+//         }
+//     }
+
+//     #[derive(Clone, Debug, PartialEq, Deserialize)]
+//     struct MultiKeySettings {
+//         #[serde(default)]
+//         key1: String,
+//         #[serde(default)]
+//         key2: String,
+//     }
+
+//     #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+//     struct MultiKeySettingsJson {
+//         key1: Option<String>,
+//         key2: Option<String>,
+//     }
+
+//     impl Setting for MultiKeySettings {
+//         const KEY: Option<&'static str> = None;
+
+//         type FileContent = MultiKeySettingsJson;
+
+//         fn load(
+//             default_value: &MultiKeySettingsJson,
+//             user_values: &[&MultiKeySettingsJson],
+//             _: &AppContext,
+//         ) -> Result<Self> {
+//             Self::load_via_json_merge(default_value, user_values)
+//         }
+//     }
+
+//     #[derive(Debug, Deserialize)]
+//     struct JournalSettings {
+//         pub path: String,
+//         pub hour_format: HourFormat,
+//     }
+
+//     #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
+//     #[serde(rename_all = "snake_case")]
+//     enum HourFormat {
+//         Hour12,
+//         Hour24,
+//     }
+
+//     #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
+//     struct JournalSettingsJson {
+//         pub path: Option<String>,
+//         pub hour_format: Option<HourFormat>,
+//     }
+
+//     impl Setting for JournalSettings {
+//         const KEY: Option<&'static str> = Some("journal");
+
+//         type FileContent = JournalSettingsJson;
+
+//         fn load(
+//             default_value: &JournalSettingsJson,
+//             user_values: &[&JournalSettingsJson],
+//             _: &AppContext,
+//         ) -> Result<Self> {
+//             Self::load_via_json_merge(default_value, user_values)
+//         }
+//     }
+
+//     #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+//     struct LanguageSettings {
+//         #[serde(default)]
+//         languages: HashMap<String, LanguageSettingEntry>,
+//     }
+
+//     #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+//     struct LanguageSettingEntry {
+//         language_setting_1: Option<bool>,
+//         language_setting_2: Option<bool>,
+//     }
+
+//     impl Setting for LanguageSettings {
+//         const KEY: Option<&'static str> = None;
+
+//         type FileContent = Self;
+
+//         fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
+//             Self::load_via_json_merge(default_value, user_values)
+//         }
+//     }
+// }

crates/storybook2/Cargo.toml 🔗

@@ -28,4 +28,4 @@ ui = { package = "ui2", path = "../ui2", features = ["stories"] }
 util = { path = "../util" }
 
 [dev-dependencies]
-gpui2 = { path = "../gpui2", features = ["test"] }
+gpui2 = { path = "../gpui2", features = ["test-support"] }

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

@@ -3,14 +3,15 @@ use gpui2::{
     div, view, Context, Focusable, KeyBinding, ParentElement, StatelessInteractive, Styled, View,
     WindowContext,
 };
+use serde::Deserialize;
 
-#[derive(Clone, PartialEq)]
+#[derive(Clone, Default, PartialEq, Deserialize)]
 struct ActionA;
 
-#[derive(Clone, PartialEq)]
+#[derive(Clone, Default, PartialEq, Deserialize)]
 struct ActionB;
 
-#[derive(Clone, PartialEq)]
+#[derive(Clone, Default, PartialEq, Deserialize)]
 struct ActionC;
 
 pub struct FocusStory {
@@ -24,6 +25,8 @@ impl FocusStory {
             KeyBinding::new("cmd-a", ActionB, Some("child-1")),
             KeyBinding::new("cmd-c", ActionC, None),
         ]);
+        cx.register_action_type::<ActionA>();
+        cx.register_action_type::<ActionB>();
         let theme = rose_pine();
 
         let color_1 = theme.lowest.negative.default.foreground;

crates/zed2/Cargo.toml 🔗

@@ -0,0 +1,180 @@
+[package]
+description = "The fast, collaborative code editor."
+edition = "2021"
+name = "zed2"
+version = "0.109.0"
+publish = false
+
+[lib]
+name = "zed2"
+path = "src/zed2.rs"
+doctest = false
+
+[[bin]]
+name = "Zed"
+path = "src/main.rs"
+
+[dependencies]
+# audio = { path = "../audio" }
+# activity_indicator = { path = "../activity_indicator" }
+# auto_update = { path = "../auto_update" }
+# breadcrumbs = { path = "../breadcrumbs" }
+# call = { path = "../call" }
+# channel = { path = "../channel" }
+cli = { path = "../cli" }
+# collab_ui = { path = "../collab_ui" }
+collections = { path = "../collections" }
+# command_palette = { path = "../command_palette" }
+# component_test = { path = "../component_test" }
+# context_menu = { path = "../context_menu" }
+# client = { path = "../client" }
+# clock = { path = "../clock" }
+# copilot = { path = "../copilot" }
+# copilot_button = { path = "../copilot_button" }
+# diagnostics = { path = "../diagnostics" }
+# db = { path = "../db" }
+# editor = { path = "../editor" }
+# feedback = { path = "../feedback" }
+# file_finder = { path = "../file_finder" }
+# search = { path = "../search" }
+fs = { path = "../fs" }
+fsevent = { path = "../fsevent" }
+fuzzy = { path = "../fuzzy" }
+# go_to_line = { path = "../go_to_line" }
+gpui2 = { path = "../gpui2" }
+install_cli = { path = "../install_cli" }
+# journal = { path = "../journal" }
+# language = { path = "../language" }
+# language_selector = { path = "../language_selector" }
+lsp = { path = "../lsp" }
+language_tools = { path = "../language_tools" }
+node_runtime = { path = "../node_runtime" }
+# assistant = { path = "../assistant" }
+# outline = { path = "../outline" }
+# plugin_runtime = { path = "../plugin_runtime",optional = true }
+# project = { path = "../project" }
+# project_panel = { path = "../project_panel" }
+# project_symbols = { path = "../project_symbols" }
+# quick_action_bar = { path = "../quick_action_bar" }
+# recent_projects = { path = "../recent_projects" }
+rpc = { path = "../rpc" }
+settings2 = { path = "../settings2" }
+feature_flags = { path = "../feature_flags" }
+sum_tree = { path = "../sum_tree" }
+shellexpand = "2.1.0"
+text = { path = "../text" }
+# terminal_view = { path = "../terminal_view" }
+# theme = { path = "../theme" }
+# theme_selector = { path = "../theme_selector" }
+util = { path = "../util" }
+# semantic_index = { path = "../semantic_index" }
+# vim = { path = "../vim" }
+# workspace = { path = "../workspace" }
+# welcome = { path = "../welcome" }
+# zed-actions = {path = "../zed-actions"}
+anyhow.workspace = true
+async-compression = { version = "0.3", features = ["gzip", "futures-bufread"] }
+async-tar = "0.4.2"
+async-recursion = "0.3"
+async-trait.workspace = true
+backtrace = "0.3"
+chrono = "0.4"
+ctor = "0.1.20"
+env_logger.workspace = true
+futures.workspace = true
+ignore = "0.4"
+image = "0.23"
+indexmap = "1.6.2"
+isahc.workspace = true
+lazy_static.workspace = true
+libc = "0.2"
+log.workspace = true
+num_cpus = "1.13.0"
+parking_lot.workspace = true
+postage.workspace = true
+rand.workspace = true
+regex.workspace = true
+rsa = "0.4"
+rust-embed.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+schemars.workspace = true
+simplelog = "0.9"
+smallvec.workspace = true
+smol.workspace = true
+tempdir.workspace = true
+thiserror.workspace = true
+tiny_http = "0.8"
+toml.workspace = true
+tree-sitter.workspace = true
+tree-sitter-bash.workspace = true
+tree-sitter-c.workspace = true
+tree-sitter-cpp.workspace = true
+tree-sitter-css.workspace = true
+tree-sitter-elixir.workspace = true
+tree-sitter-elm.workspace = true
+tree-sitter-embedded-template.workspace = true
+tree-sitter-glsl.workspace = true
+tree-sitter-go.workspace = true
+tree-sitter-heex.workspace = true
+tree-sitter-json.workspace = true
+tree-sitter-rust.workspace = true
+tree-sitter-markdown.workspace = true
+tree-sitter-python.workspace = true
+tree-sitter-toml.workspace = true
+tree-sitter-typescript.workspace = true
+tree-sitter-ruby.workspace = true
+tree-sitter-html.workspace = true
+tree-sitter-php.workspace = true
+tree-sitter-scheme.workspace = true
+tree-sitter-svelte.workspace = true
+tree-sitter-racket.workspace = true
+tree-sitter-yaml.workspace = true
+tree-sitter-lua.workspace = true
+tree-sitter-nix.workspace = true
+tree-sitter-nu.workspace = true
+
+url = "2.2"
+urlencoding = "2.1.2"
+uuid.workspace = true
+
+[dev-dependencies]
+# call = { path = "../call", features = ["test-support"] }
+# client = { path = "../client", features = ["test-support"] }
+# editor = { path = "../editor", features = ["test-support"] }
+# gpui = { path = "../gpui", features = ["test-support"] }
+gpui2 = { path = "../gpui2", features = ["test-support"] }
+# language = { path = "../language", features = ["test-support"] }
+# lsp = { path = "../lsp", features = ["test-support"] }
+# project = { path = "../project", features = ["test-support"] }
+# rpc = { path = "../rpc", features = ["test-support"] }
+# settings = { path = "../settings", features = ["test-support"] }
+# text = { path = "../text", features = ["test-support"] }
+# util = { path = "../util", features = ["test-support"] }
+# workspace = { path = "../workspace", features = ["test-support"] }
+unindent.workspace = true
+
+[package.metadata.bundle-dev]
+icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]
+identifier = "dev.zed.Zed-Dev"
+name = "Zed Dev"
+osx_minimum_system_version = "10.15.7"
+osx_info_plist_exts = ["resources/info/*"]
+osx_url_schemes = ["zed-dev"]
+
+[package.metadata.bundle-preview]
+icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]
+identifier = "dev.zed.Zed-Preview"
+name = "Zed Preview"
+osx_minimum_system_version = "10.15.7"
+osx_info_plist_exts = ["resources/info/*"]
+osx_url_schemes = ["zed-preview"]
+
+[package.metadata.bundle-stable]
+icon = ["resources/app-icon@2x.png", "resources/app-icon.png"]
+identifier = "dev.zed.Zed"
+name = "Zed"
+osx_minimum_system_version = "10.15.7"
+osx_info_plist_exts = ["resources/info/*"]
+osx_url_schemes = ["zed"]

crates/zed2/resources/info/DocumentTypes.plist 🔗

@@ -0,0 +1,62 @@
+<key>CFBundleDocumentTypes</key>
+<array>
+    <dict>
+        <key>CFBundleTypeIconFile</key>
+        <string>Document</string>
+        <key>CFBundleTypeRole</key>
+        <string>Editor</string>
+        <key>LSHandlerRank</key>
+        <string>Alternate</string>
+        <key>LSItemContentTypes</key>
+        <array>
+            <string>public.text</string>
+            <string>public.plain-text</string>
+            <string>public.utf8-plain-text</string>
+        </array>
+    </dict>
+    <dict>
+        <key>CFBundleTypeIconFile</key>
+        <string>Document</string>
+        <key>CFBundleTypeName</key>
+        <string>Zed Text Document</string>
+        <key>CFBundleTypeRole</key>
+        <string>Editor</string>
+        <key>CFBundleTypeOSTypes</key>
+        <array>
+            <string>****</string>
+        </array>
+        <key>LSHandlerRank</key>
+        <string>Default</string>
+        <key>CFBundleTypeExtensions</key>
+        <array>
+            <string>Gemfile</string>
+            <string>c</string>
+            <string>c++</string>
+            <string>cc</string>
+            <string>cpp</string>
+            <string>css</string>
+            <string>erb</string>
+            <string>ex</string>
+            <string>exs</string>
+            <string>go</string>
+            <string>h</string>
+            <string>h++</string>
+            <string>hh</string>
+            <string>hpp</string>
+            <string>html</string>
+            <string>js</string>
+            <string>json</string>
+            <string>jsx</string>
+            <string>md</string>
+            <string>py</string>
+            <string>rb</string>
+            <string>rkt</string>
+            <string>rs</string>
+            <string>scm</string>
+            <string>toml</string>
+            <string>ts</string>
+            <string>tsx</string>
+            <string>txt</string>
+        </array>
+    </dict>
+</array>

crates/zed2/resources/info/Permissions.plist 🔗

@@ -0,0 +1,24 @@
+<key>NSSystemAdministrationUsageDescription</key>
+<string>The operation being performed by a program in Zed requires elevated permission.</string>
+<key>NSAppleEventsUsageDescription</key>
+<string>An application in Zed wants to use AppleScript.</string>
+<key>NSBluetoothAlwaysUsageDescription</key>
+<string>An application in Zed wants to use Bluetooth.</string>
+<key>NSCalendarsUsageDescription</key>
+<string>An application in Zed wants to use Calendar data.</string>
+<key>NSCameraUsageDescription</key>
+<string>An application in Zed wants to use the camera.</string>
+<key>NSContactsUsageDescription</key>
+<string>An application in Zed wants to use your contacts.</string>
+<key>NSLocationAlwaysUsageDescription</key>
+<string>An application in Zed wants to use your location information, even in the background.</string>
+<key>NSLocationUsageDescription</key>
+<string>An application in Zed wants to use your location information.</string>
+<key>NSLocationWhenInUseUsageDescription</key>
+<string>An application in Zed wants to use your location information while active.</string>
+<key>NSMicrophoneUsageDescription</key>
+<string>An application in Zed wants to use your microphone.</string>
+<key>NSSpeechRecognitionUsageDescription</key>
+<string>An application in Zed wants to use speech recognition.</string>
+<key>NSRemindersUsageDescription</key>
+<string>An application in Zed wants to use your reminders.</string>

crates/zed2/resources/zed.entitlements 🔗

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.automation.apple-events</key>
+	<true/>
+	<key>com.apple.security.cs.allow-jit</key>
+	<true/>
+	<key>com.apple.security.device.audio-input</key>
+	<true/>
+	<key>com.apple.security.device.camera</key>
+	<true/>
+	<key>com.apple.security.personal-information.addressbook</key>
+	<true/>
+	<key>com.apple.security.personal-information.calendars</key>
+	<true/>
+	<key>com.apple.security.personal-information.location</key>
+	<true/>
+	<key>com.apple.security.personal-information.photos-library</key>
+	<true/>
+	<!-- <key>com.apple.security.cs.disable-library-validation</key>
+	<true/> -->
+</dict>
+</plist>

crates/zed2/src/assets.rs 🔗

@@ -0,0 +1,33 @@
+use anyhow::anyhow;
+use gpui2::{AssetSource, Result, SharedString};
+use rust_embed::RustEmbed;
+
+#[derive(RustEmbed)]
+#[folder = "../../assets"]
+#[include = "fonts/**/*"]
+#[include = "icons/**/*"]
+#[include = "themes/**/*"]
+#[include = "sounds/**/*"]
+#[include = "*.md"]
+#[exclude = "*.DS_Store"]
+pub struct Assets;
+
+impl AssetSource for Assets {
+    fn load(&self, path: &SharedString) -> Result<std::borrow::Cow<[u8]>> {
+        Self::get(path)
+            .map(|f| f.data)
+            .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+    }
+
+    fn list(&self, path: &SharedString) -> Result<Vec<SharedString>> {
+        Ok(Self::iter()
+            .filter_map(|p| {
+                if p.starts_with(path.as_ref()) {
+                    Some(p.into())
+                } else {
+                    None
+                }
+            })
+            .collect())
+    }
+}

crates/zed2/src/main.rs 🔗

@@ -0,0 +1,912 @@
+// Allow binary to be called Zed for a nice application menu when running executable directly
+#![allow(non_snake_case)]
+
+use crate::open_listener::{OpenListener, OpenRequest};
+use anyhow::{anyhow, Context, Result};
+use cli::{
+    ipc::{self, IpcSender},
+    CliRequest, CliResponse, IpcHandshake, FORCE_CLI_MODE_ENV_VAR_NAME,
+};
+use fs::RealFs;
+use futures::{channel::mpsc, SinkExt, StreamExt};
+use gpui2::{App, AsyncAppContext, Task};
+use log::LevelFilter;
+
+use parking_lot::Mutex;
+use serde::{Deserialize, Serialize};
+use settings::{default_settings, handle_settings_file_changes, watch_config_file, SettingsStore};
+use simplelog::ConfigBuilder;
+use smol::process::Command;
+use std::{
+    collections::HashMap,
+    env,
+    fs::OpenOptions,
+    io::IsTerminal,
+    path::Path,
+    sync::{
+        atomic::{AtomicU32, Ordering},
+        Arc, Weak,
+    },
+    thread,
+};
+use util::{channel::RELEASE_CHANNEL, http, paths, ResultExt};
+use zed2::{ensure_only_instance, AppState, Assets, IsOnlyInstance};
+// use zed2::{
+//     assets::Assets,
+//     build_window_options, handle_keymap_file_changes, initialize_workspace, languages, menus,
+//     only_instance::{ensure_only_instance, IsOnlyInstance},
+// };
+
+mod open_listener;
+
+fn main() {
+    let http = http::client();
+    init_paths();
+    init_logger();
+
+    if ensure_only_instance() != IsOnlyInstance::Yes {
+        return;
+    }
+
+    log::info!("========== starting zed ==========");
+    let mut app = App::production(Arc::new(Assets));
+
+    // let installation_id = app.background().block(installation_id()).ok();
+    // let session_id = Uuid::new_v4().to_string();
+    // init_panic_hook(&app, installation_id.clone(), session_id.clone());
+
+    load_embedded_fonts(&app);
+
+    let fs = Arc::new(RealFs);
+    let user_settings_file_rx =
+        watch_config_file(app.executor(), fs.clone(), paths::SETTINGS.clone());
+    let user_keymap_file_rx = watch_config_file(app.executor(), fs.clone(), paths::KEYMAP.clone());
+
+    let login_shell_env_loaded = if stdout_is_a_pty() {
+        Task::ready(())
+    } else {
+        app.executor().spawn(async {
+            load_login_shell_environment().await.log_err();
+        })
+    };
+
+    let (listener, mut open_rx) = OpenListener::new();
+    let listener = Arc::new(listener);
+    let callback_listener = listener.clone();
+    app.on_open_urls(move |urls, _| callback_listener.open_urls(urls))
+        .on_reopen(move |cx| {
+            if cx.has_global::<Weak<AppState>>() {
+                if let Some(app_state) = cx.global::<Weak<AppState>>().upgrade() {
+                    // todo!("workspace")
+                    // workspace::open_new(&app_state, cx, |workspace, cx| {
+                    //     Editor::new_file(workspace, &Default::default(), cx)
+                    // })
+                    // .detach();
+                }
+            }
+        });
+
+    app.run(move |cx| {
+        cx.set_global(*RELEASE_CHANNEL);
+
+        let mut store = SettingsStore::default();
+        store
+            .set_default_settings(default_settings().as_ref(), cx)
+            .unwrap();
+        cx.set_global(store);
+        handle_settings_file_changes(user_settings_file_rx, cx);
+        // handle_keymap_file_changes(user_keymap_file_rx, cx);
+
+        // let client = client::Client::new(http.clone(), cx);
+        // let mut languages = LanguageRegistry::new(login_shell_env_loaded);
+        // let copilot_language_server_id = languages.next_language_server_id();
+        // languages.set_executor(cx.background().clone());
+        // languages.set_language_server_download_dir(paths::LANGUAGES_DIR.clone());
+        // let languages = Arc::new(languages);
+        // let node_runtime = RealNodeRuntime::new(http.clone());
+
+        // languages::init(languages.clone(), node_runtime.clone(), cx);
+        // let user_store = cx.add_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());
+
+        // theme::init(Assets, cx);
+        // context_menu::init(cx);
+        // project::Project::init(&client, cx);
+        // client::init(&client, cx);
+        // command_palette::init(cx);
+        // language::init(cx);
+        // editor::init(cx);
+        // go_to_line::init(cx);
+        // file_finder::init(cx);
+        // outline::init(cx);
+        // project_symbols::init(cx);
+        // project_panel::init(Assets, cx);
+        // channel::init(&client, user_store.clone(), cx);
+        // diagnostics::init(cx);
+        // search::init(cx);
+        // semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
+        // vim::init(cx);
+        // terminal_view::init(cx);
+        // copilot::init(
+        //     copilot_language_server_id,
+        //     http.clone(),
+        //     node_runtime.clone(),
+        //     cx,
+        // );
+        // assistant::init(cx);
+        // component_test::init(cx);
+
+        // cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach();
+        // cx.spawn(|_| watch_languages(fs.clone(), languages.clone()))
+        //     .detach();
+        // watch_file_types(fs.clone(), cx);
+
+        // languages.set_theme(theme::current(cx).clone());
+        // cx.observe_global::<SettingsStore, _>({
+        //     let languages = languages.clone();
+        //     move |cx| languages.set_theme(theme::current(cx).clone())
+        // })
+        // .detach();
+
+        // client.telemetry().start(installation_id, session_id, cx);
+
+        // todo!("app_state")
+        let app_state = Arc::new(AppState);
+        // let app_state = Arc::new(AppState {
+        //     languages,
+        //     client: client.clone(),
+        //     user_store,
+        //     fs,
+        //     build_window_options,
+        //     initialize_workspace,
+        //     background_actions,
+        //     workspace_store,
+        //     node_runtime,
+        // });
+        // cx.set_global(Arc::downgrade(&app_state));
+
+        // audio::init(Assets, cx);
+        // auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
+
+        // todo!("workspace")
+        // workspace::init(app_state.clone(), cx);
+        // recent_projects::init(cx);
+
+        // journal::init(app_state.clone(), cx);
+        // language_selector::init(cx);
+        // theme_selector::init(cx);
+        // activity_indicator::init(cx);
+        // language_tools::init(cx);
+        // call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
+        // collab_ui::init(&app_state, cx);
+        // feedback::init(cx);
+        // welcome::init(cx);
+        // zed::init(&app_state, cx);
+
+        // cx.set_menus(menus::menus());
+
+        if stdout_is_a_pty() {
+            cx.activate(true);
+            let urls = collect_url_args();
+            if !urls.is_empty() {
+                listener.open_urls(urls)
+            }
+        } else {
+            upload_previous_panics(http.clone(), cx);
+
+            // TODO Development mode that forces the CLI mode usually runs Zed binary as is instead
+            // of an *app, hence gets no specific callbacks run. Emulate them here, if needed.
+            if std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_some()
+                && !listener.triggered.load(Ordering::Acquire)
+            {
+                listener.open_urls(collect_url_args())
+            }
+        }
+
+        let mut triggered_authentication = false;
+
+        match open_rx.try_next() {
+            Ok(Some(OpenRequest::Paths { paths })) => {
+                // todo!("workspace")
+                // cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
+                //     .detach();
+            }
+            Ok(Some(OpenRequest::CliConnection { connection })) => {
+                cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx))
+                    .detach();
+            }
+            Ok(Some(OpenRequest::JoinChannel { channel_id })) => {
+                // triggered_authentication = true;
+                // let app_state = app_state.clone();
+                // let client = client.clone();
+                // cx.spawn(|mut cx| async move {
+                //     // ignore errors here, we'll show a generic "not signed in"
+                //     let _ = authenticate(client, &cx).await;
+                //     cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx))
+                //         .await
+                // })
+                // .detach_and_log_err(cx)
+            }
+            Ok(None) | Err(_) => cx
+                .spawn({
+                    let app_state = app_state.clone();
+                    |cx| async move { restore_or_create_workspace(&app_state, cx).await }
+                })
+                .detach(),
+        }
+
+        cx.spawn(|mut cx| {
+            let app_state = app_state.clone();
+            async move {
+                while let Some(request) = open_rx.next().await {
+                    match request {
+                        OpenRequest::Paths { paths } => {
+                            // todo!("workspace")
+                            // cx.update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
+                            //     .detach();
+                        }
+                        OpenRequest::CliConnection { connection } => {
+                            cx.spawn(|cx| handle_cli_connection(connection, app_state.clone(), cx))
+                                .detach();
+                        }
+                        OpenRequest::JoinChannel { channel_id } => {
+                            // cx
+                            // .update(|cx| {
+                            //     workspace::join_channel(channel_id, app_state.clone(), None, cx)
+                            // })
+                            // .detach()
+                        }
+                    }
+                }
+            }
+        })
+        .detach();
+
+        // if !triggered_authentication {
+        //     cx.spawn(|cx| async move { authenticate(client, &cx).await })
+        //         .detach_and_log_err(cx);
+        // }
+    });
+}
+
+// async fn authenticate(client: Arc<Client>, cx: &AsyncAppContext) -> Result<()> {
+//     if stdout_is_a_pty() {
+//         if client::IMPERSONATE_LOGIN.is_some() {
+//             client.authenticate_and_connect(false, &cx).await?;
+//         }
+//     } else if client.has_keychain_credentials(&cx) {
+//         client.authenticate_and_connect(true, &cx).await?;
+//     }
+//     Ok::<_, anyhow::Error>(())
+// }
+
+// async fn installation_id() -> Result<String> {
+//     let legacy_key_name = "device_id";
+
+//     if let Ok(Some(installation_id)) = KEY_VALUE_STORE.read_kvp(legacy_key_name) {
+//         Ok(installation_id)
+//     } else {
+//         let installation_id = Uuid::new_v4().to_string();
+
+//         KEY_VALUE_STORE
+//             .write_kvp(legacy_key_name.to_string(), installation_id.clone())
+//             .await?;
+
+//         Ok(installation_id)
+//     }
+// }
+
+async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
+    todo!("workspace")
+    // if let Some(location) = workspace::last_opened_workspace_paths().await {
+    //     cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))
+    //         .await
+    //         .log_err();
+    // } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
+    //     cx.update(|cx| show_welcome_experience(app_state, cx));
+    // } else {
+    //     cx.update(|cx| {
+    //         workspace::open_new(app_state, cx, |workspace, cx| {
+    //             Editor::new_file(workspace, &Default::default(), cx)
+    //         })
+    //         .detach();
+    //     });
+    // }
+}
+
+fn init_paths() {
+    std::fs::create_dir_all(&*util::paths::CONFIG_DIR).expect("could not create config path");
+    std::fs::create_dir_all(&*util::paths::LANGUAGES_DIR).expect("could not create languages path");
+    std::fs::create_dir_all(&*util::paths::DB_DIR).expect("could not create database path");
+    std::fs::create_dir_all(&*util::paths::LOGS_DIR).expect("could not create logs path");
+}
+
+fn init_logger() {
+    if stdout_is_a_pty() {
+        env_logger::init();
+    } else {
+        let level = LevelFilter::Info;
+
+        // Prevent log file from becoming too large.
+        const KIB: u64 = 1024;
+        const MIB: u64 = 1024 * KIB;
+        const MAX_LOG_BYTES: u64 = MIB;
+        if std::fs::metadata(&*paths::LOG).map_or(false, |metadata| metadata.len() > MAX_LOG_BYTES)
+        {
+            let _ = std::fs::rename(&*paths::LOG, &*paths::OLD_LOG);
+        }
+
+        let log_file = OpenOptions::new()
+            .create(true)
+            .append(true)
+            .open(&*paths::LOG)
+            .expect("could not open logfile");
+
+        let config = ConfigBuilder::new()
+            .set_time_format_str("%Y-%m-%dT%T") //All timestamps are UTC
+            .build();
+
+        simplelog::WriteLogger::init(level, config, log_file).expect("could not initialize logger");
+    }
+}
+
+#[derive(Serialize, Deserialize)]
+struct LocationData {
+    file: String,
+    line: u32,
+}
+
+#[derive(Serialize, Deserialize)]
+struct Panic {
+    thread: String,
+    payload: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    location_data: Option<LocationData>,
+    backtrace: Vec<String>,
+    app_version: String,
+    release_channel: String,
+    os_name: String,
+    os_version: Option<String>,
+    architecture: String,
+    panicked_on: u128,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    installation_id: Option<String>,
+    session_id: String,
+}
+
+#[derive(Serialize)]
+struct PanicRequest {
+    panic: Panic,
+    token: String,
+}
+
+static PANIC_COUNT: AtomicU32 = AtomicU32::new(0);
+
+// fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: String) {
+//     let is_pty = stdout_is_a_pty();
+//     let platform = app.platform();
+
+//     panic::set_hook(Box::new(move |info| {
+//         let prior_panic_count = PANIC_COUNT.fetch_add(1, Ordering::SeqCst);
+//         if prior_panic_count > 0 {
+//             // Give the panic-ing thread time to write the panic file
+//             loop {
+//                 std::thread::yield_now();
+//             }
+//         }
+
+//         let thread = thread::current();
+//         let thread_name = thread.name().unwrap_or("<unnamed>");
+
+//         let payload = info
+//             .payload()
+//             .downcast_ref::<&str>()
+//             .map(|s| s.to_string())
+//             .or_else(|| info.payload().downcast_ref::<String>().map(|s| s.clone()))
+//             .unwrap_or_else(|| "Box<Any>".to_string());
+
+//         if *util::channel::RELEASE_CHANNEL == ReleaseChannel::Dev {
+//             let location = info.location().unwrap();
+//             let backtrace = Backtrace::new();
+//             eprintln!(
+//                 "Thread {:?} panicked with {:?} at {}:{}:{}\n{:?}",
+//                 thread_name,
+//                 payload,
+//                 location.file(),
+//                 location.line(),
+//                 location.column(),
+//                 backtrace,
+//             );
+//             std::process::exit(-1);
+//         }
+
+//         let app_version = ZED_APP_VERSION
+//             .or_else(|| platform.app_version().ok())
+//             .map_or("dev".to_string(), |v| v.to_string());
+
+//         let backtrace = Backtrace::new();
+//         let mut backtrace = backtrace
+//             .frames()
+//             .iter()
+//             .filter_map(|frame| Some(format!("{:#}", frame.symbols().first()?.name()?)))
+//             .collect::<Vec<_>>();
+
+//         // Strip out leading stack frames for rust panic-handling.
+//         if let Some(ix) = backtrace
+//             .iter()
+//             .position(|name| name == "rust_begin_unwind")
+//         {
+//             backtrace.drain(0..=ix);
+//         }
+
+//         let panic_data = Panic {
+//             thread: thread_name.into(),
+//             payload: payload.into(),
+//             location_data: info.location().map(|location| LocationData {
+//                 file: location.file().into(),
+//                 line: location.line(),
+//             }),
+//             app_version: app_version.clone(),
+//             release_channel: RELEASE_CHANNEL.display_name().into(),
+//             os_name: platform.os_name().into(),
+//             os_version: platform
+//                 .os_version()
+//                 .ok()
+//                 .map(|os_version| os_version.to_string()),
+//             architecture: env::consts::ARCH.into(),
+//             panicked_on: SystemTime::now()
+//                 .duration_since(UNIX_EPOCH)
+//                 .unwrap()
+//                 .as_millis(),
+//             backtrace,
+//             installation_id: installation_id.clone(),
+//             session_id: session_id.clone(),
+//         };
+
+//         if let Some(panic_data_json) = serde_json::to_string_pretty(&panic_data).log_err() {
+//             log::error!("{}", panic_data_json);
+//         }
+
+//         if !is_pty {
+//             if let Some(panic_data_json) = serde_json::to_string(&panic_data).log_err() {
+//                 let timestamp = chrono::Utc::now().format("%Y_%m_%d %H_%M_%S").to_string();
+//                 let panic_file_path = paths::LOGS_DIR.join(format!("zed-{}.panic", timestamp));
+//                 let panic_file = std::fs::OpenOptions::new()
+//                     .append(true)
+//                     .create(true)
+//                     .open(&panic_file_path)
+//                     .log_err();
+//                 if let Some(mut panic_file) = panic_file {
+//                     writeln!(&mut panic_file, "{}", panic_data_json).log_err();
+//                     panic_file.flush().log_err();
+//                 }
+//             }
+//         }
+
+//         std::process::abort();
+//     }));
+// }
+
+// fn upload_previous_panics(http: Arc<dyn HttpClient>, cx: &mut AppContext) {
+//     let telemetry_settings = *settings::get::<TelemetrySettings>(cx);
+
+//     cx.background()
+//         .spawn({
+//             async move {
+//                 let panic_report_url = format!("{}/api/panic", &*client::ZED_SERVER_URL);
+//                 let mut children = smol::fs::read_dir(&*paths::LOGS_DIR).await?;
+//                 while let Some(child) = children.next().await {
+//                     let child = child?;
+//                     let child_path = child.path();
+
+//                     if child_path.extension() != Some(OsStr::new("panic")) {
+//                         continue;
+//                     }
+//                     let filename = if let Some(filename) = child_path.file_name() {
+//                         filename.to_string_lossy()
+//                     } else {
+//                         continue;
+//                     };
+
+//                     if !filename.starts_with("zed") {
+//                         continue;
+//                     }
+
+//                     if telemetry_settings.diagnostics {
+//                         let panic_file_content = smol::fs::read_to_string(&child_path)
+//                             .await
+//                             .context("error reading panic file")?;
+
+//                         let panic = serde_json::from_str(&panic_file_content)
+//                             .ok()
+//                             .or_else(|| {
+//                                 panic_file_content
+//                                     .lines()
+//                                     .next()
+//                                     .and_then(|line| serde_json::from_str(line).ok())
+//                             })
+//                             .unwrap_or_else(|| {
+//                                 log::error!(
+//                                     "failed to deserialize panic file {:?}",
+//                                     panic_file_content
+//                                 );
+//                                 None
+//                             });
+
+//                         if let Some(panic) = panic {
+//                             let body = serde_json::to_string(&PanicRequest {
+//                                 panic,
+//                                 token: ZED_SECRET_CLIENT_TOKEN.into(),
+//                             })
+//                             .unwrap();
+
+//                             let request = Request::post(&panic_report_url)
+//                                 .redirect_policy(isahc::config::RedirectPolicy::Follow)
+//                                 .header("Content-Type", "application/json")
+//                                 .body(body.into())?;
+//                             let response =
+//                                 http.send(request).await.context("error sending panic")?;
+//                             if !response.status().is_success() {
+//                                 log::error!(
+//                                     "Error uploading panic to server: {}",
+//                                     response.status()
+//                                 );
+//                             }
+//                         }
+//                     }
+
+//                     // We've done what we can, delete the file
+//                     std::fs::remove_file(child_path)
+//                         .context("error removing panic")
+//                         .log_err();
+//                 }
+//                 Ok::<_, anyhow::Error>(())
+//             }
+//             .log_err()
+//         })
+//         .detach();
+// }
+
+async fn load_login_shell_environment() -> Result<()> {
+    let marker = "ZED_LOGIN_SHELL_START";
+    let shell = env::var("SHELL").context(
+        "SHELL environment variable is not assigned so we can't source login environment variables",
+    )?;
+    let output = Command::new(&shell)
+        .args(["-lic", &format!("echo {marker} && /usr/bin/env -0")])
+        .output()
+        .await
+        .context("failed to spawn login shell to source login environment variables")?;
+    if !output.status.success() {
+        Err(anyhow!("login shell exited with error"))?;
+    }
+
+    let stdout = String::from_utf8_lossy(&output.stdout);
+
+    if let Some(env_output_start) = stdout.find(marker) {
+        let env_output = &stdout[env_output_start + marker.len()..];
+        for line in env_output.split_terminator('\0') {
+            if let Some(separator_index) = line.find('=') {
+                let key = &line[..separator_index];
+                let value = &line[separator_index + 1..];
+                env::set_var(key, value);
+            }
+        }
+        log::info!(
+            "set environment variables from shell:{}, path:{}",
+            shell,
+            env::var("PATH").unwrap_or_default(),
+        );
+    }
+
+    Ok(())
+}
+
+fn stdout_is_a_pty() -> bool {
+    std::env::var(FORCE_CLI_MODE_ENV_VAR_NAME).ok().is_none() && std::io::stdout().is_terminal()
+}
+
+fn collect_url_args() -> Vec<String> {
+    env::args()
+        .skip(1)
+        .filter_map(|arg| match std::fs::canonicalize(Path::new(&arg)) {
+            Ok(path) => Some(format!("file://{}", path.to_string_lossy())),
+            Err(error) => {
+                if let Some(_) = parse_zed_link(&arg) {
+                    Some(arg)
+                } else {
+                    log::error!("error parsing path argument: {}", error);
+                    None
+                }
+            }
+        })
+        .collect()
+}
+
+fn load_embedded_fonts(app: &App) {
+    let font_paths = Assets.list("fonts");
+    let embedded_fonts = Mutex::new(Vec::new());
+    smol::block_on(app.background().scoped(|scope| {
+        for font_path in &font_paths {
+            if !font_path.ends_with(".ttf") {
+                continue;
+            }
+
+            scope.spawn(async {
+                let font_path = &*font_path;
+                let font_bytes = Assets.load(font_path).unwrap().to_vec();
+                embedded_fonts.lock().push(Arc::from(font_bytes));
+            });
+        }
+    }));
+    app.platform()
+        .fonts()
+        .add_fonts(&embedded_fonts.into_inner())
+        .unwrap();
+}
+
+// #[cfg(debug_assertions)]
+// async fn watch_themes(fs: Arc<dyn Fs>, mut cx: AsyncAppContext) -> Option<()> {
+//     let mut events = fs
+//         .watch("styles/src".as_ref(), Duration::from_millis(100))
+//         .await;
+//     while (events.next().await).is_some() {
+//         let output = Command::new("npm")
+//             .current_dir("styles")
+//             .args(["run", "build"])
+//             .output()
+//             .await
+//             .log_err()?;
+//         if output.status.success() {
+//             cx.update(|cx| theme_selector::reload(cx))
+//         } else {
+//             eprintln!(
+//                 "build script failed {}",
+//                 String::from_utf8_lossy(&output.stderr)
+//             );
+//         }
+//     }
+//     Some(())
+// }
+
+// #[cfg(debug_assertions)]
+// async fn watch_languages(fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>) -> Option<()> {
+//     let mut events = fs
+//         .watch(
+//             "crates/zed/src/languages".as_ref(),
+//             Duration::from_millis(100),
+//         )
+//         .await;
+//     while (events.next().await).is_some() {
+//         languages.reload();
+//     }
+//     Some(())
+// }
+
+// #[cfg(debug_assertions)]
+// fn watch_file_types(fs: Arc<dyn Fs>, cx: &mut AppContext) {
+//     cx.spawn(|mut cx| async move {
+//         let mut events = fs
+//             .watch(
+//                 "assets/icons/file_icons/file_types.json".as_ref(),
+//                 Duration::from_millis(100),
+//             )
+//             .await;
+//         while (events.next().await).is_some() {
+//             cx.update(|cx| {
+//                 cx.update_global(|file_types, _| {
+//                     *file_types = project_panel::file_associations::FileAssociations::new(Assets);
+//                 });
+//             })
+//         }
+//     })
+//     .detach()
+// }
+
+// #[cfg(not(debug_assertions))]
+// async fn watch_themes(_fs: Arc<dyn Fs>, _cx: AsyncAppContext) -> Option<()> {
+//     None
+// }
+
+// #[cfg(not(debug_assertions))]
+// async fn watch_languages(_: Arc<dyn Fs>, _: Arc<LanguageRegistry>) -> Option<()> {
+//     None
+// }
+
+// #[cfg(not(debug_assertions))]
+// fn watch_file_types(_fs: Arc<dyn Fs>, _cx: &mut AppContext) {}
+
+fn connect_to_cli(
+    server_name: &str,
+) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
+    let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
+        .context("error connecting to cli")?;
+    let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
+    let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
+
+    handshake_tx
+        .send(IpcHandshake {
+            requests: request_tx,
+            responses: response_rx,
+        })
+        .context("error sending ipc handshake")?;
+
+    let (mut async_request_tx, async_request_rx) =
+        futures::channel::mpsc::channel::<CliRequest>(16);
+    thread::spawn(move || {
+        while let Ok(cli_request) = request_rx.recv() {
+            if smol::block_on(async_request_tx.send(cli_request)).is_err() {
+                break;
+            }
+        }
+        Ok::<_, anyhow::Error>(())
+    });
+
+    Ok((async_request_rx, response_tx))
+}
+
+async fn handle_cli_connection(
+    (mut requests, responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
+    app_state: Arc<AppState>,
+    mut cx: AsyncAppContext,
+) {
+    if let Some(request) = requests.next().await {
+        match request {
+            CliRequest::Open { paths, wait } => {
+                let mut caret_positions = HashMap::new();
+
+                // todo!("workspace")
+                // let paths = if paths.is_empty() {
+                // workspace::last_opened_workspace_paths()
+                //     .await
+                //     .map(|location| location.paths().to_vec())
+                //     .unwrap_or_default()
+                // } else {
+                //     paths
+                //         .into_iter()
+                //         .filter_map(|path_with_position_string| {
+                //             let path_with_position = PathLikeWithPosition::parse_str(
+                //                 &path_with_position_string,
+                //                 |path_str| {
+                //                     Ok::<_, std::convert::Infallible>(
+                //                         Path::new(path_str).to_path_buf(),
+                //                     )
+                //                 },
+                //             )
+                //             .expect("Infallible");
+                //             let path = path_with_position.path_like;
+                //             if let Some(row) = path_with_position.row {
+                //                 if path.is_file() {
+                //                     let row = row.saturating_sub(1);
+                //                     let col =
+                //                         path_with_position.column.unwrap_or(0).saturating_sub(1);
+                //                     caret_positions.insert(path.clone(), Point::new(row, col));
+                //                 }
+                //             }
+                //             Some(path)
+                //         })
+                //         .collect()
+                // };
+
+                // let mut errored = false;
+                // match cx
+                //     .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
+                //     .await
+                // {
+                //     Ok((workspace, items)) => {
+                //         let mut item_release_futures = Vec::new();
+
+                //         for (item, path) in items.into_iter().zip(&paths) {
+                //             match item {
+                //                 Some(Ok(item)) => {
+                //                     if let Some(point) = caret_positions.remove(path) {
+                //                         if let Some(active_editor) = item.downcast::<Editor>() {
+                //                             active_editor
+                //                                 .downgrade()
+                //                                 .update(&mut cx, |editor, cx| {
+                //                                     let snapshot =
+                //                                         editor.snapshot(cx).display_snapshot;
+                //                                     let point = snapshot
+                //                                         .buffer_snapshot
+                //                                         .clip_point(point, Bias::Left);
+                //                                     editor.change_selections(
+                //                                         Some(Autoscroll::center()),
+                //                                         cx,
+                //                                         |s| s.select_ranges([point..point]),
+                //                                     );
+                //                                 })
+                //                                 .log_err();
+                //                         }
+                //                     }
+
+                //                     let released = oneshot::channel();
+                //                     cx.update(|cx| {
+                //                         item.on_release(
+                //                             cx,
+                //                             Box::new(move |_| {
+                //                                 let _ = released.0.send(());
+                //                             }),
+                //                         )
+                //                         .detach();
+                //                     });
+                //                     item_release_futures.push(released.1);
+                //                 }
+                //                 Some(Err(err)) => {
+                //                     responses
+                //                         .send(CliResponse::Stderr {
+                //                             message: format!("error opening {:?}: {}", path, err),
+                //                         })
+                //                         .log_err();
+                //                     errored = true;
+                //                 }
+                //                 None => {}
+                //             }
+                //         }
+
+                //         if wait {
+                //             let background = cx.background();
+                //             let wait = async move {
+                //                 if paths.is_empty() {
+                //                     let (done_tx, done_rx) = oneshot::channel();
+                //                     if let Some(workspace) = workspace.upgrade(&cx) {
+                //                         let _subscription = cx.update(|cx| {
+                //                             cx.observe_release(&workspace, move |_, _| {
+                //                                 let _ = done_tx.send(());
+                //                             })
+                //                         });
+                //                         drop(workspace);
+                //                         let _ = done_rx.await;
+                //                     }
+                //                 } else {
+                //                     let _ =
+                //                         futures::future::try_join_all(item_release_futures).await;
+                //                 };
+                //             }
+                //             .fuse();
+                //             futures::pin_mut!(wait);
+
+                //             loop {
+                //                 // Repeatedly check if CLI is still open to avoid wasting resources
+                //                 // waiting for files or workspaces to close.
+                //                 let mut timer = background.timer(Duration::from_secs(1)).fuse();
+                //                 futures::select_biased! {
+                //                     _ = wait => break,
+                //                     _ = timer => {
+                //                         if responses.send(CliResponse::Ping).is_err() {
+                //                             break;
+                //                         }
+                //                     }
+                //                 }
+                //             }
+                //         }
+                //     }
+                //     Err(error) => {
+                //         errored = true;
+                //         responses
+                //             .send(CliResponse::Stderr {
+                //                 message: format!("error opening {:?}: {}", paths, error),
+                //             })
+                //             .log_err();
+                //     }
+                // }
+
+                // responses
+                //     .send(CliResponse::Exit {
+                //         status: i32::from(errored),
+                //     })
+                //     .log_err();
+            }
+        }
+    }
+}
+
+// pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
+//     &[
+//         ("Go to file", &file_finder::Toggle),
+//         ("Open command palette", &command_palette::Toggle),
+//         ("Open recent projects", &recent_projects::OpenRecent),
+//         ("Change your settings", &zed_actions::OpenSettings),
+//     ]
+// }

crates/zed2/src/only_instance.rs 🔗

@@ -0,0 +1,104 @@
+use std::{
+    io::{Read, Write},
+    net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, TcpStream},
+    thread,
+    time::Duration,
+};
+
+use util::channel::ReleaseChannel;
+
+const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
+const CONNECT_TIMEOUT: Duration = Duration::from_millis(10);
+const RECEIVE_TIMEOUT: Duration = Duration::from_millis(35);
+const SEND_TIMEOUT: Duration = Duration::from_millis(20);
+
+fn address() -> SocketAddr {
+    let port = match *util::channel::RELEASE_CHANNEL {
+        ReleaseChannel::Dev => 43737,
+        ReleaseChannel::Preview => 43738,
+        ReleaseChannel::Stable => 43739,
+    };
+
+    SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port))
+}
+
+fn instance_handshake() -> &'static str {
+    match *util::channel::RELEASE_CHANNEL {
+        ReleaseChannel::Dev => "Zed Editor Dev Instance Running",
+        ReleaseChannel::Preview => "Zed Editor Preview Instance Running",
+        ReleaseChannel::Stable => "Zed Editor Stable Instance Running",
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum IsOnlyInstance {
+    Yes,
+    No,
+}
+
+pub fn ensure_only_instance() -> IsOnlyInstance {
+    // todo!("zed_stateless")
+    // if *db::ZED_STATELESS {
+    //     return IsOnlyInstance::Yes;
+    // }
+
+    if check_got_handshake() {
+        return IsOnlyInstance::No;
+    }
+
+    let listener = match TcpListener::bind(address()) {
+        Ok(listener) => listener,
+
+        Err(err) => {
+            log::warn!("Error binding to single instance port: {err}");
+            if check_got_handshake() {
+                return IsOnlyInstance::No;
+            }
+
+            // Avoid failing to start when some other application by chance already has
+            // a claim on the port. This is sub-par as any other instance that gets launched
+            // will be unable to communicate with this instance and will duplicate
+            log::warn!("Backup handshake request failed, continuing without handshake");
+            return IsOnlyInstance::Yes;
+        }
+    };
+
+    thread::spawn(move || {
+        for stream in listener.incoming() {
+            let mut stream = match stream {
+                Ok(stream) => stream,
+                Err(_) => return,
+            };
+
+            _ = stream.set_nodelay(true);
+            _ = stream.set_read_timeout(Some(SEND_TIMEOUT));
+            _ = stream.write_all(instance_handshake().as_bytes());
+        }
+    });
+
+    IsOnlyInstance::Yes
+}
+
+fn check_got_handshake() -> bool {
+    match TcpStream::connect_timeout(&address(), CONNECT_TIMEOUT) {
+        Ok(mut stream) => {
+            let mut buf = vec![0u8; instance_handshake().len()];
+
+            stream.set_read_timeout(Some(RECEIVE_TIMEOUT)).unwrap();
+            if let Err(err) = stream.read_exact(&mut buf) {
+                log::warn!("Connected to single instance port but failed to read: {err}");
+                return false;
+            }
+
+            if buf == instance_handshake().as_bytes() {
+                log::info!("Got instance handshake");
+                return true;
+            }
+
+            log::warn!("Got wrong instance handshake value");
+            false
+        }
+
+        Err(_) => false,
+    }
+}

crates/zed2/src/open_listener.rs 🔗

@@ -0,0 +1,98 @@
+use anyhow::anyhow;
+use cli::{ipc::IpcSender, CliRequest, CliResponse};
+use futures::channel::mpsc;
+use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender};
+use std::ffi::OsStr;
+use std::os::unix::prelude::OsStrExt;
+use std::sync::atomic::Ordering;
+use std::{path::PathBuf, sync::atomic::AtomicBool};
+use util::channel::parse_zed_link;
+use util::ResultExt;
+
+use crate::connect_to_cli;
+
+pub enum OpenRequest {
+    Paths {
+        paths: Vec<PathBuf>,
+    },
+    CliConnection {
+        connection: (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
+    },
+    JoinChannel {
+        channel_id: u64,
+    },
+}
+
+pub struct OpenListener {
+    tx: UnboundedSender<OpenRequest>,
+    pub triggered: AtomicBool,
+}
+
+impl OpenListener {
+    pub fn new() -> (Self, UnboundedReceiver<OpenRequest>) {
+        let (tx, rx) = mpsc::unbounded();
+        (
+            OpenListener {
+                tx,
+                triggered: AtomicBool::new(false),
+            },
+            rx,
+        )
+    }
+
+    pub fn open_urls(&self, urls: Vec<String>) {
+        self.triggered.store(true, Ordering::Release);
+        let request = if let Some(server_name) =
+            urls.first().and_then(|url| url.strip_prefix("zed-cli://"))
+        {
+            self.handle_cli_connection(server_name)
+        } else if let Some(request_path) = urls.first().and_then(|url| parse_zed_link(url)) {
+            self.handle_zed_url_scheme(request_path)
+        } else {
+            self.handle_file_urls(urls)
+        };
+
+        if let Some(request) = request {
+            self.tx
+                .unbounded_send(request)
+                .map_err(|_| anyhow!("no listener for open requests"))
+                .log_err();
+        }
+    }
+
+    fn handle_cli_connection(&self, server_name: &str) -> Option<OpenRequest> {
+        if let Some(connection) = connect_to_cli(server_name).log_err() {
+            return Some(OpenRequest::CliConnection { connection });
+        }
+
+        None
+    }
+
+    fn handle_zed_url_scheme(&self, request_path: &str) -> Option<OpenRequest> {
+        let mut parts = request_path.split("/");
+        if parts.next() == Some("channel") {
+            if let Some(slug) = parts.next() {
+                if let Some(id_str) = slug.split("-").last() {
+                    if let Ok(channel_id) = id_str.parse::<u64>() {
+                        return Some(OpenRequest::JoinChannel { channel_id });
+                    }
+                }
+            }
+        }
+        log::error!("invalid zed url: {}", request_path);
+        None
+    }
+
+    fn handle_file_urls(&self, urls: Vec<String>) -> Option<OpenRequest> {
+        let paths: Vec<_> = urls
+            .iter()
+            .flat_map(|url| url.strip_prefix("file://"))
+            .map(|url| {
+                let decoded = urlencoding::decode_binary(url.as_bytes());
+                PathBuf::from(OsStr::from_bytes(decoded.as_ref()))
+            })
+            .collect();
+
+        Some(OpenRequest::Paths { paths })
+    }
+}

crates/zed2/src/zed2.rs 🔗

@@ -0,0 +1,203 @@
+mod assets;
+mod only_instance;
+mod open_listener;
+
+pub use assets::*;
+use gpui2::AsyncAppContext;
+pub use only_instance::*;
+pub use open_listener::*;
+
+use anyhow::{Context, Result};
+use cli::{
+    ipc::{self, IpcSender},
+    CliRequest, CliResponse, IpcHandshake,
+};
+use futures::{channel::mpsc, SinkExt, StreamExt};
+use std::{sync::Arc, thread};
+
+pub fn connect_to_cli(
+    server_name: &str,
+) -> Result<(mpsc::Receiver<CliRequest>, IpcSender<CliResponse>)> {
+    let handshake_tx = cli::ipc::IpcSender::<IpcHandshake>::connect(server_name.to_string())
+        .context("error connecting to cli")?;
+    let (request_tx, request_rx) = ipc::channel::<CliRequest>()?;
+    let (response_tx, response_rx) = ipc::channel::<CliResponse>()?;
+
+    handshake_tx
+        .send(IpcHandshake {
+            requests: request_tx,
+            responses: response_rx,
+        })
+        .context("error sending ipc handshake")?;
+
+    let (mut async_request_tx, async_request_rx) =
+        futures::channel::mpsc::channel::<CliRequest>(16);
+    thread::spawn(move || {
+        while let Ok(cli_request) = request_rx.recv() {
+            if smol::block_on(async_request_tx.send(cli_request)).is_err() {
+                break;
+            }
+        }
+        Ok::<_, anyhow::Error>(())
+    });
+
+    Ok((async_request_rx, response_tx))
+}
+
+pub struct AppState;
+
+pub async fn handle_cli_connection(
+    (mut requests, responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
+    app_state: Arc<AppState>,
+    mut cx: AsyncAppContext,
+) {
+    if let Some(request) = requests.next().await {
+        match request {
+            CliRequest::Open { paths, wait } => {
+                // let mut caret_positions = HashMap::new();
+
+                // let paths = if paths.is_empty() {
+                // todo!()
+                // workspace::last_opened_workspace_paths()
+                //     .await
+                //     .map(|location| location.paths().to_vec())
+                //     .unwrap_or_default()
+                // } else {
+                //     paths
+                //         .into_iter()
+                //         .filter_map(|path_with_position_string| {
+                //             let path_with_position = PathLikeWithPosition::parse_str(
+                //                 &path_with_position_string,
+                //                 |path_str| {
+                //                     Ok::<_, std::convert::Infallible>(
+                //                         Path::new(path_str).to_path_buf(),
+                //                     )
+                //                 },
+                //             )
+                //             .expect("Infallible");
+                //             let path = path_with_position.path_like;
+                //             if let Some(row) = path_with_position.row {
+                //                 if path.is_file() {
+                //                     let row = row.saturating_sub(1);
+                //                     let col =
+                //                         path_with_position.column.unwrap_or(0).saturating_sub(1);
+                //                     caret_positions.insert(path.clone(), Point::new(row, col));
+                //                 }
+                //             }
+                //             Some(path)
+                //         })
+                //         .collect()
+                // };
+
+                // let mut errored = false;
+                // todo!("workspace")
+                // match cx
+                //     .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
+                //     .await
+                // {
+                //     Ok((workspace, items)) => {
+                //         let mut item_release_futures = Vec::new();
+
+                //         for (item, path) in items.into_iter().zip(&paths) {
+                //             match item {
+                //                 Some(Ok(item)) => {
+                //                     if let Some(point) = caret_positions.remove(path) {
+                //                         if let Some(active_editor) = item.downcast::<Editor>() {
+                //                             active_editor
+                //                                 .downgrade()
+                //                                 .update(&mut cx, |editor, cx| {
+                //                                     let snapshot =
+                //                                         editor.snapshot(cx).display_snapshot;
+                //                                     let point = snapshot
+                //                                         .buffer_snapshot
+                //                                         .clip_point(point, Bias::Left);
+                //                                     editor.change_selections(
+                //                                         Some(Autoscroll::center()),
+                //                                         cx,
+                //                                         |s| s.select_ranges([point..point]),
+                //                                     );
+                //                                 })
+                //                                 .log_err();
+                //                         }
+                //                     }
+
+                //                     let released = oneshot::channel();
+                //                     cx.update(|cx| {
+                //                         item.on_release(
+                //                             cx,
+                //                             Box::new(move |_| {
+                //                                 let _ = released.0.send(());
+                //                             }),
+                //                         )
+                //                         .detach();
+                //                     });
+                //                     item_release_futures.push(released.1);
+                //                 }
+                //                 Some(Err(err)) => {
+                //                     responses
+                //                         .send(CliResponse::Stderr {
+                //                             message: format!("error opening {:?}: {}", path, err),
+                //                         })
+                //                         .log_err();
+                //                     errored = true;
+                //                 }
+                //                 None => {}
+                //             }
+                //         }
+
+                //         if wait {
+                //             let background = cx.background();
+                //             let wait = async move {
+                //                 if paths.is_empty() {
+                //                     let (done_tx, done_rx) = oneshot::channel();
+                //                     if let Some(workspace) = workspace.upgrade(&cx) {
+                //                         let _subscription = cx.update(|cx| {
+                //                             cx.observe_release(&workspace, move |_, _| {
+                //                                 let _ = done_tx.send(());
+                //                             })
+                //                         });
+                //                         drop(workspace);
+                //                         let _ = done_rx.await;
+                //                     }
+                //                 } else {
+                //                     let _ =
+                //                         futures::future::try_join_all(item_release_futures).await;
+                //                 };
+                //             }
+                //             .fuse();
+                //             futures::pin_mut!(wait);
+
+                //             loop {
+                //                 // Repeatedly check if CLI is still open to avoid wasting resources
+                //                 // waiting for files or workspaces to close.
+                //                 let mut timer = background.timer(Duration::from_secs(1)).fuse();
+                //                 futures::select_biased! {
+                //                     _ = wait => break,
+                //                     _ = timer => {
+                //                         if responses.send(CliResponse::Ping).is_err() {
+                //                             break;
+                //                         }
+                //                     }
+                //                 }
+                //             }
+                //         }
+                //     }
+                //     Err(error) => {
+                //         errored = true;
+                //         responses
+                //             .send(CliResponse::Stderr {
+                //                 message: format!("error opening {:?}: {}", paths, error),
+                //             })
+                //             .log_err();
+                //     }
+                // }
+
+                // responses
+                //     .send(CliResponse::Exit {
+                //         status: i32::from(errored),
+                //     })
+                //     .log_err();
+            }
+        }
+    }
+}