Compare commits

...

2 Commits

Author SHA1 Message Date
Conrad Irwin
f802444e79 Remove can_open_windows
Co-Authored-By: Mikayla <mikayla@zed.dev>
2024-06-10 15:24:01 -06:00
Conrad Irwin
7716fa8312 Make window initialization fallible 2024-06-10 15:20:20 -06:00
29 changed files with 272 additions and 204 deletions

7
Cargo.lock generated
View File

@@ -1511,7 +1511,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-graphics" name = "blade-graphics"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" source = "git+https://github.com/zed-industries/blade?rev=86bf7228f50f44058edf7872055261efd9eca9de#86bf7228f50f44058edf7872055261efd9eca9de"
dependencies = [ dependencies = [
"ash", "ash",
"ash-window", "ash-window",
@@ -1541,7 +1541,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-macros" name = "blade-macros"
version = "0.2.1" version = "0.2.1"
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" source = "git+https://github.com/zed-industries/blade?rev=86bf7228f50f44058edf7872055261efd9eca9de#86bf7228f50f44058edf7872055261efd9eca9de"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1551,7 +1551,7 @@ dependencies = [
[[package]] [[package]]
name = "blade-util" name = "blade-util"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/kvark/blade?rev=bdaf8c534fbbc9fbca71d1cf272f45640b3a068d#bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" source = "git+https://github.com/zed-industries/blade?rev=86bf7228f50f44058edf7872055261efd9eca9de#86bf7228f50f44058edf7872055261efd9eca9de"
dependencies = [ dependencies = [
"blade-graphics", "blade-graphics",
"bytemuck", "bytemuck",
@@ -13152,6 +13152,7 @@ version = "0.140.0"
dependencies = [ dependencies = [
"activity_indicator", "activity_indicator",
"anyhow", "anyhow",
"ashpd",
"assets", "assets",
"assistant", "assistant",
"audio", "audio",

View File

@@ -267,9 +267,9 @@ async-tar = "0.4.2"
async-trait = "0.1" async-trait = "0.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] } async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
bitflags = "2.4.2" bitflags = "2.4.2"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" } blade-graphics = { git = "https://github.com/zed-industries/blade", rev = "86bf7228f50f44058edf7872055261efd9eca9de" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" } blade-macros = { git = "https://github.com/zed-industries/blade", rev = "86bf7228f50f44058edf7872055261efd9eca9de" }
blade-util = { git = "https://github.com/kvark/blade", rev = "bdaf8c534fbbc9fbca71d1cf272f45640b3a068d" } blade-util = { git = "https://github.com/zed-industries/blade", rev = "86bf7228f50f44058edf7872055261efd9eca9de" }
cap-std = "3.0" cap-std = "3.0"
cargo_toml = "0.20" cargo_toml = "0.20"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }

View File

@@ -97,7 +97,7 @@ pub fn open_prompt_library(
}, },
|cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)), |cx| cx.new_view(|cx| PromptLibrary::new(store, language_registry, cx)),
) )
}) })?
}) })
} }
} }

View File

@@ -8,6 +8,7 @@ use settings::Settings;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, Button, Label}; use ui::{prelude::*, Button, Label};
use util::ResultExt;
use workspace::AppState; use workspace::AppState;
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) { pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
@@ -27,16 +28,21 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
for screen in cx.displays() { for screen in cx.displays() {
let options = notification_window_options(screen, window_size, cx); let options = notification_window_options(screen, window_size, cx);
let window = cx.open_window(options, |cx| { let Some(window) = cx
cx.new_view(|_| { .open_window(options, |cx| {
ProjectSharedNotification::new( cx.new_view(|_| {
owner.clone(), ProjectSharedNotification::new(
*project_id, owner.clone(),
worktree_root_names.clone(), *project_id,
app_state.clone(), worktree_root_names.clone(),
) app_state.clone(),
)
})
}) })
}); .log_err()
else {
continue;
};
notification_windows notification_windows
.entry(*project_id) .entry(*project_id)
.or_insert(Vec::new()) .or_insert(Vec::new())

View File

@@ -509,6 +509,7 @@ fn test_clone(cx: &mut TestAppContext) {
.update(cx, |editor, cx| { .update(cx, |editor, cx| {
cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx))) cx.open_window(Default::default(), |cx| cx.new_view(|cx| editor.clone(cx)))
}) })
.unwrap()
.unwrap(); .unwrap();
let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap(); let snapshot = editor.update(cx, |e, cx| e.snapshot(cx)).unwrap();
@@ -7657,6 +7658,7 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
}, },
|cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)), |cx| cx.new_view(|cx| build_editor(buffer.clone(), cx)),
) )
.unwrap()
}); });
let is_still_following = Rc::new(RefCell::new(true)); let is_still_following = Rc::new(RefCell::new(true));

