From 5eac797a93a0d684c0d36d6b4904c0d21f7f9578 Mon Sep 17 00:00:00 2001 From: Kay Simmons Date: Wed, 25 Jan 2023 11:30:03 -0800 Subject: [PATCH] mostly working now --- Cargo.lock | 11 + crates/collab/src/tests.rs | 2 +- crates/collab_ui/src/collab_ui.rs | 25 +- crates/gpui/Cargo.toml | 3 +- crates/gpui/src/app.rs | 43 +-- crates/gpui/src/platform.rs | 96 ++++++- crates/gpui/src/platform/mac/platform.rs | 12 +- crates/gpui/src/platform/mac/screen.rs | 67 ++++- crates/gpui/src/platform/mac/status_item.rs | 96 ++++--- crates/gpui/src/platform/mac/window.rs | 111 ++++--- crates/gpui/src/platform/test.rs | 167 ++++++----- crates/sqlez/Cargo.toml | 1 + crates/sqlez/src/bindable.rs | 41 ++- crates/workspace/Cargo.toml | 1 + crates/workspace/src/dock.rs | 2 +- crates/workspace/src/persistence.rs | 304 ++++++++++---------- crates/workspace/src/persistence/model.rs | 21 +- crates/workspace/src/workspace.rs | 35 +-- crates/zed/Cargo.toml | 1 + crates/zed/src/zed.rs | 16 +- 20 files changed, 638 insertions(+), 417 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f858a61aaa7a7415fa9d33b0315a3bf974a00212..927010c4d057b8bf318567b6744d57a1c49fc9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1275,6 +1275,7 @@ source = "git+https://github.com/servo/core-foundation-rs?rev=079665882507dd5e2f dependencies = [ "core-foundation-sys", "libc", + "uuid 0.5.1", ] [[package]] @@ -2591,6 +2592,7 @@ dependencies = [ "tiny-skia", "usvg", "util", + "uuid 1.2.2", "waker-fn", ] @@ -6014,6 +6016,7 @@ dependencies = [ "parking_lot 0.11.2", "smol", "thread_local", + "uuid 1.2.2", ] [[package]] @@ -7324,6 +7327,12 @@ dependencies = [ "tempdir", ] +[[package]] +name = "uuid" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc7e3b898aa6f6c08e5295b6c89258d1331e9ac578cc992fb818759951bdc22" + [[package]] name = "uuid" version = "0.8.2" @@ -8169,6 +8178,7 @@ dependencies = [ "smallvec", "theme", "util", + "uuid 1.2.2", ] [[package]] @@ -8312,6 +8322,7 @@ dependencies = [ "url", "urlencoding", "util", + "uuid 1.2.2", "vim", "workspace", ] diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index fb018a901759afeab4e0b200ac12c1b4173cba7b..120c577e0f215c42068003ed39b3acc9ebbd07ab 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -196,7 +196,7 @@ impl TestServer { languages: Arc::new(LanguageRegistry::new(Task::ready(()))), themes: ThemeRegistry::new((), cx.font_cache()), fs: fs.clone(), - build_window_options: |_| Default::default(), + build_window_options: |_, _, _| Default::default(), initialize_workspace: |_, _, _| unimplemented!(), dock_default_item_factory: |_, _| unimplemented!(), }); diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 2d6b9489d809ca975ed80ceabda3afad27f57a33..38a47e87dcb6b5e2f420297e8d279a80f5f5688c 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -54,17 +54,20 @@ pub fn init(app_state: Arc, cx: &mut MutableAppContext) { }) .await?; - let (_, workspace) = cx.add_window((app_state.build_window_options)(None), |cx| { - let mut workspace = Workspace::new( - Default::default(), - 0, - project, - app_state.dock_default_item_factory, - cx, - ); - (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - workspace - }); + let (_, workspace) = cx.add_window( + (app_state.build_window_options)(None, None, cx.platform().as_ref()), + |cx| { + let mut workspace = Workspace::new( + Default::default(), + 0, + project, + app_state.dock_default_item_factory, + cx, + ); + (app_state.initialize_workspace)(&mut workspace, &app_state, cx); + workspace + }, + ); workspace }; diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index e1b6e11b46896787529a283518982c6811e2c6b5..7be254be4da2b0c0f47b9f03f4ae437df1407261 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -47,6 +47,7 @@ smol = "1.2" time = { version = "0.3", features = ["serde", "serde-well-known"] } tiny-skia = "0.5" usvg = "0.14" +uuid = { version = "1.1.2", features = ["v4"] } waker-fn = "1.1.0" [build-dependencies] @@ -66,7 +67,7 @@ media = { path = "../media" } anyhow = "1" block = "0.1" cocoa = "0.24" -core-foundation = "0.9.3" +core-foundation = { version = "0.9.3", features = ["with-uuid"] } core-graphics = "0.22.3" core-text = "19.2" font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "8eaf7a918eafa28b0a37dc759e2e0e7683fa24f1" } diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index df62580c4d0a66bffc3899aa3c4a0f571c0fda53..c51de8c0c8333b8de84426cdbd2fe8d5ab7e11a7 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -32,6 +32,7 @@ use collections::{hash_map::Entry, HashMap, HashSet, VecDeque}; use platform::Event; #[cfg(any(test, feature = "test-support"))] pub use test_app_context::{ContextHandle, TestAppContext}; +use uuid::Uuid; use crate::{ elements::ElementBox, @@ -595,7 +596,7 @@ type ReleaseObservationCallback = Box; type WindowActivationCallback = Box bool>; type WindowFullscreenCallback = Box bool>; -type WindowBoundsCallback = Box bool>; +type WindowBoundsCallback = Box bool>; type KeystrokeCallback = Box< dyn FnMut(&Keystroke, &MatchResult, Option<&Box>, &mut MutableAppContext) -> bool, >; @@ -909,10 +910,17 @@ impl MutableAppContext { .map_or(false, |window| window.is_fullscreen) } - pub fn window_bounds(&self, window_id: usize) -> RectF { + pub fn window_bounds(&self, window_id: usize) -> WindowBounds { self.presenters_and_platform_windows[&window_id].1.bounds() } + pub fn window_display_uuid(&self, window_id: usize) -> Uuid { + self.presenters_and_platform_windows[&window_id] + .1 + .screen() + .display_uuid() + } + pub fn render_view(&mut self, params: RenderParams) -> Result { let window_id = params.window_id; let view_id = params.view_id; @@ -1246,7 +1254,7 @@ impl MutableAppContext { fn observe_window_bounds(&mut self, window_id: usize, callback: F) -> Subscription where - F: 'static + FnMut(WindowBounds, &mut MutableAppContext) -> bool, + F: 'static + FnMut(WindowBounds, Uuid, &mut MutableAppContext) -> bool, { let subscription_id = post_inc(&mut self.next_subscription_id); self.pending_effects @@ -2382,14 +2390,12 @@ impl MutableAppContext { callback(is_fullscreen, this) }); - let bounds = if this.window_is_fullscreen(window_id) { - WindowBounds::Fullscreen - } else { - WindowBounds::Fixed(this.window_bounds(window_id)) - }; - + let bounds = this.window_bounds(window_id); + let uuid = this.window_display_uuid(window_id); let mut bounds_observations = this.window_bounds_observations.clone(); - bounds_observations.emit(window_id, this, |callback, this| callback(bounds, this)); + bounds_observations.emit(window_id, this, |callback, this| { + callback(bounds, uuid, this) + }); Some(()) }); @@ -2568,15 +2574,12 @@ impl MutableAppContext { } fn handle_window_moved(&mut self, window_id: usize) { - let bounds = if self.window_is_fullscreen(window_id) { - WindowBounds::Fullscreen - } else { - WindowBounds::Fixed(self.window_bounds(window_id)) - }; + let bounds = self.window_bounds(window_id); + let display = self.window_display_uuid(window_id); self.window_bounds_observations .clone() .emit(window_id, self, move |callback, this| { - callback(bounds, this); + callback(bounds, display, this); true }); } @@ -3717,7 +3720,7 @@ impl<'a, T: View> ViewContext<'a, T> { self.app.toggle_window_full_screen(self.window_id) } - pub fn window_bounds(&self) -> RectF { + pub fn window_bounds(&self) -> WindowBounds { self.app.window_bounds(self.window_id) } @@ -4023,14 +4026,14 @@ impl<'a, T: View> ViewContext<'a, T> { pub fn observe_window_bounds(&mut self, mut callback: F) -> Subscription where - F: 'static + FnMut(&mut T, WindowBounds, &mut ViewContext), + F: 'static + FnMut(&mut T, WindowBounds, Uuid, &mut ViewContext), { let observer = self.weak_handle(); self.app - .observe_window_bounds(self.window_id(), move |bounds, cx| { + .observe_window_bounds(self.window_id(), move |bounds, display, cx| { if let Some(observer) = observer.upgrade(cx) { observer.update(cx, |observer, cx| { - callback(observer, bounds, cx); + callback(observer, bounds, display, cx); }); true } else { diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 1db1fe62b0926ab918a24aa95dac17584d92c382..e91c1e87aa0e6b0fe361665982af56073d9fb600 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -18,11 +18,15 @@ use crate::{ text_layout::{LineLayout, RunStyle}, Action, ClipboardItem, Menu, Scene, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use async_task::Runnable; pub use event::*; use postage::oneshot; use serde::Deserialize; +use sqlez::{ + bindable::{Bind, Column, StaticColumnCount}, + statement::Statement, +}; use std::{ any::Any, fmt::{self, Debug, Display}, @@ -33,6 +37,7 @@ use std::{ sync::Arc, }; use time::UtcOffset; +use uuid::Uuid; pub trait Platform: Send + Sync { fn dispatcher(&self) -> Arc; @@ -44,6 +49,7 @@ pub trait Platform: Send + Sync { fn unhide_other_apps(&self); fn quit(&self); + fn screen_by_id(&self, id: Uuid) -> Option>; fn screens(&self) -> Vec>; fn open_window( @@ -118,17 +124,18 @@ pub trait InputHandler { pub trait Screen: Debug { fn as_any(&self) -> &dyn Any; fn size(&self) -> Vector2F; + fn display_uuid(&self) -> Uuid; } pub trait Window { + fn bounds(&self) -> WindowBounds; + fn content_size(&self) -> Vector2F; + fn scale_factor(&self) -> f32; + fn titlebar_height(&self) -> f32; + fn appearance(&self) -> Appearance; + fn screen(&self) -> Rc; + fn as_any_mut(&mut self) -> &mut dyn Any; - fn on_event(&mut self, callback: Box bool>); - fn on_active_status_change(&mut self, callback: Box); - fn on_resize(&mut self, callback: Box); - fn on_fullscreen(&mut self, callback: Box); - fn on_moved(&mut self, callback: Box); - fn on_should_close(&mut self, callback: Box bool>); - fn on_close(&mut self, callback: Box); fn set_input_handler(&mut self, input_handler: Box); fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; fn activate(&self); @@ -137,14 +144,16 @@ pub trait Window { fn show_character_palette(&self); fn minimize(&self); fn zoom(&self); + fn present_scene(&mut self, scene: Scene); fn toggle_full_screen(&self); - fn bounds(&self) -> RectF; - fn content_size(&self) -> Vector2F; - fn scale_factor(&self) -> f32; - fn titlebar_height(&self) -> f32; - fn present_scene(&mut self, scene: Scene); - fn appearance(&self) -> Appearance; + fn on_event(&mut self, callback: Box bool>); + fn on_active_status_change(&mut self, callback: Box); + fn on_resize(&mut self, callback: Box); + fn on_fullscreen(&mut self, callback: Box); + fn on_moved(&mut self, callback: Box); + fn on_should_close(&mut self, callback: Box bool>); + fn on_close(&mut self, callback: Box); fn on_appearance_changed(&mut self, callback: Box); fn is_topmost_for_position(&self, position: Vector2F) -> bool; } @@ -187,13 +196,70 @@ pub enum WindowKind { PopUp, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub enum WindowBounds { Fullscreen, Maximized, Fixed(RectF), } +impl StaticColumnCount for WindowBounds { + fn column_count() -> usize { + 5 + } +} + +impl Bind for WindowBounds { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + let (region, next_index) = match self { + WindowBounds::Fullscreen => { + let next_index = statement.bind("Fullscreen", start_index)?; + (None, next_index) + } + WindowBounds::Maximized => { + let next_index = statement.bind("Maximized", start_index)?; + (None, next_index) + } + WindowBounds::Fixed(region) => { + let next_index = statement.bind("Fixed", start_index)?; + (Some(*region), next_index) + } + }; + + statement.bind( + region.map(|region| { + ( + region.min_x(), + region.min_y(), + region.width(), + region.height(), + ) + }), + next_index, + ) + } +} + +impl Column for WindowBounds { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (window_state, next_index) = String::column(statement, start_index)?; + let bounds = match window_state.as_str() { + "Fullscreen" => WindowBounds::Fullscreen, + "Maximized" => WindowBounds::Maximized, + "Fixed" => { + let ((x, y, width, height), _) = Column::column(statement, next_index)?; + WindowBounds::Fixed(RectF::new( + Vector2F::new(x, y), + Vector2F::new(width, height), + )) + } + _ => bail!("Window State did not have a valid string"), + }; + + Ok((bounds, next_index + 4)) + } +} + pub struct PathPromptOptions { pub files: bool, pub directories: bool, diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index dbb1a01f31136c20faa429271d840af0e66d6486..5d132275854c8b92e317b8226cf4d869dd92f422 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -440,6 +440,10 @@ impl platform::Platform for MacPlatform { self.dispatcher.clone() } + fn fonts(&self) -> Arc { + self.fonts.clone() + } + fn activate(&self, ignoring_other_apps: bool) { unsafe { let app = NSApplication::sharedApplication(nil); @@ -488,6 +492,10 @@ impl platform::Platform for MacPlatform { } } + fn screen_by_id(&self, id: uuid::Uuid) -> Option> { + Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) + } + fn screens(&self) -> Vec> { Screen::all() .into_iter() @@ -512,10 +520,6 @@ impl platform::Platform for MacPlatform { Box::new(StatusItem::add(self.fonts())) } - fn fonts(&self) -> Arc { - self.fonts.clone() - } - fn write_to_clipboard(&self, item: ClipboardItem) { unsafe { self.pasteboard.clearContents(); diff --git a/crates/gpui/src/platform/mac/screen.rs b/crates/gpui/src/platform/mac/screen.rs index fdc7fbb50581a1d13f465e23ed1d7c50187fb590..a54ffb3f90f9e322a4053ee777a5e4172e3591fe 100644 --- a/crates/gpui/src/platform/mac/screen.rs +++ b/crates/gpui/src/platform/mac/screen.rs @@ -1,4 +1,4 @@ -use std::any::Any; +use std::{any::Any, ffi::c_void}; use crate::{ geometry::vector::{vec2f, Vector2F}, @@ -7,8 +7,19 @@ use crate::{ use cocoa::{ appkit::NSScreen, base::{id, nil}, - foundation::NSArray, + foundation::{NSArray, NSDictionary, NSString}, }; +use core_foundation::{ + number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef}, + uuid::{CFUUIDGetUUIDBytes, CFUUIDRef}, +}; +use core_graphics::display::CGDirectDisplayID; +use uuid::Uuid; + +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef; +} #[derive(Debug)] pub struct Screen { @@ -16,11 +27,23 @@ pub struct Screen { } impl Screen { + pub fn find_by_id(uuid: Uuid) -> Option { + unsafe { + let native_screens = NSScreen::screens(nil); + (0..NSArray::count(native_screens)) + .into_iter() + .map(|ix| Screen { + native_screen: native_screens.objectAtIndex(ix), + }) + .find(|screen| platform::Screen::display_uuid(screen) == uuid) + } + } + pub fn all() -> Vec { let mut screens = Vec::new(); unsafe { let native_screens = NSScreen::screens(nil); - for ix in 0..native_screens.count() { + for ix in 0..NSArray::count(native_screens) { screens.push(Screen { native_screen: native_screens.objectAtIndex(ix), }); @@ -41,4 +64,42 @@ impl platform::Screen for Screen { vec2f(frame.size.width as f32, frame.size.height as f32) } } + + fn display_uuid(&self) -> uuid::Uuid { + unsafe { + // Screen ids are not stable. Further, the default device id is also unstable across restarts. + // CGDisplayCreateUUIDFromDisplayID is stable but not exposed in the bindings we use. + // This approach is similar to that which winit takes + // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99 + let device_description = self.native_screen.deviceDescription(); + let key = NSString::alloc(nil).init_str("NSScreenNumber"); + let device_id_obj = device_description.objectForKey_(key); + let mut device_id: u32 = 0; + CFNumberGetValue( + device_id_obj as CFNumberRef, + kCFNumberIntType, + (&mut device_id) as *mut _ as *mut c_void, + ); + let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID); + let bytes = CFUUIDGetUUIDBytes(cfuuid); + Uuid::from_bytes([ + bytes.byte0, + bytes.byte1, + bytes.byte2, + bytes.byte3, + bytes.byte4, + bytes.byte5, + bytes.byte6, + bytes.byte7, + bytes.byte8, + bytes.byte9, + bytes.byte10, + bytes.byte11, + bytes.byte12, + bytes.byte13, + bytes.byte14, + bytes.byte15, + ]) + } + } } diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index 8ac9dbea71de4ccd724d5a718c42a4e11876aad5..812027d35c55a38101539709554a15e9b2ef06f6 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -7,7 +7,7 @@ use crate::{ self, mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer}, }, - Event, FontSystem, Scene, + Event, FontSystem, Scene, WindowBounds, }; use cocoa::{ appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow}, @@ -32,6 +32,8 @@ use std::{ sync::Arc, }; +use super::screen::Screen; + static mut VIEW_CLASS: *const Class = ptr::null(); const STATE_IVAR: &str = "state"; @@ -167,29 +169,41 @@ impl StatusItem { } impl platform::Window for StatusItem { - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { - self + fn bounds(&self) -> WindowBounds { + self.0.borrow().bounds() } - fn on_event(&mut self, callback: Box bool>) { - self.0.borrow_mut().event_callback = Some(callback); + fn content_size(&self) -> Vector2F { + self.0.borrow().content_size() } - fn on_appearance_changed(&mut self, callback: Box) { - self.0.borrow_mut().appearance_changed_callback = Some(callback); + fn scale_factor(&self) -> f32 { + self.0.borrow().scale_factor() } - fn on_active_status_change(&mut self, _: Box) {} - - fn on_resize(&mut self, _: Box) {} - - fn on_moved(&mut self, _: Box) {} + fn titlebar_height(&self) -> f32 { + 0. + } - fn on_fullscreen(&mut self, _: Box) {} + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = + msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; + crate::Appearance::from_native(appearance) + } + } - fn on_should_close(&mut self, _: Box bool>) {} + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.borrow().native_window().screen(), + }) + } + } - fn on_close(&mut self, _: Box) {} + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } fn set_input_handler(&mut self, _: Box) {} @@ -226,39 +240,35 @@ impl platform::Window for StatusItem { unimplemented!() } + fn present_scene(&mut self, scene: Scene) { + self.0.borrow_mut().scene = Some(scene); + unsafe { + let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES]; + } + } + fn toggle_full_screen(&self) { unimplemented!() } - fn bounds(&self) -> RectF { - self.0.borrow().bounds() + fn on_event(&mut self, callback: Box bool>) { + self.0.borrow_mut().event_callback = Some(callback); } - fn content_size(&self) -> Vector2F { - self.0.borrow().content_size() - } + fn on_active_status_change(&mut self, _: Box) {} - fn scale_factor(&self) -> f32 { - self.0.borrow().scale_factor() - } + fn on_resize(&mut self, _: Box) {} - fn titlebar_height(&self) -> f32 { - 0. - } + fn on_fullscreen(&mut self, _: Box) {} - fn present_scene(&mut self, scene: Scene) { - self.0.borrow_mut().scene = Some(scene); - unsafe { - let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES]; - } - } + fn on_moved(&mut self, _: Box) {} - fn appearance(&self) -> crate::Appearance { - unsafe { - let appearance: id = - msg_send![self.0.borrow().native_item.button(), effectiveAppearance]; - crate::Appearance::from_native(appearance) - } + fn on_should_close(&mut self, _: Box bool>) {} + + fn on_close(&mut self, _: Box) {} + + fn on_appearance_changed(&mut self, callback: Box) { + self.0.borrow_mut().appearance_changed_callback = Some(callback); } fn is_topmost_for_position(&self, _: Vector2F) -> bool { @@ -267,9 +277,9 @@ impl platform::Window for StatusItem { } impl StatusItemState { - fn bounds(&self) -> RectF { + fn bounds(&self) -> WindowBounds { unsafe { - let window: id = msg_send![self.native_item.button(), window]; + let window: id = self.native_window(); let screen_frame = window.screen().visibleFrame(); let window_frame = NSWindow::frame(window); let origin = vec2f( @@ -281,7 +291,7 @@ impl StatusItemState { window_frame.size.width as f32, window_frame.size.height as f32, ); - RectF::new(origin, size) + WindowBounds::Fixed(RectF::new(origin, size)) } } @@ -299,6 +309,10 @@ impl StatusItemState { NSScreen::backingScaleFactor(window.screen()) as f32 } } + + pub fn native_window(&self) -> id { + unsafe { msg_send![self.native_item.button(), window] } + } } extern "C" fn dealloc_view(this: &Object, _: Sel) { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index c4cd7d0e27d1b8688dbd4231e47553c4ad252ebd..e981cc6131b54c9e561bc64db3fc829143c75afe 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -419,19 +419,17 @@ impl Window { WindowBounds::Fixed(top_left_bounds) => { let frame = screen.visibleFrame(); let bottom_left_bounds = RectF::new( - vec2f( + dbg!(vec2f( top_left_bounds.origin_x(), frame.size.height as f32 - top_left_bounds.origin_y() - top_left_bounds.height(), - ), - top_left_bounds.size(), + )), + dbg!(top_left_bounds.size()), ) .to_ns_rect(); - native_window.setFrame_display_( - native_window.convertRectToScreen_(bottom_left_bounds), - YES, - ); + let screen_rect = native_window.convertRectToScreen_(bottom_left_bounds); + native_window.setFrame_display_(screen_rect, YES); } } @@ -585,36 +583,39 @@ impl Drop for Window { } impl platform::Window for Window { - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn on_event(&mut self, callback: Box bool>) { - self.0.as_ref().borrow_mut().event_callback = Some(callback); + fn bounds(&self) -> WindowBounds { + self.0.as_ref().borrow().bounds() } - fn on_resize(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().resize_callback = Some(callback); + fn content_size(&self) -> Vector2F { + self.0.as_ref().borrow().content_size() } - fn on_moved(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().moved_callback = Some(callback); + fn scale_factor(&self) -> f32 { + self.0.as_ref().borrow().scale_factor() } - fn on_fullscreen(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); + fn titlebar_height(&self) -> f32 { + self.0.as_ref().borrow().titlebar_height() } - fn on_should_close(&mut self, callback: Box bool>) { - self.0.as_ref().borrow_mut().should_close_callback = Some(callback); + fn appearance(&self) -> crate::Appearance { + unsafe { + let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance]; + crate::Appearance::from_native(appearance) + } } - fn on_close(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().close_callback = Some(callback); + fn screen(&self) -> Rc { + unsafe { + Rc::new(Screen { + native_screen: self.0.as_ref().borrow().native_window.screen(), + }) + } } - fn on_active_status_change(&mut self, callback: Box) { - self.0.as_ref().borrow_mut().activate_callback = Some(callback); + fn as_any_mut(&mut self) -> &mut dyn Any { + self } fn set_input_handler(&mut self, input_handler: Box) { @@ -726,6 +727,10 @@ impl platform::Window for Window { .detach(); } + fn present_scene(&mut self, scene: Scene) { + self.0.as_ref().borrow_mut().present_scene(scene); + } + fn toggle_full_screen(&self) { let this = self.0.borrow(); let window = this.native_window; @@ -738,31 +743,32 @@ impl platform::Window for Window { .detach(); } - fn bounds(&self) -> RectF { - self.0.as_ref().borrow().bounds() + fn on_event(&mut self, callback: Box bool>) { + self.0.as_ref().borrow_mut().event_callback = Some(callback); } - fn content_size(&self) -> Vector2F { - self.0.as_ref().borrow().content_size() + fn on_active_status_change(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().activate_callback = Some(callback); } - fn scale_factor(&self) -> f32 { - self.0.as_ref().borrow().scale_factor() + fn on_resize(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().resize_callback = Some(callback); } - fn present_scene(&mut self, scene: Scene) { - self.0.as_ref().borrow_mut().present_scene(scene); + fn on_fullscreen(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback); } - fn titlebar_height(&self) -> f32 { - self.0.as_ref().borrow().titlebar_height() + fn on_moved(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().moved_callback = Some(callback); } - fn appearance(&self) -> crate::Appearance { - unsafe { - let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance]; - crate::Appearance::from_native(appearance) - } + fn on_should_close(&mut self, callback: Box bool>) { + self.0.as_ref().borrow_mut().should_close_callback = Some(callback); + } + + fn on_close(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().close_callback = Some(callback); } fn on_appearance_changed(&mut self, callback: Box) { @@ -846,20 +852,39 @@ impl WindowState { } } - fn bounds(&self) -> RectF { + fn is_fullscreen(&self) -> bool { unsafe { + let style_mask = self.native_window.styleMask(); + style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask) + } + } + + fn bounds(&self) -> WindowBounds { + unsafe { + if self.is_fullscreen() { + return WindowBounds::Fullscreen; + } + let screen_frame = self.native_window.screen().visibleFrame(); let window_frame = NSWindow::frame(self.native_window); let origin = vec2f( window_frame.origin.x as f32, - (window_frame.origin.y - screen_frame.size.height - window_frame.size.height) + (screen_frame.size.height - window_frame.origin.y - window_frame.size.height) as f32, ); let size = vec2f( window_frame.size.width as f32, window_frame.size.height as f32, ); - RectF::new(origin, size) + + if origin.is_zero() + && size.x() == screen_frame.size.width as f32 + && size.y() == screen_frame.size.height as f32 + { + WindowBounds::Maximized + } else { + WindowBounds::Fixed(RectF::new(origin, size)) + } } } diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index f3e1ce405577811e7d278359e99586427c285f47..2c8a9409694c96d58ff49b2d902ed652710b04d6 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -20,11 +20,20 @@ use std::{ }; use time::UtcOffset; -pub struct Platform { - dispatcher: Arc, - fonts: Arc, - current_clipboard_item: Mutex>, - cursor: Mutex, +struct Dispatcher; + +impl super::Dispatcher for Dispatcher { + fn is_main_thread(&self) -> bool { + true + } + + fn run_on_main_thread(&self, task: async_task::Runnable) { + task.run(); + } +} + +pub fn foreground_platform() -> ForegroundPlatform { + ForegroundPlatform::default() } #[derive(Default)] @@ -32,24 +41,6 @@ pub struct ForegroundPlatform { last_prompt_for_new_path_args: RefCell>)>>, } -struct Dispatcher; - -pub struct Window { - pub(crate) size: Vector2F, - scale_factor: f32, - current_scene: Option, - event_handlers: Vec bool>>, - pub(crate) resize_handlers: Vec>, - pub(crate) moved_handlers: Vec>, - close_handlers: Vec>, - fullscreen_handlers: Vec>, - pub(crate) active_status_change_handlers: Vec>, - pub(crate) should_close_handler: Option bool>>, - pub(crate) title: Option, - pub(crate) edited: bool, - pub(crate) pending_prompts: RefCell>>, -} - #[cfg(any(test, feature = "test-support"))] impl ForegroundPlatform { pub(crate) fn simulate_new_path_selection( @@ -103,6 +94,17 @@ impl super::ForegroundPlatform for ForegroundPlatform { } } +pub fn platform() -> Platform { + Platform::new() +} + +pub struct Platform { + dispatcher: Arc, + fonts: Arc, + current_clipboard_item: Mutex>, + cursor: Mutex, +} + impl Platform { fn new() -> Self { Self { @@ -133,6 +135,10 @@ impl super::Platform for Platform { fn quit(&self) {} + fn screen_by_id(&self, _id: uuid::Uuid) -> Option> { + None + } + fn screens(&self) -> Vec> { Default::default() } @@ -220,6 +226,39 @@ impl super::Platform for Platform { } } +#[derive(Debug)] +pub struct Screen; + +impl super::Screen for Screen { + fn as_any(&self) -> &dyn Any { + self + } + + fn size(&self) -> Vector2F { + Vector2F::new(1920., 1080.) + } + + fn display_uuid(&self) -> uuid::Uuid { + uuid::Uuid::new_v4() + } +} + +pub struct Window { + pub(crate) size: Vector2F, + scale_factor: f32, + current_scene: Option, + event_handlers: Vec bool>>, + pub(crate) resize_handlers: Vec>, + pub(crate) moved_handlers: Vec>, + close_handlers: Vec>, + fullscreen_handlers: Vec>, + pub(crate) active_status_change_handlers: Vec>, + pub(crate) should_close_handler: Option bool>>, + pub(crate) title: Option, + pub(crate) edited: bool, + pub(crate) pending_prompts: RefCell>>, +} + impl Window { fn new(size: Vector2F) -> Self { Self { @@ -244,43 +283,33 @@ impl Window { } } -impl super::Dispatcher for Dispatcher { - fn is_main_thread(&self) -> bool { - true - } - - fn run_on_main_thread(&self, task: async_task::Runnable) { - task.run(); - } -} - impl super::Window for Window { - fn as_any_mut(&mut self) -> &mut dyn Any { - self + fn bounds(&self) -> WindowBounds { + WindowBounds::Fixed(RectF::new(Vector2F::zero(), self.size)) } - fn on_event(&mut self, callback: Box bool>) { - self.event_handlers.push(callback); + fn content_size(&self) -> Vector2F { + self.size } - fn on_active_status_change(&mut self, callback: Box) { - self.active_status_change_handlers.push(callback); + fn scale_factor(&self) -> f32 { + self.scale_factor } - fn on_fullscreen(&mut self, callback: Box) { - self.fullscreen_handlers.push(callback) + fn titlebar_height(&self) -> f32 { + 24. } - fn on_resize(&mut self, callback: Box) { - self.resize_handlers.push(callback); + fn appearance(&self) -> crate::Appearance { + crate::Appearance::Light } - fn on_moved(&mut self, callback: Box) { - self.moved_handlers.push(callback); + fn screen(&self) -> Rc { + Rc::new(Screen) } - fn on_close(&mut self, callback: Box) { - self.close_handlers.push(callback); + fn as_any_mut(&mut self) -> &mut dyn Any { + self } fn set_input_handler(&mut self, _: Box) {} @@ -301,40 +330,44 @@ impl super::Window for Window { self.edited = edited; } - fn on_should_close(&mut self, callback: Box bool>) { - self.should_close_handler = Some(callback); - } - fn show_character_palette(&self) {} fn minimize(&self) {} fn zoom(&self) {} + fn present_scene(&mut self, scene: crate::Scene) { + self.current_scene = Some(scene); + } + fn toggle_full_screen(&self) {} - fn bounds(&self) -> RectF { - RectF::new(Default::default(), self.size) + fn on_event(&mut self, callback: Box bool>) { + self.event_handlers.push(callback); } - fn content_size(&self) -> Vector2F { - self.size + fn on_active_status_change(&mut self, callback: Box) { + self.active_status_change_handlers.push(callback); } - fn scale_factor(&self) -> f32 { - self.scale_factor + fn on_resize(&mut self, callback: Box) { + self.resize_handlers.push(callback); } - fn titlebar_height(&self) -> f32 { - 24. + fn on_fullscreen(&mut self, callback: Box) { + self.fullscreen_handlers.push(callback) } - fn present_scene(&mut self, scene: crate::Scene) { - self.current_scene = Some(scene); + fn on_moved(&mut self, callback: Box) { + self.moved_handlers.push(callback); } - fn appearance(&self) -> crate::Appearance { - crate::Appearance::Light + fn on_should_close(&mut self, callback: Box bool>) { + self.should_close_handler = Some(callback); + } + + fn on_close(&mut self, callback: Box) { + self.close_handlers.push(callback); } fn on_appearance_changed(&mut self, _: Box) {} @@ -343,11 +376,3 @@ impl super::Window for Window { true } } - -pub fn platform() -> Platform { - Platform::new() -} - -pub fn foreground_platform() -> ForegroundPlatform { - ForegroundPlatform::default() -} diff --git a/crates/sqlez/Cargo.toml b/crates/sqlez/Cargo.toml index 8409a1dff5ac2c46c9b3731be660388a9707e61c..716ec766443bc997c57fb77f78d94f0290aad579 100644 --- a/crates/sqlez/Cargo.toml +++ b/crates/sqlez/Cargo.toml @@ -15,3 +15,4 @@ thread_local = "1.1.4" lazy_static = "1.4" parking_lot = "0.11.1" futures = "0.3" +uuid = { version = "1.1.2", features = ["v4"] } \ No newline at end of file diff --git a/crates/sqlez/src/bindable.rs b/crates/sqlez/src/bindable.rs index 9d5d0c8b2f9a62a43dddca9b8c9460bb0edb5f9f..86d69afe5f8203224f5db5f7fd319b75ecb993c8 100644 --- a/crates/sqlez/src/bindable.rs +++ b/crates/sqlez/src/bindable.rs @@ -60,6 +60,14 @@ impl Bind for &[u8; C] { } } +impl Column for [u8; C] { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let bytes_slice = statement.column_blob(start_index)?; + let array = bytes_slice.try_into()?; + Ok((array, start_index + 1)) + } +} + impl StaticColumnCount for Vec {} impl Bind for Vec { fn bind(&self, statement: &Statement, start_index: i32) -> Result { @@ -236,8 +244,7 @@ impl Bind for Option { if let Some(this) = self { this.bind(statement, start_index) } else { - for i in 0..T::column_count() { - dbg!(i); + for _ in 0..T::column_count() { statement.bind_null(start_index)?; start_index += 1; } @@ -272,17 +279,6 @@ impl Bind for [T; COUNT] { } } -impl Column for [T; COUNT] { - fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { - let mut array = [Default::default(); COUNT]; - let mut current_index = start_index; - for i in 0..COUNT { - (array[i], current_index) = T::column(statement, current_index)?; - } - Ok((array, current_index)) - } -} - impl StaticColumnCount for &Path {} impl Bind for &Path { fn bind(&self, statement: &Statement, start_index: i32) -> Result { @@ -315,6 +311,25 @@ impl Column for PathBuf { } } +impl StaticColumnCount for uuid::Uuid { + fn column_count() -> usize { + 1 + } +} + +impl Bind for uuid::Uuid { + fn bind(&self, statement: &Statement, start_index: i32) -> Result { + self.as_bytes().bind(statement, start_index) + } +} + +impl Column for uuid::Uuid { + fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> { + let (bytes, next_index) = Column::column(statement, start_index)?; + Ok((uuid::Uuid::from_bytes(bytes), next_index)) + } +} + impl StaticColumnCount for () { fn column_count() -> usize { 0 diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 60680f82a22a3541958e59e6ec219d7174b4b20c..fc069fe6c8d7d481873a22ebc6b679b1aba85632 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -46,6 +46,7 @@ serde = { version = "1.0", features = ["derive", "rc"] } serde_json = { version = "1.0", features = ["preserve_order"] } smallvec = { version = "1.6", features = ["union"] } indoc = "1.0.4" +uuid = { version = "1.1.2", features = ["v4"] } [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7a31d02433193d9f798334486c0027f2c55e5400..1702c6e52112d4a993aed2b94fe6881f57550237 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -534,8 +534,8 @@ mod tests { }], }, left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; let fs = FakeFs::new(cx.background()); diff --git a/crates/workspace/src/persistence.rs b/crates/workspace/src/persistence.rs index 7711ee5141319cf88f44b721bce3c79874a327d1..ddbea4c9f9a2fdfa55d8defc0dccef425330c543 100644 --- a/crates/workspace/src/persistence.rs +++ b/crates/workspace/src/persistence.rs @@ -6,9 +6,10 @@ use std::path::Path; use anyhow::{anyhow, bail, Context, Result}; use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql}; -use gpui::{Axis, geometry::{rect::RectF, vector::Vector2F}}; +use gpui::{Axis, WindowBounds}; use util::{unzip_option, ResultExt}; +use uuid::Uuid; use crate::dock::DockPosition; use crate::WorkspaceId; @@ -29,11 +30,12 @@ define_connection! { // dock_pane: Option, // PaneId // left_sidebar_open: boolean, // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS - // fullscreen bool, // Boolean - // window_x: f32, - // window_y: f32, - // window_width: f32, - // window_height: f32, + // window_state: String, // WindowBounds Discriminant + // window_x: Option, // WindowBounds::Fixed RectF x + // window_y: Option, // WindowBounds::Fixed RectF y + // window_width: Option, // WindowBounds::Fixed RectF width + // window_height: Option, // WindowBounds::Fixed RectF height + // display: Option, // Display id // ) // // pane_groups( @@ -76,7 +78,7 @@ define_connection! { timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY(dock_pane) REFERENCES panes(pane_id) ) STRICT; - + CREATE TABLE pane_groups( group_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, @@ -88,7 +90,7 @@ define_connection! { ON UPDATE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; - + CREATE TABLE panes( pane_id INTEGER PRIMARY KEY, workspace_id INTEGER NOT NULL, @@ -97,7 +99,7 @@ define_connection! { ON DELETE CASCADE ON UPDATE CASCADE ) STRICT; - + CREATE TABLE center_panes( pane_id INTEGER PRIMARY KEY, parent_group_id INTEGER, // NULL means that this is a root pane @@ -106,7 +108,7 @@ define_connection! { ON DELETE CASCADE, FOREIGN KEY(parent_group_id) REFERENCES pane_groups(group_id) ON DELETE CASCADE ) STRICT; - + CREATE TABLE items( item_id INTEGER NOT NULL, // This is the item's view id, so this is not unique workspace_id INTEGER NOT NULL, @@ -123,11 +125,12 @@ define_connection! { ) STRICT; ), sql!( - ALTER TABLE workspaces ADD COLUMN fullscreen INTEGER; // Boolean - ALTER TABLE workspaces ADD COLUMN window_x REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_y REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_width REAL; // Null means set to whatever - ALTER TABLE workspaces ADD COLUMN window_height REAL; // Null means set to whatever + ALTER TABLE workspaces ADD COLUMN window_state TEXT; + ALTER TABLE workspaces ADD COLUMN window_x REAL; + ALTER TABLE workspaces ADD COLUMN window_y REAL; + ALTER TABLE workspaces ADD COLUMN window_width REAL; + ALTER TABLE workspaces ADD COLUMN window_height REAL; + ALTER TABLE workspaces ADD COLUMN display BLOB; )]; } @@ -140,16 +143,16 @@ impl WorkspaceDb { worktree_roots: &[P], ) -> Option { let workspace_location: WorkspaceLocation = worktree_roots.into(); - + // Note that we re-assign the workspace_id here in case it's empty // and we've grabbed the most recent workspace - let (workspace_id, workspace_location, left_sidebar_open, dock_position, fullscreen, window_region): ( + let (workspace_id, workspace_location, left_sidebar_open, dock_position, bounds, display): ( WorkspaceId, WorkspaceLocation, bool, DockPosition, - bool, - Option<(f32, f32, f32, f32)> + Option, + Option, ) = self .select_row_bound(sql! { SELECT @@ -158,11 +161,12 @@ impl WorkspaceDb { left_sidebar_open, dock_visible, dock_anchor, - fullscreen, + window_state, window_x, window_y, window_width, - window_height + window_height, + display FROM workspaces WHERE workspace_location = ? }) @@ -170,7 +174,7 @@ impl WorkspaceDb { .context("No workspaces found") .warn_on_err() .flatten()?; - + Some(SerializedWorkspace { id: workspace_id, location: workspace_location.clone(), @@ -184,14 +188,11 @@ impl WorkspaceDb { .log_err()?, dock_position, left_sidebar_open, - fullscreen, - bounds: dbg!(window_region).map(|(x, y, width, height)| RectF::new( - Vector2F::new(x, y), - Vector2F::new(width, height) - )) + bounds, + display, }) } - + /// Saves a workspace using the worktree roots. Will garbage collect any workspaces /// that used this workspace previously pub async fn save_workspace(&self, workspace: SerializedWorkspace) { @@ -203,12 +204,12 @@ impl WorkspaceDb { DELETE FROM pane_groups WHERE workspace_id = ?1; DELETE FROM panes WHERE workspace_id = ?1;))?(workspace.id) .expect("Clearing old panes"); - + conn.exec_bound(sql!( DELETE FROM workspaces WHERE workspace_location = ? AND workspace_id != ? ))?((&workspace.location, workspace.id.clone())) .context("clearing out old locations")?; - + // Upsert conn.exec_bound(sql!( INSERT INTO workspaces( @@ -222,11 +223,11 @@ impl WorkspaceDb { VALUES (?1, ?2, ?3, ?4, ?5, CURRENT_TIMESTAMP) ON CONFLICT DO UPDATE SET - workspace_location = ?2, - left_sidebar_open = ?3, - dock_visible = ?4, - dock_anchor = ?5, - timestamp = CURRENT_TIMESTAMP + workspace_location = ?2, + left_sidebar_open = ?3, + dock_visible = ?4, + dock_anchor = ?5, + timestamp = CURRENT_TIMESTAMP ))?(( workspace.id, &workspace.location, @@ -234,14 +235,14 @@ impl WorkspaceDb { workspace.dock_position, )) .context("Updating workspace")?; - + // Save center pane group and dock pane Self::save_pane_group(conn, workspace.id, &workspace.center_group, None) .context("save pane group in save workspace")?; - + let dock_id = Self::save_pane(conn, workspace.id, &workspace.dock_pane, None, true) .context("save pane in save workspace")?; - + // Complete workspace initialization conn.exec_bound(sql!( UPDATE workspaces @@ -249,20 +250,20 @@ impl WorkspaceDb { WHERE workspace_id = ? ))?((dock_id, workspace.id)) .context("Finishing initialization with dock pane")?; - + Ok(()) }) .log_err(); }) .await; } - + query! { pub async fn next_id() -> Result { INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id } } - + query! { fn recent_workspaces() -> Result> { SELECT workspace_id, workspace_location @@ -271,14 +272,14 @@ impl WorkspaceDb { ORDER BY timestamp DESC } } - + query! { async fn delete_stale_workspace(id: WorkspaceId) -> Result<()> { DELETE FROM workspaces WHERE workspace_id IS ? } } - + // Returns the recent locations which are still valid on disk and deletes ones which no longer // exist. pub async fn recent_workspaces_on_disk(&self) -> Result> { @@ -286,18 +287,18 @@ impl WorkspaceDb { let mut delete_tasks = Vec::new(); for (id, location) in self.recent_workspaces()? { if location.paths().iter().all(|path| path.exists()) - && location.paths().iter().any(|path| path.is_dir()) + && location.paths().iter().any(|path| path.is_dir()) { result.push((id, location)); } else { delete_tasks.push(self.delete_stale_workspace(id)); } } - + futures::future::join_all(delete_tasks).await; Ok(result) } - + pub async fn last_workspace(&self) -> Result> { Ok(self .recent_workspaces_on_disk() @@ -306,7 +307,7 @@ impl WorkspaceDb { .next() .map(|(_, location)| location)) } - + fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result { Ok(self .get_pane_group(workspace_id, None)? @@ -319,7 +320,7 @@ impl WorkspaceDb { }) })) } - + fn get_pane_group( &self, workspace_id: WorkspaceId, @@ -376,7 +377,7 @@ impl WorkspaceDb { }) .collect::>() } - + fn save_pane_group( conn: &Connection, workspace_id: WorkspaceId, @@ -386,18 +387,18 @@ impl WorkspaceDb { match pane_group { SerializedPaneGroup::Group { axis, children } => { let (parent_id, position) = unzip_option(parent); - + let group_id = conn.select_row_bound::<_, i64>(sql!( INSERT INTO pane_groups(workspace_id, parent_group_id, position, axis) VALUES (?, ?, ?, ?) RETURNING group_id ))?((workspace_id, parent_id, position, *axis))? .ok_or_else(|| anyhow!("Couldn't retrieve group_id from inserted pane_group"))?; - + for (position, group) in children.iter().enumerate() { Self::save_pane_group(conn, workspace_id, group, Some((group_id, position)))? } - + Ok(()) } SerializedPaneGroup::Pane(pane) => { @@ -406,7 +407,7 @@ impl WorkspaceDb { } } } - + fn get_dock_pane(&self, workspace_id: WorkspaceId) -> Result { let (pane_id, active) = self.select_row_bound(sql!( SELECT pane_id, active @@ -414,13 +415,13 @@ impl WorkspaceDb { WHERE pane_id = (SELECT dock_pane FROM workspaces WHERE workspace_id = ?) ))?(workspace_id)? .context("No dock pane for workspace")?; - + Ok(SerializedPane::new( self.get_items(pane_id).context("Reading items")?, active, )) } - + fn save_pane( conn: &Connection, workspace_id: WorkspaceId, @@ -434,7 +435,7 @@ impl WorkspaceDb { RETURNING pane_id ))?((workspace_id, pane.active))? .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?; - + if !dock { let (parent_id, order) = unzip_option(parent); conn.exec_bound(sql!( @@ -442,12 +443,12 @@ impl WorkspaceDb { VALUES (?, ?, ?) ))?((pane_id, parent_id, order))?; } - + Self::save_items(conn, workspace_id, pane_id, &pane.children).context("Saving items")?; - + Ok(pane_id) } - + fn get_items(&self, pane_id: PaneId) -> Result> { Ok(self.select_bound(sql!( SELECT kind, item_id, active FROM items @@ -455,7 +456,7 @@ impl WorkspaceDb { ORDER BY position ))?(pane_id)?) } - + fn save_items( conn: &Connection, workspace_id: WorkspaceId, @@ -468,10 +469,10 @@ impl WorkspaceDb { for (position, item) in items.iter().enumerate() { insert((workspace_id, pane_id, position, item))?; } - + Ok(()) } - + query! { pub async fn update_timestamp(workspace_id: WorkspaceId) -> Result<()> { UPDATE workspaces @@ -479,15 +480,16 @@ impl WorkspaceDb { WHERE workspace_id = ? } } - + query! { - pub async fn set_bounds(workspace_id: WorkspaceId, fullscreen: bool, bounds: Option<(f32, f32, f32, f32)>) -> Result<()> { + pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> { UPDATE workspaces - SET fullscreen = ?2, + SET window_state = ?2, window_x = ?3, window_y = ?4, window_width = ?5, - window_height = ?6 + window_height = ?6, + display = ?7 WHERE workspace_id = ?1 } } @@ -495,20 +497,20 @@ impl WorkspaceDb { #[cfg(test)] mod tests { - + use std::sync::Arc; - + use db::open_test_db; use settings::DockAnchor; - + use super::*; - + #[gpui::test] async fn test_next_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_next_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -524,7 +526,7 @@ mod tests { .unwrap(); }) .await; - + let id = db.next_id().await.unwrap(); // Assert the empty row got inserted assert_eq!( @@ -535,28 +537,28 @@ mod tests { .unwrap()(id) .unwrap() ); - + db.write(move |conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", id)) - .unwrap() + .unwrap() }) .await; - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_workspace_id_stability() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_workspace_id_stability").await); - + db.write(|conn| { conn.migrate( "test_table", @@ -572,7 +574,7 @@ mod tests { }) .await .unwrap(); - + let mut workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -580,10 +582,10 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -591,60 +593,60 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-1", 1)) - .unwrap(); + .unwrap(); }) .await; - + db.save_workspace(workspace_2.clone()).await; - + db.write(|conn| { conn.exec_bound(sql!(INSERT INTO test_table(text, workspace_id) VALUES (?, ?))) .unwrap()(("test-text-2", 2)) - .unwrap(); + .unwrap(); }) .await; - + workspace_1.location = (["/tmp", "/tmp3"]).into(); db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_1).await; - + workspace_2.dock_pane.children.push(SerializedItem { kind: Arc::from("Test"), item_id: 10, active: true, }); db.save_workspace(workspace_2).await; - + let test_text_2 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(2) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_2, "test-text-2"); - + let test_text_1 = db .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?)) .unwrap()(1) - .unwrap() - .unwrap(); + .unwrap() + .unwrap(); assert_eq!(test_text_1, "test-text-1"); } - + #[gpui::test] async fn test_full_workspace_serialization() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await); - + let dock_pane = crate::persistence::model::SerializedPane { children: vec![ SerializedItem::new("Terminal", 1, false), @@ -654,7 +656,7 @@ mod tests { ], active: false, }; - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -691,7 +693,7 @@ mod tests { )), ], }; - + let workspace = SerializedWorkspace { id: 5, location: (["/tmp", "/tmp2"]).into(), @@ -699,29 +701,29 @@ mod tests { center_group, dock_pane, left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace.clone()).await; let round_trip_workspace = db.workspace_for_roots(&["/tmp2", "/tmp"]); - + assert_eq!(workspace, round_trip_workspace.unwrap()); - + // Test guaranteed duplicate IDs db.save_workspace(workspace.clone()).await; db.save_workspace(workspace.clone()).await; - + let round_trip_workspace = db.workspace_for_roots(&["/tmp", "/tmp2"]); assert_eq!(workspace, round_trip_workspace.unwrap()); } - + #[gpui::test] async fn test_workspace_assignment() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_basic_functionality").await); - + let workspace_1 = SerializedWorkspace { id: 1, location: (["/tmp", "/tmp2"]).into(), @@ -729,10 +731,10 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + let mut workspace_2 = SerializedWorkspace { id: 2, location: (["/tmp"]).into(), @@ -740,13 +742,13 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_1.clone()).await; db.save_workspace(workspace_2.clone()).await; - + // Test that paths are treated as a set assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), @@ -756,20 +758,20 @@ mod tests { db.workspace_for_roots(&["/tmp2", "/tmp"]).unwrap(), workspace_1 ); - + // Make sure that other keys work assert_eq!(db.workspace_for_roots(&["/tmp"]).unwrap(), workspace_2); assert_eq!(db.workspace_for_roots(&["/tmp3", "/tmp2", "/tmp4"]), None); - + // Test 'mutate' case of updating a pre-existing id workspace_2.location = (["/tmp", "/tmp2"]).into(); - + db.save_workspace(workspace_2.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_2 ); - + // Test other mechanism for mutating let mut workspace_3 = SerializedWorkspace { id: 3, @@ -778,16 +780,16 @@ mod tests { center_group: Default::default(), dock_pane: Default::default(), left_sidebar_open: false, - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; - + db.save_workspace(workspace_3.clone()).await; assert_eq!( db.workspace_for_roots(&["/tmp", "/tmp2"]).unwrap(), workspace_3 ); - + // Make sure that updating paths differently also works workspace_3.location = (["/tmp3", "/tmp4", "/tmp2"]).into(); db.save_workspace(workspace_3.clone()).await; @@ -798,11 +800,11 @@ mod tests { workspace_3 ); } - + use crate::dock::DockPosition; use crate::persistence::model::SerializedWorkspace; use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup}; - + fn default_workspace>( workspace_id: &[P], dock_pane: SerializedPane, @@ -815,17 +817,17 @@ mod tests { center_group: center_group.clone(), dock_pane, left_sidebar_open: true, - fullscreen: false, bounds: Default::default(), + display: Default::default(), } } - + #[gpui::test] async fn test_basic_dock_pane() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("basic_dock_pane").await); - + let dock_pane = crate::persistence::model::SerializedPane::new( vec![ SerializedItem::new("Terminal", 1, false), @@ -835,22 +837,22 @@ mod tests { ], false, ); - + let workspace = default_workspace(&["/tmp"], dock_pane, &Default::default()); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.dock_pane, new_workspace.dock_pane); } - + #[gpui::test] async fn test_simple_split() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("simple_split").await); - + // ----------------- // | 1,2 | 5,6 | // | - - - | | @@ -887,22 +889,22 @@ mod tests { )), ], }; - + let workspace = default_workspace(&["/tmp"], Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(&["/tmp"]).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } - + #[gpui::test] async fn test_cleanup_panes() { env_logger::try_init().ok(); - + let db = WorkspaceDb(open_test_db("test_cleanup_panes").await); - + let center_pane = SerializedPaneGroup::Group { axis: gpui::Axis::Horizontal, children: vec![ @@ -934,13 +936,13 @@ mod tests { )), ], }; - + let id = &["/tmp"]; - + let mut workspace = default_workspace(id, Default::default(), ¢er_pane); - + db.save_workspace(workspace.clone()).await; - + workspace.center_group = SerializedPaneGroup::Group { axis: gpui::Axis::Vertical, children: vec![ @@ -960,11 +962,11 @@ mod tests { )), ], }; - + db.save_workspace(workspace.clone()).await; - + let new_workspace = db.workspace_for_roots(id).unwrap(); - + assert_eq!(workspace.center_group, new_workspace.center_group); } } diff --git a/crates/workspace/src/persistence/model.rs b/crates/workspace/src/persistence/model.rs index f6236ced79f6e35e89baf3b384af01436394aebf..507582b2160d4585c3dac1eb199c234c14ea8de4 100644 --- a/crates/workspace/src/persistence/model.rs +++ b/crates/workspace/src/persistence/model.rs @@ -6,9 +6,7 @@ use std::{ use anyhow::{Context, Result}; use async_recursion::async_recursion; -use gpui::{ - geometry::rect::RectF, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds, -}; +use gpui::{AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WindowBounds}; use db::sqlez::{ bindable::{Bind, Column, StaticColumnCount}, @@ -17,6 +15,7 @@ use db::sqlez::{ use project::Project; use settings::DockAnchor; use util::ResultExt; +use uuid::Uuid; use crate::{ dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId, @@ -69,20 +68,8 @@ pub struct SerializedWorkspace { pub center_group: SerializedPaneGroup, pub dock_pane: SerializedPane, pub left_sidebar_open: bool, - pub fullscreen: bool, - pub bounds: Option, -} - -impl SerializedWorkspace { - pub fn bounds(&self) -> WindowBounds { - if self.fullscreen { - WindowBounds::Fullscreen - } else if let Some(bounds) = self.bounds { - WindowBounds::Fixed(bounds) - } else { - WindowBounds::Maximized - } - } + pub bounds: Option, + pub display: Option, } #[derive(Debug, PartialEq, Eq, Clone)] diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 4c742cd7faf14f010e340f3e8aa0d8cacb99a03d..4602e498f1e3d3f7bc50f34de48b7981e1ed29a3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -37,8 +37,8 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, WindowOptions}, AnyModelHandle, AnyViewHandle, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, - MouseButton, MutableAppContext, PathPromptOptions, PromptLevel, RenderContext, SizeConstraint, - Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, + MouseButton, MutableAppContext, PathPromptOptions, Platform, PromptLevel, RenderContext, + SizeConstraint, Task, View, ViewContext, ViewHandle, WeakViewHandle, WindowBounds, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem}; use language::LanguageRegistry; @@ -339,7 +339,8 @@ pub struct AppState { pub client: Arc, pub user_store: ModelHandle, pub fs: Arc, - pub build_window_options: fn(Option) -> WindowOptions<'static>, + pub build_window_options: + fn(Option, Option, &dyn Platform) -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), pub dock_default_item_factory: DockDefaultItemFactory, } @@ -366,7 +367,7 @@ impl AppState { languages, user_store, initialize_workspace: |_, _, _| {}, - build_window_options: |_| Default::default(), + build_window_options: |_, _, _| Default::default(), dock_default_item_factory: |_, _| unimplemented!(), }) } @@ -681,11 +682,14 @@ impl Workspace { DB.next_id().await.unwrap_or(0) }; + let (bounds, display) = dbg!(serialized_workspace + .as_ref() + .and_then(|sw| sw.bounds.zip(sw.display)) + .unzip()); + // Use the serialized workspace to construct the new window let (_, workspace) = cx.add_window( - (app_state.build_window_options)(dbg!(serialized_workspace - .as_ref() - .map(|sw| sw.bounds()))), + (app_state.build_window_options)(bounds, display, cx.platform().as_ref()), |cx| { let mut workspace = Workspace::new( serialized_workspace, @@ -695,20 +699,9 @@ impl Workspace { cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); - cx.observe_window_bounds(move |_, bounds, cx| { - let fullscreen = cx.window_is_fullscreen(cx.window_id()); - let bounds = if let WindowBounds::Fixed(region) = bounds { - Some(( - region.min_x(), - region.min_y(), - region.width(), - region.height(), - )) - } else { - None - }; + cx.observe_window_bounds(move |_, bounds, display, cx| { cx.background() - .spawn(DB.set_bounds(workspace_id, fullscreen, bounds)) + .spawn(DB.set_window_bounds(workspace_id, dbg!(bounds), dbg!(display))) .detach_and_log_err(cx); }) .detach(); @@ -2349,8 +2342,8 @@ impl Workspace { dock_pane, center_group, left_sidebar_open: self.left_sidebar.read(cx).is_open(), - fullscreen: false, bounds: Default::default(), + display: Default::default(), }; cx.background() diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index c2b15c7cb5beb9cb5540ad5b4829bf9aaec9de5d..40861a8f262792f639cb2e9506608dfcd2a42d6f 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -111,6 +111,7 @@ tree-sitter-scheme = { git = "https://github.com/6cdh/tree-sitter-scheme", rev = tree-sitter-racket = { git = "https://github.com/zed-industries/tree-sitter-racket", rev = "eb010cf2c674c6fd9a6316a84e28ef90190fe51a"} url = "2.2" urlencoding = "2.1.2" +uuid = { version = "1.1.2", features = ["v4"] } [dev-dependencies] call = { path = "../call", features = ["test-support"] } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 3529e1c6dc61257ce2ef9390ed805e98bbe6b4c0..879cc441c8d4279861348ac50eaf6a1809428cec 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -20,7 +20,8 @@ use gpui::{ }, impl_actions, platform::{WindowBounds, WindowOptions}, - AssetSource, AsyncAppContext, PromptLevel, TitlebarOptions, ViewContext, WindowKind, + AssetSource, AsyncAppContext, ClipboardItem, Platform, PromptLevel, TitlebarOptions, + ViewContext, WindowKind, }; use language::Rope; use lazy_static::lazy_static; @@ -33,6 +34,7 @@ use serde_json::to_string_pretty; use settings::{keymap_file_json_schema, settings_file_json_schema, Settings}; use std::{borrow::Cow, env, path::Path, str, sync::Arc}; use util::{channel::ReleaseChannel, paths, ResultExt}; +use uuid::Uuid; pub use workspace; use workspace::{sidebar::SidebarSide, AppState, Workspace}; @@ -369,7 +371,11 @@ pub fn initialize_workspace( }); } -pub fn build_window_options(bounds: Option) -> WindowOptions<'static> { +pub fn build_window_options( + bounds: Option, + display: Option, + platform: &dyn Platform, +) -> WindowOptions<'static> { let bounds = bounds .or_else(|| { ZED_WINDOW_POSITION @@ -378,8 +384,9 @@ pub fn build_window_options(bounds: Option) -> WindowOptions<'stat }) .unwrap_or(WindowBounds::Maximized); + let screen = display.and_then(|display| platform.screen_by_id(display)); + WindowOptions { - bounds, titlebar: Some(TitlebarOptions { title: None, appears_transparent: true, @@ -389,7 +396,8 @@ pub fn build_window_options(bounds: Option) -> WindowOptions<'stat focus: true, kind: WindowKind::Normal, is_movable: true, - screen: None, + bounds, + screen, } }