mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 07:12:03 +03:00
WIP
This commit is contained in:
parent
e4fe9538d7
commit
b7d30fca2b
118
Cargo.lock
generated
118
Cargo.lock
generated
@ -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"
|
||||
|
@ -81,6 +81,7 @@ members = [
|
||||
"crates/welcome",
|
||||
"crates/xtask",
|
||||
"crates/zed",
|
||||
"crates/zed2",
|
||||
"crates/zed-actions"
|
||||
]
|
||||
default-members = ["crates/zed"]
|
||||
|
@ -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"
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
flushing_effects: false,
|
||||
next_frame_callbacks: Default::default(),
|
||||
platform: MainThreadOnly::new(platform, executor.clone()),
|
||||
flushing_effects: false,
|
||||
pending_updates: 0,
|
||||
next_frame_callbacks: Default::default(),
|
||||
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> {
|
||||
|
@ -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,
|
||||
) -> Self::Result<R> {
|
||||
let app = self
|
||||
.0
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
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 lock = app.lock(); // Does not compile without this variable.
|
||||
Ok(lock.update_entity(handle, update))
|
||||
let mut app_context = app.lock();
|
||||
app_context.read_window(handle.id, update)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncAppContext {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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> {
|
||||
|
42
crates/settings2/Cargo.toml
Normal file
42
crates/settings2/Cargo.toml
Normal file
@ -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
|
163
crates/settings2/src/keymap_file.rs
Normal file
163
crates/settings2/src/keymap_file.rs
Normal file
@ -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();
|
||||
}
|
||||
}
|
38
crates/settings2/src/settings2.rs
Normal file
38
crates/settings2/src/settings2.rs
Normal file
@ -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")
|
||||
}
|
135
crates/settings2/src/settings_file.rs
Normal file
135
crates/settings2/src/settings_file.rs
Normal file
@ -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);
|
||||
}
|
1268
crates/settings2/src/settings_store.rs
Normal file
1268
crates/settings2/src/settings_store.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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"] }
|
||||
|
@ -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;
|
||||
|
180
crates/zed2/Cargo.toml
Normal file
180
crates/zed2/Cargo.toml
Normal file
@ -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"]
|
BIN
crates/zed2/resources/app-icon-preview.png
Normal file
BIN
crates/zed2/resources/app-icon-preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 187 KiB |
BIN
crates/zed2/resources/app-icon-preview@2x.png
Normal file
BIN
crates/zed2/resources/app-icon-preview@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 663 KiB |
BIN
crates/zed2/resources/app-icon.png
Normal file
BIN
crates/zed2/resources/app-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
BIN
crates/zed2/resources/app-icon@2x.png
Normal file
BIN
crates/zed2/resources/app-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 442 KiB |
62
crates/zed2/resources/info/DocumentTypes.plist
Normal file
62
crates/zed2/resources/info/DocumentTypes.plist
Normal file
@ -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>
|
24
crates/zed2/resources/info/Permissions.plist
Normal file
24
crates/zed2/resources/info/Permissions.plist
Normal file
@ -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>
|
24
crates/zed2/resources/zed.entitlements
Normal file
24
crates/zed2/resources/zed.entitlements
Normal file
@ -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>
|
33
crates/zed2/src/assets.rs
Normal file
33
crates/zed2/src/assets.rs
Normal file
@ -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())
|
||||
}
|
||||
}
|
912
crates/zed2/src/main.rs
Normal file
912
crates/zed2/src/main.rs
Normal file
@ -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),
|
||||
// ]
|
||||
// }
|
104
crates/zed2/src/only_instance.rs
Normal file
104
crates/zed2/src/only_instance.rs
Normal file
@ -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,
|
||||
}
|
||||
}
|
98
crates/zed2/src/open_listener.rs
Normal file
98
crates/zed2/src/open_listener.rs
Normal file
@ -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 })
|
||||
}
|
||||
}
|
203
crates/zed2/src/zed2.rs
Normal file
203
crates/zed2/src/zed2.rs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user