View File

@@ -76,6 +76,7 @@ fn main() {
cx.open_window(options, |cx| { cx.open_window(options, |cx| {
cx.activate(false); cx.activate(false);
cx.new_view(|_cx| AnimationExample {}) cx.new_view(|_cx| AnimationExample {})
}); })
.unwrap();
}); });
} }

View File

@@ -34,6 +34,7 @@ fn main() {
text: "World".into(), text: "World".into(),
}) })
}, },
); )
.unwrap();
}); });
} }

View File

@@ -93,6 +93,7 @@ fn main() {
local_resource: Arc::new(PathBuf::from_str("examples/image/app-icon.png").unwrap()), local_resource: Arc::new(PathBuf::from_str("examples/image/app-icon.png").unwrap()),
remote_resource: "https://picsum.photos/512/512".into(), remote_resource: "https://picsum.photos/512/512".into(),
}) })
}); })
.unwrap();
}); });
} }

View File

@@ -29,7 +29,8 @@ fn main() {
}]); }]);
cx.open_window(WindowOptions::default(), |cx| { cx.open_window(WindowOptions::default(), |cx| {
cx.new_view(|_cx| SetMenus {}) cx.new_view(|_cx| SetMenus {})
}); })
.unwrap();
}); });
} }

View File

@@ -61,7 +61,8 @@ fn main() {
cx.new_view(|_| WindowContent { cx.new_view(|_| WindowContent {
text: format!("{:?}", screen.id()).into(), text: format!("{:?}", screen.id()).into(),
}) })
}); })
.unwrap();
} }
}); });
} }

View File

@@ -490,26 +490,26 @@ impl AppContext {
&mut self, &mut self,
options: crate::WindowOptions, options: crate::WindowOptions,
build_root_view: impl FnOnce(&mut WindowContext) -> View<V>, build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
) -> WindowHandle<V> { ) -> anyhow::Result<WindowHandle<V>> {
self.update(|cx| { self.update(|cx| {
let id = cx.windows.insert(None); let id = cx.windows.insert(None);
let handle = WindowHandle::new(id); let handle = WindowHandle::new(id);
let mut window = Window::new(handle.into(), options, cx); match Window::new(handle.into(), options, cx) {
let root_view = build_root_view(&mut WindowContext::new(cx, &mut window)); Ok(mut window) => {
window.root_view.replace(root_view.into()); let root_view = build_root_view(&mut WindowContext::new(cx, &mut window));
cx.window_handles.insert(id, window.handle); window.root_view.replace(root_view.into());
cx.windows.get_mut(id).unwrap().replace(window); cx.window_handles.insert(id, window.handle);
handle cx.windows.get_mut(id).unwrap().replace(window);
Ok(handle)
}
Err(e) => {
cx.windows.remove(id);
return Err(e);
}
}
}) })
} }
/// Returns Ok() if the platform supports opening windows.
/// This returns false (for example) on linux when we could
/// not establish a connection to X or Wayland.
pub fn can_open_windows(&self) -> anyhow::Result<()> {
self.platform.can_open_windows()
}
/// Instructs the platform to activate the application by bringing it to the foreground. /// Instructs the platform to activate the application by bringing it to the foreground.
pub fn activate(&self, ignoring_other_apps: bool) { pub fn activate(&self, ignoring_other_apps: bool) {
self.platform.activate(ignoring_other_apps); self.platform.activate(ignoring_other_apps);

View File

@@ -151,7 +151,7 @@ impl AsyncAppContext {
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("app was released"))?; .ok_or_else(|| anyhow!("app was released"))?;
let mut lock = app.borrow_mut(); let mut lock = app.borrow_mut();
Ok(lock.open_window(options, build_root_view)) lock.open_window(options, build_root_view)
} }
/// Schedule a future to be polled in the background. /// Schedule a future to be polled in the background.

View File

@@ -193,19 +193,22 @@ impl TestAppContext {
}, },
|cx| cx.new_view(build_window), |cx| cx.new_view(build_window),
) )
.unwrap()
} }
/// Adds a new window with no content. /// Adds a new window with no content.
pub fn add_empty_window(&mut self) -> &mut VisualTestContext { pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
let bounds = Bounds::maximized(None, &mut cx); let bounds = Bounds::maximized(None, &mut cx);
let window = cx.open_window( let window = cx
WindowOptions { .open_window(
window_bounds: Some(WindowBounds::Windowed(bounds)), WindowOptions {
..Default::default() window_bounds: Some(WindowBounds::Windowed(bounds)),
}, ..Default::default()
|cx| cx.new_view(|_| Empty), },
); |cx| cx.new_view(|_| Empty),
)
.unwrap();
drop(cx); drop(cx);
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut(); let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
cx.run_until_parked(); cx.run_until_parked();
@@ -222,13 +225,15 @@ impl TestAppContext {
{ {
let mut cx = self.app.borrow_mut(); let mut cx = self.app.borrow_mut();
let bounds = Bounds::maximized(None, &mut cx); let bounds = Bounds::maximized(None, &mut cx);
let window = cx.open_window( let window = cx
WindowOptions { .open_window(
window_bounds: Some(WindowBounds::Windowed(bounds)), WindowOptions {
..Default::default() window_bounds: Some(WindowBounds::Windowed(bounds)),
}, ..Default::default()
|cx| cx.new_view(build_root_view), },
); |cx| cx.new_view(build_root_view),
)
.unwrap();
drop(cx); drop(cx);
let view = window.root_view(self).unwrap(); let view = window.root_view(self).unwrap();
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut(); let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();

View File

@@ -486,6 +486,7 @@ mod test {
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
}) })
}) })
.unwrap()
}); });
cx.update(|cx| { cx.update(|cx| {

View File

@@ -106,14 +106,12 @@ pub(crate) trait Platform: 'static {
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>; fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>; fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
fn active_window(&self) -> Option<AnyWindowHandle>; fn active_window(&self) -> Option<AnyWindowHandle>;
fn can_open_windows(&self) -> anyhow::Result<()> {
Ok(())
}
fn open_window( fn open_window(
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow>; ) -> anyhow::Result<Box<dyn PlatformWindow>>;
/// Returns the appearance of the application's windows. /// Returns the appearance of the application's windows.
fn window_appearance(&self) -> WindowAppearance; fn window_appearance(&self) -> WindowAppearance;

View File

@@ -59,10 +59,6 @@ impl LinuxClient for HeadlessClient {
None None
} }
fn can_open_windows(&self) -> anyhow::Result<()> {
return Err(anyhow::anyhow!("neither DISPLAY, nor WAYLAND_DISPLAY found. You can still run zed for remote development with --dev-server-token."));
}
fn active_window(&self) -> Option<AnyWindowHandle> { fn active_window(&self) -> Option<AnyWindowHandle> {
None None
} }
@@ -71,8 +67,10 @@ impl LinuxClient for HeadlessClient {
&self, &self,
_handle: AnyWindowHandle, _handle: AnyWindowHandle,
_params: WindowParams, _params: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
unimplemented!() Err(anyhow::anyhow!(
"neither DISPLAY nor WAYLAND_DISPLAY is set. You can run in headless mode"
))
} }
fn set_cursor_style(&self, _style: CursorStyle) {} fn set_cursor_style(&self, _style: CursorStyle) {}

View File

@@ -65,7 +65,7 @@ pub trait LinuxClient {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow>; ) -> anyhow::Result<Box<dyn PlatformWindow>>;
fn set_cursor_style(&self, style: CursorStyle); fn set_cursor_style(&self, style: CursorStyle);
fn open_uri(&self, uri: &str); fn open_uri(&self, uri: &str);
fn write_to_primary(&self, item: ClipboardItem); fn write_to_primary(&self, item: ClipboardItem);
@@ -245,7 +245,7 @@ impl<P: LinuxClient + 'static> Platform for P {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowParams, options: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
self.open_window(handle, options) self.open_window(handle, options)
} }

View File

@@ -559,7 +559,7 @@ impl LinuxClient for WaylandClient {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
params: WindowParams, params: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let (window, surface_id) = WaylandWindow::new( let (window, surface_id) = WaylandWindow::new(
@@ -568,10 +568,10 @@ impl LinuxClient for WaylandClient {
WaylandClientStatePtr(Rc::downgrade(&self.0)), WaylandClientStatePtr(Rc::downgrade(&self.0)),
params, params,
state.common.appearance, state.common.appearance,
); )?;
state.windows.insert(surface_id, window.0.clone()); state.windows.insert(surface_id, window.0.clone());
Box::new(window) Ok(Box::new(window))
} }
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {

View File

@@ -107,7 +107,7 @@ impl WaylandWindowState {
client: WaylandClientStatePtr, client: WaylandClientStatePtr,
globals: Globals, globals: Globals,
options: WindowParams, options: WindowParams,
) -> Self { ) -> anyhow::Result<Self> {
let bounds = options.bounds.map(|p| p.0 as u32); let bounds = options.bounds.map(|p| p.0 as u32);
let raw = RawWindow { let raw = RawWindow {
@@ -130,7 +130,7 @@ impl WaylandWindowState {
}, },
) )
} }
.unwrap(), .map_err(|e| anyhow::anyhow!("{:?}", e))?,
); );
let config = BladeSurfaceConfig { let config = BladeSurfaceConfig {
size: gpu::Extent { size: gpu::Extent {
@@ -141,7 +141,7 @@ impl WaylandWindowState {
transparent: options.window_background != WindowBackgroundAppearance::Opaque, transparent: options.window_background != WindowBackgroundAppearance::Opaque,
}; };
Self { Ok(Self {
xdg_surface, xdg_surface,
acknowledged_first_configure: false, acknowledged_first_configure: false,
surface, surface,
@@ -164,7 +164,7 @@ impl WaylandWindowState {
appearance, appearance,
handle, handle,
active: false, active: false,
} })
} }
} }
@@ -224,7 +224,7 @@ impl WaylandWindow {
client: WaylandClientStatePtr, client: WaylandClientStatePtr,
params: WindowParams, params: WindowParams,
appearance: WindowAppearance, appearance: WindowAppearance,
) -> (Self, ObjectId) { ) -> anyhow::Result<(Self, ObjectId)> {
let surface = globals.compositor.create_surface(&globals.qh, ()); let surface = globals.compositor.create_surface(&globals.qh, ());
let xdg_surface = globals let xdg_surface = globals
.wm_base .wm_base
@@ -267,14 +267,14 @@ impl WaylandWindow {
client, client,
globals, globals,
params, params,
))), )?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())), callbacks: Rc::new(RefCell::new(Callbacks::default())),
}); });
// Kick things off // Kick things off
surface.commit(); surface.commit();
(this, surface.id()) Ok((this, surface.id()))
} }
} }

View File

@@ -923,7 +923,7 @@ impl LinuxClient for X11Client {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
params: WindowParams, params: WindowParams,
) -> Box<dyn PlatformWindow> { ) -> anyhow::Result<Box<dyn PlatformWindow>> {
let mut state = self.0.borrow_mut(); let mut state = self.0.borrow_mut();
let x_window = state.xcb_connection.generate_id().unwrap(); let x_window = state.xcb_connection.generate_id().unwrap();
@@ -938,7 +938,7 @@ impl LinuxClient for X11Client {
&state.atoms, &state.atoms,
state.scale_factor, state.scale_factor,
state.common.appearance, state.common.appearance,
); )?;
let screen_resources = state let screen_resources = state
.xcb_connection .xcb_connection
@@ -1006,7 +1006,7 @@ impl LinuxClient for X11Client {
}; };
state.windows.insert(x_window, window_ref); state.windows.insert(x_window, window_ref);
Box::new(window) Ok(Box::new(window))
} }
fn set_cursor_style(&self, style: CursorStyle) { fn set_cursor_style(&self, style: CursorStyle) {

View File

@@ -216,7 +216,7 @@ impl X11WindowState {
atoms: &XcbAtoms, atoms: &XcbAtoms,
scale_factor: f32, scale_factor: f32,
appearance: WindowAppearance, appearance: WindowAppearance,
) -> Self { ) -> anyhow::Result<Self> {
let x_screen_index = params let x_screen_index = params
.display_id .display_id
.map_or(x_main_screen_index, |did| did.0 as usize); .map_or(x_main_screen_index, |did| did.0 as usize);
@@ -248,8 +248,7 @@ impl X11WindowState {
xcb_connection xcb_connection
.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id) .create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id)
.unwrap() .unwrap()
.check() .check()?;
.unwrap();
id id
}; };
@@ -281,8 +280,7 @@ impl X11WindowState {
&win_aux, &win_aux,
) )
.unwrap() .unwrap()
.check() .check()?;
.unwrap();
if let Some(titlebar) = params.titlebar { if let Some(titlebar) = params.titlebar {
if let Some(title) = titlebar.title { if let Some(title) = titlebar.title {
@@ -345,7 +343,7 @@ impl X11WindowState {
}, },
) )
} }
.unwrap(), .map_err(|e| anyhow::anyhow!("{:?}", e))?,
); );
let config = BladeSurfaceConfig { let config = BladeSurfaceConfig {
@@ -355,7 +353,7 @@ impl X11WindowState {
transparent: params.window_background != WindowBackgroundAppearance::Opaque, transparent: params.window_background != WindowBackgroundAppearance::Opaque,
}; };
Self { Ok(Self {
client, client,
executor, executor,
display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()), display: Rc::new(X11Display::new(xcb_connection, x_screen_index).unwrap()),
@@ -368,7 +366,7 @@ impl X11WindowState {
input_handler: None, input_handler: None,
appearance, appearance,
handle, handle,
} })
} }
fn content_size(&self) -> Size<Pixels> { fn content_size(&self) -> Size<Pixels> {
@@ -426,8 +424,8 @@ impl X11Window {
atoms: &XcbAtoms, atoms: &XcbAtoms,
scale_factor: f32, scale_factor: f32,
appearance: WindowAppearance, appearance: WindowAppearance,
) -> Self { ) -> anyhow::Result<Self> {
Self(X11WindowStatePtr { Ok(Self(X11WindowStatePtr {
state: Rc::new(RefCell::new(X11WindowState::new( state: Rc::new(RefCell::new(X11WindowState::new(
handle, handle,
client, client,
@@ -439,11 +437,11 @@ impl X11Window {
atoms, atoms,
scale_factor, scale_factor,
appearance, appearance,
))), )?)),
callbacks: Rc::new(RefCell::new(Callbacks::default())), callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb_connection: xcb_connection.clone(), xcb_connection: xcb_connection.clone(),
x_window, x_window,
}) }))
} }
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) { fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {

View File

@@ -187,14 +187,14 @@ impl Platform for TestPlatform {
&self, &self,
handle: AnyWindowHandle, handle: AnyWindowHandle,
params: WindowParams, params: WindowParams,
) -> Box<dyn crate::PlatformWindow> { ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
let window = TestWindow::new( let window = TestWindow::new(
handle, handle,
params, params,
self.weak.clone(), self.weak.clone(),
self.active_display.clone(), self.active_display.clone(),
); );
Box::new(window) Ok(Box::new(window))
} }
fn window_appearance(&self) -> WindowAppearance { fn window_appearance(&self) -> WindowAppearance {

View File

@@ -605,7 +605,7 @@ impl Window {
handle: AnyWindowHandle, handle: AnyWindowHandle,
options: WindowOptions, options: WindowOptions,
cx: &mut AppContext, cx: &mut AppContext,
) -> Self { ) -> Result<Self> {
let WindowOptions { let WindowOptions {
window_bounds, window_bounds,
titlebar, titlebar,
@@ -633,7 +633,7 @@ impl Window {
display_id, display_id,
window_background, window_background,
}, },
); )?;
let display_id = platform_window.display().map(|display| display.id()); let display_id = platform_window.display().map(|display| display.id());
let sprite_atlas = platform_window.sprite_atlas(); let sprite_atlas = platform_window.sprite_atlas();
let mouse_position = platform_window.mouse_position(); let mouse_position = platform_window.mouse_position();
@@ -761,7 +761,7 @@ impl Window {
platform_window.set_app_id(&app_id); platform_window.set_app_id(&app_id);
} }
Window { Ok(Window {
handle, handle,
removed: false, removed: false,
platform_window, platform_window,
@@ -807,7 +807,7 @@ impl Window {
focus_enabled: true, focus_enabled: true,
pending_input: None, pending_input: None,
prompt: None, prompt: None,
} })
} }
fn new_focus_listener( fn new_focus_listener(
&mut self, &mut self,

View File

@@ -147,7 +147,8 @@ pub fn main() {
cx, cx,
) )
}) })
}); })
.unwrap();
}); });
} }

View File

@@ -5,7 +5,7 @@ use client::{telemetry::Telemetry, TelemetrySettings};
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::{ use gpui::{
svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement, svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
ParentElement, Render, Styled, Subscription, View, ViewContext, VisualContext, WeakView, ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
WindowContext, WindowContext,
}; };
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
@@ -36,19 +36,21 @@ pub fn init(cx: &mut AppContext) {
base_keymap_picker::init(cx); base_keymap_picker::init(cx);
} }
pub fn show_welcome_view(app_state: Arc<AppState>, cx: &mut AppContext) { pub fn show_welcome_view(
app_state: Arc<AppState>,
cx: &mut AppContext,
) -> Task<anyhow::Result<()>> {
open_new(app_state, cx, |workspace, cx| { open_new(app_state, cx, |workspace, cx| {
workspace.toggle_dock(DockPosition::Left, cx); workspace.toggle_dock(DockPosition::Left, cx);
let welcome_page = WelcomePage::new(workspace, cx); let welcome_page = WelcomePage::new(workspace, cx);
workspace.add_item_to_center(Box::new(welcome_page.clone()), cx); workspace.add_item_to_center(Box::new(welcome_page.clone()), cx);
cx.focus_view(&welcome_page); cx.focus_view(&welcome_page);
cx.notify(); cx.notify();
})
.detach();
db::write_and_log(cx, || { db::write_and_log(cx, || {
KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string()) KEY_VALUE_STORE.write_kvp(FIRST_OPEN.to_string(), "false".to_string())
}); });
})
} }
pub struct WelcomePage { pub struct WelcomePage {

View File

@@ -4839,18 +4839,16 @@ pub fn open_new(
app_state: Arc<AppState>, app_state: Arc<AppState>,
cx: &mut AppContext, cx: &mut AppContext,
init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send, init: impl FnOnce(&mut Workspace, &mut ViewContext<Workspace>) + 'static + Send,
) -> Task<()> { ) -> Task<anyhow::Result<()>> {
let task = Workspace::new_local(Vec::new(), app_state, None, cx); let task = Workspace::new_local(Vec::new(), app_state, None, cx);
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
if let Some((workspace, opened_paths)) = task.await.log_err() { let (workspace, opened_paths) = task.await?;
workspace workspace.update(&mut cx, |workspace, cx| {
.update(&mut cx, |workspace, cx| { if opened_paths.is_empty() {
if opened_paths.is_empty() { init(workspace, cx)
init(workspace, cx) }
} })?;
}) Ok(())
.log_err();
}
}) })
} }
@@ -4922,7 +4920,7 @@ pub fn join_hosted_project(
Workspace::new(Default::default(), project, app_state.clone(), cx) Workspace::new(Default::default(), project, app_state.clone(), cx)
}) })
}) })
})? })??
}; };
workspace.update(&mut cx, |_, cx| { workspace.update(&mut cx, |_, cx| {
@@ -4987,7 +4985,7 @@ pub fn join_dev_server_project(
Workspace::new(Default::default(), project, app_state.clone(), cx) Workspace::new(Default::default(), project, app_state.clone(), cx)
}) })
}) })
})? })??
} }
}; };
@@ -5050,7 +5048,7 @@ pub fn join_in_room_project(
Workspace::new(Default::default(), project, app_state.clone(), cx) Workspace::new(Default::default(), project, app_state.clone(), cx)
}) })
}) })
})? })??
}; };
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {

View File

@@ -103,6 +103,9 @@ zed_actions.workspace = true
[target.'cfg(target_os = "windows")'.build-dependencies] [target.'cfg(target_os = "windows")'.build-dependencies]
winresource = "0.1" winresource = "0.1"
[target.'cfg(target_os = "linux")'.dependencies]
ashpd.workspace = true
[dev-dependencies] [dev-dependencies]
call = { workspace = true, features = ["test-support"] } call = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] } editor = { workspace = true, features = ["test-support"] }

View File

@@ -56,21 +56,58 @@ use crate::zed::inline_completion_registry;
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn fail_to_launch(e: anyhow::Error) { fn fail_to_launch(e: anyhow::Error) {
eprintln!("Zed failed to launch: {:?}", e);
App::new().run(move |cx| { App::new().run(move |cx| {
let window = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)); if let Ok(window) = cx.open_window(gpui::WindowOptions::default(), |cx| cx.new_view(|_| gpui::Empty)) {
window.update(cx, |_, cx| { window.update(cx, |_, cx| {
let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]); let response = cx.prompt(gpui::PromptLevel::Critical, "Zed failed to launch", Some(&format!("{}\n\nFor help resolving this, please open an issue on https://github.com/zed-industries/zed", e)), &["Exit"]);
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
response.await?; response.await?;
cx.update(|cx| { cx.update(|cx| {
cx.quit() cx.quit()
}) })
}).detach_and_log_err(cx); }).detach_and_log_err(cx);
}).log_err(); }).log_err();
} else {
fail_to_open_window(e, cx)
}
}) })
} }
fn fail_to_open_window_async(e: anyhow::Error, cx: &mut AsyncAppContext) {
cx.update(|cx| fail_to_open_window(e, cx)).log_err();
}
fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
eprintln!("Zed failed to open a window: {:?}", e);
#[cfg(target_os = "linux")]
{
use ashpd::desktop::notification::{Notification, NotificationProxy, Priority};
_cx.spawn(|cx| async move {
let proxy = NotificationProxy::new().await?;
let notification_id = "dev.zed.Oops";
proxy
.add_notification(
notification_id,
Notification::new("Zed failed to launch")
.body(Some(format!("{:?}", e).as_str()))
.priority(Priority::High)
.icon(ashpd::desktop::Icon::with_names(&[
"dialog-question-symbolic",
])),
)
.await?;
cx.update(|cx| {
cx.quit();
})
})
.detach();
}
}
enum AppMode { enum AppMode {
Headless(DevServerToken), Headless(DevServerToken),
Ui, Ui,
@@ -122,10 +159,6 @@ fn init_ui(app_state: Arc<AppState>, cx: &mut AppContext) -> Result<()> {
} }
}; };
if let Err(err) = cx.can_open_windows() {
return Err(err);
}
SystemAppearance::init(cx); SystemAppearance::init(cx);
load_embedded_fonts(cx); load_embedded_fonts(cx);
@@ -319,7 +352,11 @@ fn main() {
{ {
cx.spawn({ cx.spawn({
let app_state = app_state.clone(); let app_state = app_state.clone();
|cx| async move { restore_or_create_workspace(app_state, cx).await } |mut cx| async move {
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
fail_to_open_window_async(e, &mut cx)
}
}
}) })
.detach(); .detach();
} }
@@ -421,7 +458,11 @@ fn main() {
init_ui(app_state.clone(), cx).unwrap(); init_ui(app_state.clone(), cx).unwrap();
cx.spawn({ cx.spawn({
let app_state = app_state.clone(); let app_state = app_state.clone();
|cx| async move { restore_or_create_workspace(app_state, cx).await } |mut cx| async move {
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
fail_to_open_window_async(e, &mut cx)
}
}
}) })
.detach(); .detach();
} }
@@ -448,13 +489,12 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
let app_state = app_state.clone(); let app_state = app_state.clone();
cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx)) cx.spawn(move |cx| handle_cli_connection(connection, app_state, cx))
.detach(); .detach();
return;
} }
if let Err(e) = init_ui(app_state.clone(), cx) { if let Err(e) = init_ui(app_state.clone(), cx) {
log::error!("{}", e); fail_to_open_window(e, cx);
return; return;
} };
let mut task = None; let mut task = None;
if !request.open_paths.is_empty() { if !request.open_paths.is_empty() {
@@ -478,48 +518,59 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
if !request.open_channel_notes.is_empty() || request.join_channel.is_some() { if !request.open_channel_notes.is_empty() || request.join_channel.is_some() {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
if let Some(task) = task { let result = maybe!(async {
task.await?; if let Some(task) = task {
} task.await?;
let client = app_state.client.clone(); }
// we continue even if authentication fails as join_channel/ open channel notes will let client = app_state.client.clone();
// show a visible error message. // we continue even if authentication fails as join_channel/ open channel notes will
authenticate(client, &cx).await.log_err(); // show a visible error message.
authenticate(client, &cx).await.log_err();
if let Some(channel_id) = request.join_channel { if let Some(channel_id) = request.join_channel {
cx.update(|cx| { cx.update(|cx| {
workspace::join_channel( workspace::join_channel(
client::ChannelId(channel_id), client::ChannelId(channel_id),
app_state.clone(), app_state.clone(),
None, None,
cx, cx,
) )
})? })?
.await?; .await?;
} }
let workspace_window = let workspace_window =
workspace::get_any_active_workspace(app_state, cx.clone()).await?; workspace::get_any_active_workspace(app_state, cx.clone()).await?;
let workspace = workspace_window.root_view(&cx)?; let workspace = workspace_window.root_view(&cx)?;
let mut promises = Vec::new(); let mut promises = Vec::new();
for (channel_id, heading) in request.open_channel_notes { for (channel_id, heading) in request.open_channel_notes {
promises.push(cx.update_window(workspace_window.into(), |_, cx| { promises.push(cx.update_window(workspace_window.into(), |_, cx| {
ChannelView::open( ChannelView::open(
client::ChannelId(channel_id), client::ChannelId(channel_id),
heading, heading,
workspace.clone(), workspace.clone(),
cx, cx,
) )
.log_err() .log_err()
})?) })?)
}
future::join_all(promises).await;
anyhow::Ok(())
})
.await;
if let Err(err) = result {
fail_to_open_window_async(err, &mut cx);
} }
future::join_all(promises).await;
anyhow::Ok(())
}) })
.detach_and_log_err(cx); .detach()
} else if let Some(task) = task { } else if let Some(task) = task {
task.detach_and_log_err(cx) cx.spawn(|mut cx| async move {
if let Err(err) = task.await {
fail_to_open_window_async(err, &mut cx);
}
})
.detach();
} }
} }
@@ -562,41 +613,39 @@ async fn installation_id() -> Result<(String, bool)> {
Ok((installation_id, false)) Ok((installation_id, false))
} }
async fn restore_or_create_workspace(app_state: Arc<AppState>, cx: AsyncAppContext) { async fn restore_or_create_workspace(
maybe!(async { app_state: Arc<AppState>,
let restore_behaviour = cx: &mut AsyncAppContext,
cx.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)?; ) -> Result<()> {
let location = match restore_behaviour { let restore_behaviour = cx.update(|cx| WorkspaceSettings::get(None, cx).restore_on_startup)?;
workspace::RestoreOnStartupBehaviour::LastWorkspace => { let location = match restore_behaviour {
workspace::last_opened_workspace_paths().await workspace::RestoreOnStartupBehaviour::LastWorkspace => {
} workspace::last_opened_workspace_paths().await
_ => None,
};
if let Some(location) = location {
cx.update(|cx| {
workspace::open_paths(
location.paths().as_ref(),
app_state,
workspace::OpenOptions::default(),
cx,
)
})?
.await
.log_err();
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
cx.update(|cx| show_welcome_view(app_state, cx)).log_err();
} else {
cx.update(|cx| {
workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
.detach();
})?;
} }
anyhow::Ok(()) _ => None,
}) };
.await if let Some(location) = location {
.log_err(); cx.update(|cx| {
workspace::open_paths(
location.paths().as_ref(),
app_state,
workspace::OpenOptions::default(),
cx,
)
})?
.await?;
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
cx.update(|cx| show_welcome_view(app_state, cx))?.await?;
} else {
cx.update(|cx| {
workspace::open_new(app_state, cx, |workspace, cx| {
Editor::new_file(workspace, &Default::default(), cx)
})
})?
.await?;
}
Ok(())
} }
fn init_paths() -> anyhow::Result<()> { fn init_paths() -> anyhow::Result<()> {

View File

@@ -1314,7 +1314,8 @@ mod tests {
Editor::new_file(workspace, &Default::default(), cx) Editor::new_file(workspace, &Default::default(), cx)
}) })
}) })
.await; .await
.unwrap();
cx.run_until_parked(); cx.run_until_parked();
let workspace = cx let workspace = cx