Detailed changes
@@ -10796,6 +10796,44 @@ dependencies = [
"uuid 1.4.1",
]
+[[package]]
+name = "workspace2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-recursion 1.0.5",
+ "bincode",
+ "call2",
+ "client2",
+ "collections",
+ "db2",
+ "env_logger 0.9.3",
+ "fs2",
+ "futures 0.3.28",
+ "gpui2",
+ "indoc",
+ "install_cli2",
+ "itertools 0.10.5",
+ "language2",
+ "lazy_static",
+ "log",
+ "node_runtime",
+ "parking_lot 0.11.2",
+ "postage",
+ "project2",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "settings2",
+ "smallvec",
+ "terminal2",
+ "theme2",
+ "ui2",
+ "util",
+ "uuid 1.4.1",
+]
+
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
@@ -11111,6 +11149,7 @@ dependencies = [
"urlencoding",
"util",
"uuid 1.4.1",
+ "workspace2",
]
[[package]]
@@ -99,7 +99,7 @@ members = [
"crates/semantic_index",
"crates/vim",
"crates/vcs_menu",
- "crates/workspace",
+ "crates/workspace2",
"crates/welcome",
"crates/xtask",
"crates/zed",
@@ -822,10 +822,13 @@ impl Context for AppContext {
let root_view = window.root_view.clone().unwrap();
let result = update(root_view, &mut WindowContext::new(cx, &mut window));
- cx.windows
- .get_mut(handle.id)
- .ok_or_else(|| anyhow!("window not found"))?
- .replace(window);
+
+ if !window.removed {
+ cx.windows
+ .get_mut(handle.id)
+ .ok_or_else(|| anyhow!("window not found"))?
+ .replace(window);
+ }
Ok(result)
})
@@ -678,10 +678,15 @@ impl MacWindow {
impl Drop for MacWindow {
fn drop(&mut self) {
- let native_window = self.0.lock().native_window;
- unsafe {
- native_window.close();
- }
+ let this = self.0.lock();
+ let window = this.native_window;
+ this.executor
+ .spawn(async move {
+ unsafe {
+ window.close();
+ }
+ })
+ .detach();
}
}
@@ -868,17 +873,25 @@ impl PlatformWindow for MacWindow {
fn zoom(&self) {
let this = self.0.lock();
let window = this.native_window;
- unsafe {
- window.zoom_(nil);
- }
+ this.executor
+ .spawn(async move {
+ unsafe {
+ window.zoom_(nil);
+ }
+ })
+ .detach();
}
fn toggle_full_screen(&self) {
let this = self.0.lock();
let window = this.native_window;
- unsafe {
- window.toggleFullScreen_(nil);
- }
+ this.executor
+ .spawn(async move {
+ unsafe {
+ window.toggleFullScreen_(nil);
+ }
+ })
+ .detach();
}
fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
@@ -4,11 +4,11 @@ use crate::{
Entity, EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId,
Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent,
- MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite,
- PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels,
- SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet, Subscription,
- TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakView,
- WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
+ MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point,
+ PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
+ RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
+ Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
+ WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Result};
use collections::HashMap;
@@ -28,6 +28,7 @@ use std::{
hash::{Hash, Hasher},
marker::PhantomData,
mem,
+ rc::Rc,
sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
@@ -164,6 +165,7 @@ impl Drop for FocusHandle {
// Holds the state for a specific window.
pub struct Window {
pub(crate) handle: AnyWindowHandle,
+ pub(crate) removed: bool,
platform_window: Box<dyn PlatformWindow>,
display_id: DisplayId,
sprite_atlas: Arc<dyn PlatformAtlas>,
@@ -256,6 +258,7 @@ impl Window {
Window {
handle,
+ removed: false,
platform_window,
display_id,
sprite_atlas,
@@ -351,6 +354,11 @@ impl<'a> WindowContext<'a> {
self.window.dirty = true;
}
+ /// Close this window.
+ pub fn remove_window(&mut self) {
+ self.window.removed = true;
+ }
+
/// Obtain a new `FocusHandle`, which allows you to track and manipulate the keyboard focus
/// for elements rendered within this window.
pub fn focus_handle(&mut self) -> FocusHandle {
@@ -579,6 +587,21 @@ impl<'a> WindowContext<'a> {
self.window.bounds
}
+ pub fn is_window_active(&self) -> bool {
+ self.window.active
+ }
+
+ pub fn zoom_window(&self) {
+ self.window.platform_window.zoom();
+ }
+
+ pub fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
+ self.platform
+ .displays()
+ .into_iter()
+ .find(|display| display.id() == self.window.display_id)
+ }
+
/// The scale factor of the display associated with the window. For example, it could
/// return 2.0 for a "retina" display, indicating that each logical pixel should actually
/// be rendered as two pixels on screen.
@@ -82,8 +82,8 @@ fn main() {
..Default::default()
},
move |cx| {
- let theme_settings = ThemeSettings::get_global(cx);
- cx.set_rem_size(theme_settings.ui_font_size);
+ let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
+ cx.set_rem_size(ui_font_size);
cx.build_view(|cx| StoryWrapper::new(selector.story(cx)))
},
@@ -0,0 +1,66 @@
+[package]
+name = "workspace2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/workspace2.rs"
+doctest = false
+
+[features]
+test-support = [
+ "call2/test-support",
+ "client2/test-support",
+ "project2/test-support",
+ "settings2/test-support",
+ "gpui2/test-support",
+ "fs2/test-support"
+]
+
+[dependencies]
+db2 = { path = "../db2" }
+call2 = { path = "../call2" }
+client2 = { path = "../client2" }
+collections = { path = "../collections" }
+# context_menu = { path = "../context_menu" }
+fs2 = { path = "../fs2" }
+gpui2 = { path = "../gpui2" }
+install_cli2 = { path = "../install_cli2" }
+language2 = { path = "../language2" }
+#menu = { path = "../menu" }
+node_runtime = { path = "../node_runtime" }
+project2 = { path = "../project2" }
+settings2 = { path = "../settings2" }
+terminal2 = { path = "../terminal2" }
+theme2 = { path = "../theme2" }
+util = { path = "../util" }
+ui = { package = "ui2", path = "../ui2" }
+
+async-recursion = "1.0.0"
+itertools = "0.10"
+bincode = "1.2.1"
+anyhow.workspace = true
+futures.workspace = true
+lazy_static.workspace = true
+log.workspace = true
+parking_lot.workspace = true
+postage.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smallvec.workspace = true
+uuid.workspace = true
+
+[dev-dependencies]
+call2 = { path = "../call2", features = ["test-support"] }
+client2 = { path = "../client2", features = ["test-support"] }
+gpui2 = { path = "../gpui2", features = ["test-support"] }
+project2 = { path = "../project2", features = ["test-support"] }
+settings2 = { path = "../settings2", features = ["test-support"] }
+fs2 = { path = "../fs2", features = ["test-support"] }
+db2 = { path = "../db2", features = ["test-support"] }
+
+indoc.workspace = true
+env_logger.workspace = true
@@ -0,0 +1,750 @@
+use crate::{status_bar::StatusItemView, Axis, Workspace};
+use gpui2::{
+ div, Action, AnyView, AppContext, Div, Entity, EntityId, EventEmitter, ParentElement, Render,
+ Subscription, View, ViewContext, WeakView, WindowContext,
+};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::sync::Arc;
+
+pub trait Panel: Render + EventEmitter {
+ fn persistent_name(&self) -> &'static str;
+ fn position(&self, cx: &WindowContext) -> DockPosition;
+ fn position_is_valid(&self, position: DockPosition) -> bool;
+ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
+ fn size(&self, cx: &WindowContext) -> f32;
+ fn set_size(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>);
+ fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>;
+ fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>);
+ fn icon_label(&self, _: &WindowContext) -> Option<String> {
+ None
+ }
+ fn should_change_position_on_event(_: &Self::Event) -> bool;
+ fn should_zoom_in_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn should_zoom_out_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn is_zoomed(&self, _cx: &WindowContext) -> bool {
+ false
+ }
+ fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {}
+ fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {}
+ fn should_activate_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn should_close_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn has_focus(&self, cx: &WindowContext) -> bool;
+ fn is_focus_event(_: &Self::Event) -> bool;
+}
+
+pub trait PanelHandle: Send + Sync {
+ fn id(&self) -> EntityId;
+ fn persistent_name(&self, cx: &WindowContext) -> &'static str;
+ fn position(&self, cx: &WindowContext) -> DockPosition;
+ fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
+ fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
+ fn is_zoomed(&self, cx: &WindowContext) -> bool;
+ fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
+ fn set_active(&self, active: bool, cx: &mut WindowContext);
+ fn size(&self, cx: &WindowContext) -> f32;
+ fn set_size(&self, size: Option<f32>, cx: &mut WindowContext);
+ fn icon_path(&self, cx: &WindowContext) -> Option<&'static str>;
+ fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option<Box<dyn Action>>);
+ fn icon_label(&self, cx: &WindowContext) -> Option<String>;
+ fn has_focus(&self, cx: &WindowContext) -> bool;
+ fn to_any(&self) -> AnyView;
+}
+
+impl<T> PanelHandle for View<T>
+where
+ T: Panel,
+{
+ fn id(&self) -> EntityId {
+ self.entity_id()
+ }
+
+ fn persistent_name(&self, cx: &WindowContext) -> &'static str {
+ self.read(cx).persistent_name()
+ }
+
+ fn position(&self, cx: &WindowContext) -> DockPosition {
+ self.read(cx).position(cx)
+ }
+
+ fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool {
+ self.read(cx).position_is_valid(position)
+ }
+
+ fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| this.set_position(position, cx))
+ }
+
+ fn is_zoomed(&self, cx: &WindowContext) -> bool {
+ self.read(cx).is_zoomed(cx)
+ }
+
+ fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| this.set_zoomed(zoomed, cx))
+ }
+
+ fn set_active(&self, active: bool, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| this.set_active(active, cx))
+ }
+
+ fn size(&self, cx: &WindowContext) -> f32 {
+ self.read(cx).size(cx)
+ }
+
+ fn set_size(&self, size: Option<f32>, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| this.set_size(size, cx))
+ }
+
+ fn icon_path(&self, cx: &WindowContext) -> Option<&'static str> {
+ self.read(cx).icon_path(cx)
+ }
+
+ fn icon_tooltip(&self, cx: &WindowContext) -> (String, Option<Box<dyn Action>>) {
+ self.read(cx).icon_tooltip()
+ }
+
+ fn icon_label(&self, cx: &WindowContext) -> Option<String> {
+ self.read(cx).icon_label(cx)
+ }
+
+ fn has_focus(&self, cx: &WindowContext) -> bool {
+ self.read(cx).has_focus(cx)
+ }
+
+ fn to_any(&self) -> AnyView {
+ self.clone().into()
+ }
+}
+
+impl From<&dyn PanelHandle> for AnyView {
+ fn from(val: &dyn PanelHandle) -> Self {
+ val.to_any()
+ }
+}
+
+pub struct Dock {
+ position: DockPosition,
+ panel_entries: Vec<PanelEntry>,
+ is_open: bool,
+ active_panel_index: usize,
+}
+
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
+#[serde(rename_all = "lowercase")]
+pub enum DockPosition {
+ Left,
+ Bottom,
+ Right,
+}
+
+impl DockPosition {
+ fn to_label(&self) -> &'static str {
+ match self {
+ Self::Left => "left",
+ Self::Bottom => "bottom",
+ Self::Right => "right",
+ }
+ }
+
+ // todo!()
+ // fn to_resize_handle_side(self) -> HandleSide {
+ // match self {
+ // Self::Left => HandleSide::Right,
+ // Self::Bottom => HandleSide::Top,
+ // Self::Right => HandleSide::Left,
+ // }
+ // }
+
+ pub fn axis(&self) -> Axis {
+ match self {
+ Self::Left | Self::Right => Axis::Horizontal,
+ Self::Bottom => Axis::Vertical,
+ }
+ }
+}
+
+struct PanelEntry {
+ panel: Arc<dyn PanelHandle>,
+ // todo!()
+ // context_menu: View<ContextMenu>,
+ _subscriptions: [Subscription; 2],
+}
+
+pub struct PanelButtons {
+ dock: View<Dock>,
+ workspace: WeakView<Workspace>,
+}
+
+impl Dock {
+ pub fn new(position: DockPosition) -> Self {
+ Self {
+ position,
+ panel_entries: Default::default(),
+ active_panel_index: 0,
+ is_open: false,
+ }
+ }
+
+ pub fn position(&self) -> DockPosition {
+ self.position
+ }
+
+ pub fn is_open(&self) -> bool {
+ self.is_open
+ }
+
+ // pub fn has_focus(&self, cx: &WindowContext) -> bool {
+ // self.visible_panel()
+ // .map_or(false, |panel| panel.has_focus(cx))
+ // }
+
+ // pub fn panel<T: Panel>(&self) -> Option<View<T>> {
+ // self.panel_entries
+ // .iter()
+ // .find_map(|entry| entry.panel.as_any().clone().downcast())
+ // }
+
+ // pub fn panel_index_for_type<T: Panel>(&self) -> Option<usize> {
+ // self.panel_entries
+ // .iter()
+ // .position(|entry| entry.panel.as_any().is::<T>())
+ // }
+
+ pub fn panel_index_for_ui_name(&self, _ui_name: &str, _cx: &AppContext) -> Option<usize> {
+ todo!()
+ // self.panel_entries.iter().position(|entry| {
+ // let panel = entry.panel.as_any();
+ // cx.view_ui_name(panel.window(), panel.id()) == Some(ui_name)
+ // })
+ }
+
+ // pub fn active_panel_index(&self) -> usize {
+ // self.active_panel_index
+ // }
+
+ pub(crate) fn set_open(&mut self, open: bool, cx: &mut ViewContext<Self>) {
+ if open != self.is_open {
+ self.is_open = open;
+ if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
+ active_panel.panel.set_active(open, cx);
+ }
+
+ cx.notify();
+ }
+ }
+
+ // pub fn set_panel_zoomed(&mut self, panel: &AnyView, zoomed: bool, cx: &mut ViewContext<Self>) {
+ // for entry in &mut self.panel_entries {
+ // if entry.panel.as_any() == panel {
+ // if zoomed != entry.panel.is_zoomed(cx) {
+ // entry.panel.set_zoomed(zoomed, cx);
+ // }
+ // } else if entry.panel.is_zoomed(cx) {
+ // entry.panel.set_zoomed(false, cx);
+ // }
+ // }
+
+ // cx.notify();
+ // }
+
+ // pub fn zoom_out(&mut self, cx: &mut ViewContext<Self>) {
+ // for entry in &mut self.panel_entries {
+ // if entry.panel.is_zoomed(cx) {
+ // entry.panel.set_zoomed(false, cx);
+ // }
+ // }
+ // }
+
+ // pub(crate) fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>) {
+ // let subscriptions = [
+ // cx.observe(&panel, |_, _, cx| cx.notify()),
+ // cx.subscribe(&panel, |this, panel, event, cx| {
+ // if T::should_activate_on_event(event) {
+ // if let Some(ix) = this
+ // .panel_entries
+ // .iter()
+ // .position(|entry| entry.panel.id() == panel.id())
+ // {
+ // this.set_open(true, cx);
+ // this.activate_panel(ix, cx);
+ // cx.focus(&panel);
+ // }
+ // } else if T::should_close_on_event(event)
+ // && this.visible_panel().map_or(false, |p| p.id() == panel.id())
+ // {
+ // this.set_open(false, cx);
+ // }
+ // }),
+ // ];
+
+ // let dock_view_id = cx.view_id();
+ // self.panel_entries.push(PanelEntry {
+ // panel: Arc::new(panel),
+ // // todo!()
+ // // context_menu: cx.add_view(|cx| {
+ // // let mut menu = ContextMenu::new(dock_view_id, cx);
+ // // menu.set_position_mode(OverlayPositionMode::Local);
+ // // menu
+ // // }),
+ // _subscriptions: subscriptions,
+ // });
+ // cx.notify()
+ // }
+
+ // pub fn remove_panel<T: Panel>(&mut self, panel: &View<T>, cx: &mut ViewContext<Self>) {
+ // if let Some(panel_ix) = self
+ // .panel_entries
+ // .iter()
+ // .position(|entry| entry.panel.id() == panel.id())
+ // {
+ // if panel_ix == self.active_panel_index {
+ // self.active_panel_index = 0;
+ // self.set_open(false, cx);
+ // } else if panel_ix < self.active_panel_index {
+ // self.active_panel_index -= 1;
+ // }
+ // self.panel_entries.remove(panel_ix);
+ // cx.notify();
+ // }
+ // }
+
+ // pub fn panels_len(&self) -> usize {
+ // self.panel_entries.len()
+ // }
+
+ pub fn activate_panel(&mut self, panel_ix: usize, cx: &mut ViewContext<Self>) {
+ if panel_ix != self.active_panel_index {
+ if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
+ active_panel.panel.set_active(false, cx);
+ }
+
+ self.active_panel_index = panel_ix;
+ if let Some(active_panel) = self.panel_entries.get(self.active_panel_index) {
+ active_panel.panel.set_active(true, cx);
+ }
+
+ cx.notify();
+ }
+ }
+
+ pub fn visible_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
+ let entry = self.visible_entry()?;
+ Some(&entry.panel)
+ }
+
+ pub fn active_panel(&self) -> Option<&Arc<dyn PanelHandle>> {
+ Some(&self.panel_entries.get(self.active_panel_index)?.panel)
+ }
+
+ fn visible_entry(&self) -> Option<&PanelEntry> {
+ if self.is_open {
+ self.panel_entries.get(self.active_panel_index)
+ } else {
+ None
+ }
+ }
+
+ // pub fn zoomed_panel(&self, cx: &WindowContext) -> Option<Arc<dyn PanelHandle>> {
+ // let entry = self.visible_entry()?;
+ // if entry.panel.is_zoomed(cx) {
+ // Some(entry.panel.clone())
+ // } else {
+ // None
+ // }
+ // }
+
+ // pub fn panel_size(&self, panel: &dyn PanelHandle, cx: &WindowContext) -> Option<f32> {
+ // self.panel_entries
+ // .iter()
+ // .find(|entry| entry.panel.id() == panel.id())
+ // .map(|entry| entry.panel.size(cx))
+ // }
+
+ // pub fn active_panel_size(&self, cx: &WindowContext) -> Option<f32> {
+ // if self.is_open {
+ // self.panel_entries
+ // .get(self.active_panel_index)
+ // .map(|entry| entry.panel.size(cx))
+ // } else {
+ // None
+ // }
+ // }
+
+ // pub fn resize_active_panel(&mut self, size: Option<f32>, cx: &mut ViewContext<Self>) {
+ // if let Some(entry) = self.panel_entries.get_mut(self.active_panel_index) {
+ // entry.panel.set_size(size, cx);
+ // cx.notify();
+ // }
+ // }
+
+ // pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement<Workspace> {
+ // todo!()
+ // if let Some(active_entry) = self.visible_entry() {
+ // Empty::new()
+ // .into_any()
+ // .contained()
+ // .with_style(self.style(cx))
+ // .resizable::<WorkspaceBounds>(
+ // self.position.to_resize_handle_side(),
+ // active_entry.panel.size(cx),
+ // |_, _, _| {},
+ // )
+ // .into_any()
+ // } else {
+ // Empty::new().into_any()
+ // }
+ // }
+}
+
+// todo!()
+// impl View for Dock {
+// fn ui_name() -> &'static str {
+// "Dock"
+// }
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// if let Some(active_entry) = self.visible_entry() {
+// let style = self.style(cx);
+// ChildView::new(active_entry.panel.as_any(), cx)
+// .contained()
+// .with_style(style)
+// .resizable::<WorkspaceBounds>(
+// self.position.to_resize_handle_side(),
+// active_entry.panel.size(cx),
+// |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx),
+// )
+// .into_any()
+// } else {
+// Empty::new().into_any()
+// }
+// }
+
+// fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
+// if cx.is_self_focused() {
+// if let Some(active_entry) = self.visible_entry() {
+// cx.focus(active_entry.panel.as_any());
+// } else {
+// cx.focus_parent();
+// }
+// }
+// }
+// }
+
+impl PanelButtons {
+ pub fn new(
+ dock: View<Dock>,
+ workspace: WeakView<Workspace>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ cx.observe(&dock, |_, _, cx| cx.notify()).detach();
+ Self { dock, workspace }
+ }
+}
+
+impl EventEmitter for PanelButtons {
+ type Event = ();
+}
+
+// impl Render for PanelButtons {
+// type Element = ();
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+// todo!("")
+// }
+
+// fn ui_name() -> &'static str {
+// "PanelButtons"
+// }
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// let theme = &settings::get::<ThemeSettings>(cx).theme;
+// let tooltip_style = theme.tooltip.clone();
+// let theme = &theme.workspace.status_bar.panel_buttons;
+// let button_style = theme.button.clone();
+// let dock = self.dock.read(cx);
+// let active_ix = dock.active_panel_index;
+// let is_open = dock.is_open;
+// let dock_position = dock.position;
+// let group_style = match dock_position {
+// DockPosition::Left => theme.group_left,
+// DockPosition::Bottom => theme.group_bottom,
+// DockPosition::Right => theme.group_right,
+// };
+// let menu_corner = match dock_position {
+// DockPosition::Left => AnchorCorner::BottomLeft,
+// DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight,
+// };
+
+// let panels = dock
+// .panel_entries
+// .iter()
+// .map(|item| (item.panel.clone(), item.context_menu.clone()))
+// .collect::<Vec<_>>();
+// Flex::row()
+// .with_children(panels.into_iter().enumerate().filter_map(
+// |(panel_ix, (view, context_menu))| {
+// let icon_path = view.icon_path(cx)?;
+// let is_active = is_open && panel_ix == active_ix;
+// let (tooltip, tooltip_action) = if is_active {
+// (
+// format!("Close {} dock", dock_position.to_label()),
+// Some(match dock_position {
+// DockPosition::Left => crate::ToggleLeftDock.boxed_clone(),
+// DockPosition::Bottom => crate::ToggleBottomDock.boxed_clone(),
+// DockPosition::Right => crate::ToggleRightDock.boxed_clone(),
+// }),
+// )
+// } else {
+// view.icon_tooltip(cx)
+// };
+// Some(
+// Stack::new()
+// .with_child(
+// MouseEventHandler::new::<Self, _>(panel_ix, cx, |state, cx| {
+// let style = button_style.in_state(is_active);
+
+// let style = style.style_for(state);
+// Flex::row()
+// .with_child(
+// Svg::new(icon_path)
+// .with_color(style.icon_color)
+// .constrained()
+// .with_width(style.icon_size)
+// .aligned(),
+// )
+// .with_children(if let Some(label) = view.icon_label(cx) {
+// Some(
+// Label::new(label, style.label.text.clone())
+// .contained()
+// .with_style(style.label.container)
+// .aligned(),
+// )
+// } else {
+// None
+// })
+// .constrained()
+// .with_height(style.icon_size)
+// .contained()
+// .with_style(style.container)
+// })
+// .with_cursor_style(CursorStyle::PointingHand)
+// .on_click(MouseButton::Left, {
+// let tooltip_action =
+// tooltip_action.as_ref().map(|action| action.boxed_clone());
+// move |_, this, cx| {
+// if let Some(tooltip_action) = &tooltip_action {
+// let window = cx.window();
+// let view_id = this.workspace.id();
+// let tooltip_action = tooltip_action.boxed_clone();
+// cx.spawn(|_, mut cx| async move {
+// window.dispatch_action(
+// view_id,
+// &*tooltip_action,
+// &mut cx,
+// );
+// })
+// .detach();
+// }
+// }
+// })
+// .on_click(MouseButton::Right, {
+// let view = view.clone();
+// let menu = context_menu.clone();
+// move |_, _, cx| {
+// const POSITIONS: [DockPosition; 3] = [
+// DockPosition::Left,
+// DockPosition::Right,
+// DockPosition::Bottom,
+// ];
+
+// menu.update(cx, |menu, cx| {
+// let items = POSITIONS
+// .into_iter()
+// .filter(|position| {
+// *position != dock_position
+// && view.position_is_valid(*position, cx)
+// })
+// .map(|position| {
+// let view = view.clone();
+// ContextMenuItem::handler(
+// format!("Dock {}", position.to_label()),
+// move |cx| view.set_position(position, cx),
+// )
+// })
+// .collect();
+// menu.show(Default::default(), menu_corner, items, cx);
+// })
+// }
+// })
+// .with_tooltip::<Self>(
+// panel_ix,
+// tooltip,
+// tooltip_action,
+// tooltip_style.clone(),
+// cx,
+// ),
+// )
+// .with_child(ChildView::new(&context_menu, cx)),
+// )
+// },
+// ))
+// .contained()
+// .with_style(group_style)
+// .into_any()
+// }
+// }
+
+impl Render for PanelButtons {
+ type Element = Div<Self>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ // todo!()
+ let dock = self.dock.read(cx);
+ div().children(
+ dock.panel_entries
+ .iter()
+ .map(|panel| panel.panel.persistent_name(cx)),
+ )
+ }
+}
+
+impl StatusItemView for PanelButtons {
+ fn set_active_pane_item(
+ &mut self,
+ _active_pane_item: Option<&dyn crate::ItemHandle>,
+ _cx: &mut ViewContext<Self>,
+ ) {
+ todo!()
+ }
+}
+
+#[cfg(any(test, feature = "test-support"))]
+pub mod test {
+ use super::*;
+ use gpui2::{div, Div, ViewContext, WindowContext};
+
+ #[derive(Debug)]
+ pub enum TestPanelEvent {
+ PositionChanged,
+ Activated,
+ Closed,
+ ZoomIn,
+ ZoomOut,
+ Focus,
+ }
+
+ pub struct TestPanel {
+ pub position: DockPosition,
+ pub zoomed: bool,
+ pub active: bool,
+ pub has_focus: bool,
+ pub size: f32,
+ }
+
+ impl EventEmitter for TestPanel {
+ type Event = TestPanelEvent;
+ }
+
+ impl TestPanel {
+ pub fn new(position: DockPosition) -> Self {
+ Self {
+ position,
+ zoomed: false,
+ active: false,
+ has_focus: false,
+ size: 300.,
+ }
+ }
+ }
+
+ impl Render for TestPanel {
+ type Element = Div<Self>;
+
+ fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ div()
+ }
+ }
+
+ impl Panel for TestPanel {
+ fn persistent_name(&self) -> &'static str {
+ "TestPanel"
+ }
+
+ fn position(&self, _: &gpui2::WindowContext) -> super::DockPosition {
+ self.position
+ }
+
+ fn position_is_valid(&self, _: super::DockPosition) -> bool {
+ true
+ }
+
+ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
+ self.position = position;
+ cx.emit(TestPanelEvent::PositionChanged);
+ }
+
+ fn size(&self, _: &WindowContext) -> f32 {
+ self.size
+ }
+
+ fn set_size(&mut self, size: Option<f32>, _: &mut ViewContext<Self>) {
+ self.size = size.unwrap_or(300.);
+ }
+
+ fn icon_path(&self, _: &WindowContext) -> Option<&'static str> {
+ Some("icons/test_panel.svg")
+ }
+
+ fn icon_tooltip(&self) -> (String, Option<Box<dyn Action>>) {
+ ("Test Panel".into(), None)
+ }
+
+ fn should_change_position_on_event(event: &Self::Event) -> bool {
+ matches!(event, TestPanelEvent::PositionChanged)
+ }
+
+ fn should_zoom_in_on_event(event: &Self::Event) -> bool {
+ matches!(event, TestPanelEvent::ZoomIn)
+ }
+
+ fn should_zoom_out_on_event(event: &Self::Event) -> bool {
+ matches!(event, TestPanelEvent::ZoomOut)
+ }
+
+ fn is_zoomed(&self, _: &WindowContext) -> bool {
+ self.zoomed
+ }
+
+ fn set_zoomed(&mut self, zoomed: bool, _cx: &mut ViewContext<Self>) {
+ self.zoomed = zoomed;
+ }
+
+ fn set_active(&mut self, active: bool, _cx: &mut ViewContext<Self>) {
+ self.active = active;
+ }
+
+ fn should_activate_on_event(event: &Self::Event) -> bool {
+ matches!(event, TestPanelEvent::Activated)
+ }
+
+ fn should_close_on_event(event: &Self::Event) -> bool {
+ matches!(event, TestPanelEvent::Closed)
+ }
+
+ fn has_focus(&self, _cx: &WindowContext) -> bool {
+ self.has_focus
+ }
+
+ fn is_focus_event(event: &Self::Event) -> bool {
+ matches!(event, TestPanelEvent::Focus)
+ }
+ }
+}
@@ -1,88 +1,80 @@
-// use crate::{
-// pane, persistence::model::ItemId, searchable::SearchableItemHandle, FollowableItemBuilders,
-// ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, WorkspaceId,
-// };
-// use crate::{AutosaveSetting, DelayedDebouncedEditAction, WorkspaceSettings};
+use crate::{
+ pane::{self, Pane},
+ persistence::model::ItemId,
+ searchable::SearchableItemHandle,
+ workspace_settings::{AutosaveSetting, WorkspaceSettings},
+ DelayedDebouncedEditAction, FollowableItemBuilders, ItemNavHistory, ToolbarItemLocation,
+ ViewId, Workspace, WorkspaceId,
+};
use anyhow::Result;
use client2::{
- proto::{self, PeerId, ViewId},
+ proto::{self, PeerId},
Client,
};
+use gpui2::{
+ AnyElement, AnyView, AppContext, Entity, EntityId, EventEmitter, HighlightStyle, Model, Pixels,
+ Point, Render, SharedString, Task, View, ViewContext, WeakView, WindowContext,
+};
+use parking_lot::Mutex;
+use project2::{Project, ProjectEntryId, ProjectPath};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
use settings2::Settings;
-use theme2::Theme;
-// use client2::{
-// proto::{self, PeerId},
-// Client,
-// };
-// use gpui2::geometry::vector::Vector2F;
-// use gpui2::AnyWindowHandle;
-// use gpui2::{
-// fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, Handle, Task, View,
-// ViewContext, View, WeakViewHandle, WindowContext,
-// };
-// use project2::{Project, ProjectEntryId, ProjectPath};
-// use schemars::JsonSchema;
-// use serde_derive::{Deserialize, Serialize};
-// use settings2::Setting;
-// use smallvec::SmallVec;
-// use std::{
-// any::{Any, TypeId},
-// borrow::Cow,
-// cell::RefCell,
-// fmt,
-// ops::Range,
-// path::PathBuf,
-// rc::Rc,
-// sync::{
-// atomic::{AtomicBool, Ordering},
-// Arc,
-// },
-// time::Duration,
-// };
-// use theme2::Theme;
-
-// #[derive(Deserialize)]
-// pub struct ItemSettings {
-// pub git_status: bool,
-// pub close_position: ClosePosition,
-// }
+use smallvec::SmallVec;
+use std::{
+ any::{Any, TypeId},
+ ops::Range,
+ path::PathBuf,
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+ },
+ time::Duration,
+};
+use theme2::ThemeVariant;
-// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-// #[serde(rename_all = "lowercase")]
-// pub enum ClosePosition {
-// Left,
-// #[default]
-// Right,
-// }
+#[derive(Deserialize)]
+pub struct ItemSettings {
+ pub git_status: bool,
+ pub close_position: ClosePosition,
+}
-// impl ClosePosition {
-// pub fn right(&self) -> bool {
-// match self {
-// ClosePosition::Left => false,
-// ClosePosition::Right => true,
-// }
-// }
-// }
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "lowercase")]
+pub enum ClosePosition {
+ Left,
+ #[default]
+ Right,
+}
-// #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-// pub struct ItemSettingsContent {
-// git_status: Option<bool>,
-// close_position: Option<ClosePosition>,
-// }
+impl ClosePosition {
+ pub fn right(&self) -> bool {
+ match self {
+ ClosePosition::Left => false,
+ ClosePosition::Right => true,
+ }
+ }
+}
-// impl Setting for ItemSettings {
-// const KEY: Option<&'static str> = Some("tabs");
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+pub struct ItemSettingsContent {
+ git_status: Option<bool>,
+ close_position: Option<ClosePosition>,
+}
-// type FileContent = ItemSettingsContent;
+impl Settings for ItemSettings {
+ const KEY: Option<&'static str> = Some("tabs");
-// fn load(
-// default_value: &Self::FileContent,
-// user_values: &[&Self::FileContent],
-// _: &gpui2::AppContext,
-// ) -> anyhow::Result<Self> {
-// Self::load_via_json_merge(default_value, user_values)
-// }
-// }
+ type FileContent = ItemSettingsContent;
+
+ fn load(
+ default_value: &Self::FileContent,
+ user_values: &[&Self::FileContent],
+ _: &mut AppContext,
+ ) -> Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+}
#[derive(Eq, PartialEq, Hash, Debug)]
pub enum ItemEvent {
@@ -98,12 +90,12 @@ pub struct BreadcrumbText {
pub highlights: Option<Vec<(Range<usize>, HighlightStyle)>>,
}
-pub trait Item: EventEmitter + Sized {
- // fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
- // fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
- // fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
- // false
- // }
+pub trait Item: Render + EventEmitter + Send {
+ fn deactivated(&mut self, _: &mut ViewContext<Self>) {}
+ fn workspace_deactivated(&mut self, _: &mut ViewContext<Self>) {}
+ fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
+ false
+ }
fn tab_tooltip_text(&self, _: &AppContext) -> Option<SharedString> {
None
}
@@ -117,131 +109,103 @@ pub trait Item: EventEmitter + Sized {
fn is_singleton(&self, _cx: &AppContext) -> bool {
false
}
- // fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
- fn clone_on_split(&self, _workspace_id: WorkspaceId, _: &mut ViewContext<Self>) -> Option<Self>
+ fn set_nav_history(&mut self, _: ItemNavHistory, _: &mut ViewContext<Self>) {}
+ fn clone_on_split(
+ &self,
+ _workspace_id: WorkspaceId,
+ _: &mut ViewContext<Self>,
+ ) -> Option<View<Self>>
where
Self: Sized,
{
None
}
- // fn is_dirty(&self, _: &AppContext) -> bool {
- // false
- // }
- // fn has_conflict(&self, _: &AppContext) -> bool {
- // false
- // }
- // fn can_save(&self, _cx: &AppContext) -> bool {
- // false
- // }
- // fn save(
- // &mut self,
- // _project: Handle<Project>,
- // _cx: &mut ViewContext<Self>,
- // ) -> Task<Result<()>> {
- // unimplemented!("save() must be implemented if can_save() returns true")
- // }
- // fn save_as(
- // &mut self,
- // _project: Handle<Project>,
- // _abs_path: PathBuf,
- // _cx: &mut ViewContext<Self>,
- // ) -> Task<Result<()>> {
- // unimplemented!("save_as() must be implemented if can_save() returns true")
- // }
- // fn reload(
- // &mut self,
- // _project: Handle<Project>,
- // _cx: &mut ViewContext<Self>,
- // ) -> Task<Result<()>> {
- // unimplemented!("reload() must be implemented if can_save() returns true")
- // }
+ fn is_dirty(&self, _: &AppContext) -> bool {
+ false
+ }
+ fn has_conflict(&self, _: &AppContext) -> bool {
+ false
+ }
+ fn can_save(&self, _cx: &AppContext) -> bool {
+ false
+ }
+ fn save(&mut self, _project: Model<Project>, _cx: &mut ViewContext<Self>) -> Task<Result<()>> {
+ unimplemented!("save() must be implemented if can_save() returns true")
+ }
+ fn save_as(
+ &mut self,
+ _project: Model<Project>,
+ _abs_path: PathBuf,
+ _cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ unimplemented!("save_as() must be implemented if can_save() returns true")
+ }
+ fn reload(
+ &mut self,
+ _project: Model<Project>,
+ _cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ unimplemented!("reload() must be implemented if can_save() returns true")
+ }
fn to_item_events(_event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
SmallVec::new()
}
- // fn should_close_item_on_event(_: &Self::Event) -> bool {
- // false
- // }
- // fn should_update_tab_on_event(_: &Self::Event) -> bool {
- // false
- // }
-
- // fn act_as_type<'a>(
- // &'a self,
- // type_id: TypeId,
- // self_handle: &'a View<Self>,
- // _: &'a AppContext,
- // ) -> Option<&AnyViewHandle> {
- // if TypeId::of::<Self>() == type_id {
- // Some(self_handle)
- // } else {
- // None
- // }
- // }
-
- // fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
- // None
- // }
-
- // fn breadcrumb_location(&self) -> ToolbarItemLocation {
- // ToolbarItemLocation::Hidden
- // }
-
- // fn breadcrumbs(&self, _theme: &Theme, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
- // None
- // }
-
- // fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
-
- // fn serialized_item_kind() -> Option<&'static str> {
- // None
- // }
-
- // fn deserialize(
- // _project: Handle<Project>,
- // _workspace: WeakViewHandle<Workspace>,
- // _workspace_id: WorkspaceId,
- // _item_id: ItemId,
- // _cx: &mut ViewContext<Pane>,
- // ) -> Task<Result<View<Self>>> {
- // unimplemented!(
- // "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
- // )
- // }
- // fn show_toolbar(&self) -> bool {
- // true
- // }
- // fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Vector2F> {
- // None
- // }
-}
+ fn should_close_item_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn should_update_tab_on_event(_: &Self::Event) -> bool {
+ false
+ }
-use std::{
- any::Any,
- cell::RefCell,
- ops::Range,
- path::PathBuf,
- rc::Rc,
- sync::{
- atomic::{AtomicBool, Ordering},
- Arc,
- },
- time::Duration,
-};
+ fn act_as_type<'a>(
+ &'a self,
+ type_id: TypeId,
+ self_handle: &'a View<Self>,
+ _: &'a AppContext,
+ ) -> Option<AnyView> {
+ if TypeId::of::<Self>() == type_id {
+ Some(self_handle.clone().into())
+ } else {
+ None
+ }
+ }
-use gpui2::{
- AnyElement, AnyWindowHandle, AppContext, EventEmitter, Handle, HighlightStyle, Pixels, Point,
- SharedString, Task, View, ViewContext, VisualContext, WindowContext,
-};
-use project2::{Project, ProjectEntryId, ProjectPath};
-use smallvec::SmallVec;
+ fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
+ None
+ }
-use crate::{
- pane::{self, Pane},
- searchable::SearchableItemHandle,
- workspace_settings::{AutosaveSetting, WorkspaceSettings},
- DelayedDebouncedEditAction, FollowableItemBuilders, ToolbarItemLocation, Workspace,
- WorkspaceId,
-};
+ fn breadcrumb_location(&self) -> ToolbarItemLocation {
+ ToolbarItemLocation::Hidden
+ }
+
+ fn breadcrumbs(&self, _theme: &ThemeVariant, _cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+ None
+ }
+
+ fn added_to_workspace(&mut self, _workspace: &mut Workspace, _cx: &mut ViewContext<Self>) {}
+
+ fn serialized_item_kind() -> Option<&'static str> {
+ None
+ }
+
+ fn deserialize(
+ _project: Model<Project>,
+ _workspace: WeakView<Workspace>,
+ _workspace_id: WorkspaceId,
+ _item_id: ItemId,
+ _cx: &mut ViewContext<Pane>,
+ ) -> Task<Result<View<Self>>> {
+ unimplemented!(
+ "deserialize() must be implemented if serialized_item_kind() returns Some(_)"
+ )
+ }
+ fn show_toolbar(&self) -> bool {
+ true
+ }
+ fn pixel_position_of_cursor(&self, _: &AppContext) -> Option<Point<Pixels>> {
+ None
+ }
+}
pub trait ItemHandle: 'static + Send {
fn subscribe_to_item_events(
@@ -273,52 +237,49 @@ pub trait ItemHandle: 'static + Send {
fn deactivated(&self, cx: &mut WindowContext);
fn workspace_deactivated(&self, cx: &mut WindowContext);
fn navigate(&self, data: Box<dyn Any>, cx: &mut WindowContext) -> bool;
- fn id(&self) -> usize;
- fn window(&self) -> AnyWindowHandle;
- // fn as_any(&self) -> &AnyView; todo!()
+ fn id(&self) -> EntityId;
+ fn to_any(&self) -> AnyView;
fn is_dirty(&self, cx: &AppContext) -> bool;
fn has_conflict(&self, cx: &AppContext) -> bool;
fn can_save(&self, cx: &AppContext) -> bool;
- fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+ fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
fn save_as(
&self,
- project: Handle<Project>,
+ project: Model<Project>,
abs_path: PathBuf,
cx: &mut WindowContext,
) -> Task<Result<()>>;
- fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
- // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle>; todo!()
+ fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>>;
+ fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyView>;
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>>;
fn on_release(
- &self,
+ &mut self,
cx: &mut AppContext,
- callback: Box<dyn FnOnce(&mut AppContext)>,
+ callback: Box<dyn FnOnce(&mut AppContext) + Send>,
) -> gpui2::Subscription;
fn to_searchable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
fn breadcrumb_location(&self, cx: &AppContext) -> ToolbarItemLocation;
- fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
+ fn breadcrumbs(&self, theme: &ThemeVariant, cx: &AppContext) -> Option<Vec<BreadcrumbText>>;
fn serialized_item_kind(&self) -> Option<&'static str>;
fn show_toolbar(&self, cx: &AppContext) -> bool;
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>>;
}
-pub trait WeakItemHandle {
- fn id(&self) -> usize;
- fn window(&self) -> AnyWindowHandle;
- fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>>;
+pub trait WeakItemHandle: Send + Sync {
+ fn id(&self) -> EntityId;
+ fn upgrade(&self) -> Option<Box<dyn ItemHandle>>;
}
-// todo!()
-// impl dyn ItemHandle {
-// pub fn downcast<T: View>(&self) -> Option<View<T>> {
-// self.as_any().clone().downcast()
-// }
+impl dyn ItemHandle {
+ pub fn downcast<V: 'static>(&self) -> Option<View<V>> {
+ self.to_any().downcast().ok()
+ }
-// pub fn act_as<T: View>(&self, cx: &AppContext) -> Option<View<T>> {
-// self.act_as_type(TypeId::of::<T>(), cx)
-// .and_then(|t| t.clone().downcast())
-// }
-// }
+ pub fn act_as<V: 'static>(&self, cx: &AppContext) -> Option<View<V>> {
+ self.act_as_type(TypeId::of::<V>(), cx)
+ .and_then(|t| t.downcast().ok())
+ }
+}
impl<T: Item> ItemHandle for View<T> {
fn subscribe_to_item_events(
@@ -399,10 +360,8 @@ impl<T: Item> ItemHandle for View<T> {
workspace_id: WorkspaceId,
cx: &mut WindowContext,
) -> Option<Box<dyn ItemHandle>> {
- self.update(cx, |item, cx| {
- cx.add_option_view(|cx| item.clone_on_split(workspace_id, cx))
- })
- .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
+ self.update(cx, |item, cx| item.clone_on_split(workspace_id, cx))
+ .map(|handle| Box::new(handle) as Box<dyn ItemHandle>)
}
fn added_to_pane(
@@ -439,109 +398,107 @@ impl<T: Item> ItemHandle for View<T> {
.is_none()
{
let mut pending_autosave = DelayedDebouncedEditAction::new();
- let pending_update = Rc::new(RefCell::new(None));
- let pending_update_scheduled = Rc::new(AtomicBool::new(false));
-
- let mut event_subscription =
- Some(cx.subscribe(self, move |workspace, item, event, cx| {
- let pane = if let Some(pane) = workspace
- .panes_by_item
- .get(&item.id())
- .and_then(|pane| pane.upgrade(cx))
+ let pending_update = Arc::new(Mutex::new(None));
+ let pending_update_scheduled = Arc::new(AtomicBool::new(false));
+
+ let event_subscription = Some(cx.subscribe(self, move |workspace, item, event, cx| {
+ let pane = if let Some(pane) = workspace
+ .panes_by_item
+ .get(&item.id())
+ .and_then(|pane| pane.upgrade())
+ {
+ pane
+ } else {
+ log::error!("unexpected item event after pane was dropped");
+ return;
+ };
+
+ if let Some(item) = item.to_followable_item_handle(cx) {
+ let _is_project_item = item.is_project_item(cx);
+ let leader_id = workspace.leader_for_pane(&pane);
+
+ if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
+ workspace.unfollow(&pane, cx);
+ }
+
+ if item.add_event_to_update_proto(event, &mut *pending_update.lock(), cx)
+ && !pending_update_scheduled.load(Ordering::SeqCst)
{
- pane
- } else {
- log::error!("unexpected item event after pane was dropped");
- return;
- };
-
- if let Some(item) = item.to_followable_item_handle(cx) {
- let is_project_item = item.is_project_item(cx);
- let leader_id = workspace.leader_for_pane(&pane);
-
- if leader_id.is_some() && item.should_unfollow_on_event(event, cx) {
- workspace.unfollow(&pane, cx);
+ pending_update_scheduled.store(true, Ordering::SeqCst);
+ todo!("replace with on_next_frame?");
+ // cx.after_window_update({
+ // let pending_update = pending_update.clone();
+ // let pending_update_scheduled = pending_update_scheduled.clone();
+ // move |this, cx| {
+ // pending_update_scheduled.store(false, Ordering::SeqCst);
+ // this.update_followers(
+ // is_project_item,
+ // proto::update_followers::Variant::UpdateView(
+ // proto::UpdateView {
+ // id: item
+ // .remote_id(&this.app_state.client, cx)
+ // .map(|id| id.to_proto()),
+ // variant: pending_update.borrow_mut().take(),
+ // leader_id,
+ // },
+ // ),
+ // cx,
+ // );
+ // }
+ // });
+ }
+ }
+
+ for item_event in T::to_item_events(event).into_iter() {
+ match item_event {
+ ItemEvent::CloseItem => {
+ pane.update(cx, |pane, cx| {
+ pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
+ })
+ .detach_and_log_err(cx);
+ return;
}
- if item.add_event_to_update_proto(
- event,
- &mut *pending_update.borrow_mut(),
- cx,
- ) && !pending_update_scheduled.load(Ordering::SeqCst)
- {
- pending_update_scheduled.store(true, Ordering::SeqCst);
- cx.after_window_update({
- let pending_update = pending_update.clone();
- let pending_update_scheduled = pending_update_scheduled.clone();
- move |this, cx| {
- pending_update_scheduled.store(false, Ordering::SeqCst);
- this.update_followers(
- is_project_item,
- proto::update_followers::Variant::UpdateView(
- proto::UpdateView {
- id: item
- .remote_id(&this.app_state.client, cx)
- .map(|id| id.to_proto()),
- variant: pending_update.borrow_mut().take(),
- leader_id,
- },
- ),
- cx,
- );
- }
+ ItemEvent::UpdateTab => {
+ pane.update(cx, |_, cx| {
+ cx.emit(pane::Event::ChangeItemTitle);
+ cx.notify();
});
}
- }
- for item_event in T::to_item_events(event).into_iter() {
- match item_event {
- ItemEvent::CloseItem => {
- pane.update(cx, |pane, cx| {
- pane.close_item_by_id(item.id(), crate::SaveIntent::Close, cx)
- })
- .detach_and_log_err(cx);
- return;
- }
-
- ItemEvent::UpdateTab => {
- pane.update(cx, |_, cx| {
- cx.emit(pane::Event::ChangeItemTitle);
- cx.notify();
+ ItemEvent::Edit => {
+ let autosave = WorkspaceSettings::get_global(cx).autosave;
+ if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
+ let delay = Duration::from_millis(milliseconds);
+ let item = item.clone();
+ pending_autosave.fire_new(delay, cx, move |workspace, cx| {
+ Pane::autosave_item(&item, workspace.project().clone(), cx)
});
}
-
- ItemEvent::Edit => {
- let autosave = WorkspaceSettings::get_global(cx).autosave;
- if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
- let delay = Duration::from_millis(milliseconds);
- let item = item.clone();
- pending_autosave.fire_new(delay, cx, move |workspace, cx| {
- Pane::autosave_item(&item, workspace.project().clone(), cx)
- });
- }
- }
-
- _ => {}
}
- }
- }));
- cx.observe_focus(self, move |workspace, item, focused, cx| {
- if !focused
- && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
- {
- Pane::autosave_item(&item, workspace.project.clone(), cx)
- .detach_and_log_err(cx);
+ _ => {}
+ }
}
- })
- .detach();
-
- let item_id = self.id();
- cx.observe_release(self, move |workspace, _, _| {
- workspace.panes_by_item.remove(&item_id);
- event_subscription.take();
- })
- .detach();
+ }));
+
+ todo!("observe focus");
+ // cx.observe_focus(self, move |workspace, item, focused, cx| {
+ // if !focused
+ // && WorkspaceSettings::get_global(cx).autosave == AutosaveSetting::OnFocusChange
+ // {
+ // Pane::autosave_item(&item, workspace.project.clone(), cx)
+ // .detach_and_log_err(cx);
+ // }
+ // })
+ // .detach();
+
+ // let item_id = self.id();
+ // cx.observe_release(self, move |workspace, _, _| {
+ // workspace.panes_by_item.remove(&item_id);
+ // event_subscription.take();
+ // })
+ // .detach();
}
cx.defer(|workspace, cx| {
@@ -561,20 +518,14 @@ impl<T: Item> ItemHandle for View<T> {
self.update(cx, |this, cx| this.navigate(data, cx))
}
- fn id(&self) -> usize {
- self.id()
+ fn id(&self) -> EntityId {
+ self.entity_id()
}
- fn window(&self) -> AnyWindowHandle {
- todo!()
- // AnyViewHandle::window(self)
+ fn to_any(&self) -> AnyView {
+ self.clone().into()
}
- // todo!()
- // fn as_any(&self) -> &AnyViewHandle {
- // self
- // }
-
fn is_dirty(&self, cx: &AppContext) -> bool {
self.read(cx).is_dirty(cx)
}
@@ -587,42 +538,41 @@ impl<T: Item> ItemHandle for View<T> {
self.read(cx).can_save(cx)
}
- fn save(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+ fn save(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
self.update(cx, |item, cx| item.save(project, cx))
}
fn save_as(
&self,
- project: Handle<Project>,
+ project: Model<Project>,
abs_path: PathBuf,
cx: &mut WindowContext,
) -> Task<anyhow::Result<()>> {
self.update(cx, |item, cx| item.save_as(project, abs_path, cx))
}
- fn reload(&self, project: Handle<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
+ fn reload(&self, project: Model<Project>, cx: &mut WindowContext) -> Task<Result<()>> {
self.update(cx, |item, cx| item.reload(project, cx))
}
- // todo!()
- // fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<&'a AnyViewHandle> {
- // self.read(cx).act_as_type(type_id, self, cx)
- // }
+ fn act_as_type<'a>(&'a self, type_id: TypeId, cx: &'a AppContext) -> Option<AnyView> {
+ self.read(cx).act_as_type(type_id, self, cx)
+ }
fn to_followable_item_handle(&self, cx: &AppContext) -> Option<Box<dyn FollowableItemHandle>> {
if cx.has_global::<FollowableItemBuilders>() {
let builders = cx.global::<FollowableItemBuilders>();
- let item = self.as_any();
- Some(builders.get(&item.view_type())?.1(item))
+ let item = self.to_any();
+ Some(builders.get(&item.entity_type())?.1(&item))
} else {
None
}
}
fn on_release(
- &self,
+ &mut self,
cx: &mut AppContext,
- callback: Box<dyn FnOnce(&mut AppContext)>,
+ callback: Box<dyn FnOnce(&mut AppContext) + Send>,
) -> gpui2::Subscription {
cx.observe_release(self, move |_, cx| callback(cx))
}
@@ -635,7 +585,7 @@ impl<T: Item> ItemHandle for View<T> {
self.read(cx).breadcrumb_location()
}
- fn breadcrumbs(&self, theme: &Theme, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
+ fn breadcrumbs(&self, theme: &ThemeVariant, cx: &AppContext) -> Option<Vec<BreadcrumbText>> {
self.read(cx).breadcrumbs(theme, cx)
}
@@ -652,17 +602,17 @@ impl<T: Item> ItemHandle for View<T> {
}
}
-// impl From<Box<dyn ItemHandle>> for AnyViewHandle {
-// fn from(val: Box<dyn ItemHandle>) -> Self {
-// val.as_any().clone()
-// }
-// }
+impl From<Box<dyn ItemHandle>> for AnyView {
+ fn from(val: Box<dyn ItemHandle>) -> Self {
+ val.to_any()
+ }
+}
-// impl From<&Box<dyn ItemHandle>> for AnyViewHandle {
-// fn from(val: &Box<dyn ItemHandle>) -> Self {
-// val.as_any().clone()
-// }
-// }
+impl From<&Box<dyn ItemHandle>> for AnyView {
+ fn from(val: &Box<dyn ItemHandle>) -> Self {
+ val.to_any()
+ }
+}
impl Clone for Box<dyn ItemHandle> {
fn clone(&self) -> Box<dyn ItemHandle> {
@@ -670,26 +620,22 @@ impl Clone for Box<dyn ItemHandle> {
}
}
-// impl<T: Item> WeakItemHandle for WeakViewHandle<T> {
-// fn id(&self) -> usize {
-// self.id()
-// }
-
-// fn window(&self) -> AnyWindowHandle {
-// self.window()
-// }
+impl<T: Item> WeakItemHandle for WeakView<T> {
+ fn id(&self) -> EntityId {
+ self.entity_id()
+ }
-// fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
-// self.upgrade(cx).map(|v| Box::new(v) as Box<dyn ItemHandle>)
-// }
-// }
+ fn upgrade(&self) -> Option<Box<dyn ItemHandle>> {
+ self.upgrade().map(|v| Box::new(v) as Box<dyn ItemHandle>)
+ }
+}
pub trait ProjectItem: Item {
type Item: project2::Item;
fn for_project_item(
- project: Handle<Project>,
- item: Handle<Self::Item>,
+ project: Model<Project>,
+ item: Model<Self::Item>,
cx: &mut ViewContext<Self>,
) -> Self
where
@@ -714,7 +660,7 @@ pub trait FollowableItem: Item {
) -> bool;
fn apply_update_proto(
&mut self,
- project: &Handle<Project>,
+ project: &Model<Project>,
message: proto::update_view::Variant,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>>;
@@ -736,7 +682,7 @@ pub trait FollowableItemHandle: ItemHandle {
) -> bool;
fn apply_update_proto(
&self,
- project: &Handle<Project>,
+ project: &Model<Project>,
message: proto::update_view::Variant,
cx: &mut WindowContext,
) -> Task<Result<()>>;
@@ -744,65 +690,65 @@ pub trait FollowableItemHandle: ItemHandle {
fn is_project_item(&self, cx: &AppContext) -> bool;
}
-// impl<T: FollowableItem> FollowableItemHandle for View<T> {
-// fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
-// self.read(cx).remote_id().or_else(|| {
-// client.peer_id().map(|creator| ViewId {
-// creator,
-// id: self.id() as u64,
-// })
-// })
-// }
+impl<T: FollowableItem> FollowableItemHandle for View<T> {
+ fn remote_id(&self, client: &Arc<Client>, cx: &AppContext) -> Option<ViewId> {
+ self.read(cx).remote_id().or_else(|| {
+ client.peer_id().map(|creator| ViewId {
+ creator,
+ id: self.id().as_u64(),
+ })
+ })
+ }
-// fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
-// self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
-// }
+ fn set_leader_peer_id(&self, leader_peer_id: Option<PeerId>, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| this.set_leader_peer_id(leader_peer_id, cx))
+ }
-// fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
-// self.read(cx).to_state_proto(cx)
-// }
+ fn to_state_proto(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+ self.read(cx).to_state_proto(cx)
+ }
-// fn add_event_to_update_proto(
-// &self,
-// event: &dyn Any,
-// update: &mut Option<proto::update_view::Variant>,
-// cx: &AppContext,
-// ) -> bool {
-// if let Some(event) = event.downcast_ref() {
-// self.read(cx).add_event_to_update_proto(event, update, cx)
-// } else {
-// false
-// }
-// }
+ fn add_event_to_update_proto(
+ &self,
+ event: &dyn Any,
+ update: &mut Option<proto::update_view::Variant>,
+ cx: &AppContext,
+ ) -> bool {
+ if let Some(event) = event.downcast_ref() {
+ self.read(cx).add_event_to_update_proto(event, update, cx)
+ } else {
+ false
+ }
+ }
-// fn apply_update_proto(
-// &self,
-// project: &Handle<Project>,
-// message: proto::update_view::Variant,
-// cx: &mut WindowContext,
-// ) -> Task<Result<()>> {
-// self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
-// }
+ fn apply_update_proto(
+ &self,
+ project: &Model<Project>,
+ message: proto::update_view::Variant,
+ cx: &mut WindowContext,
+ ) -> Task<Result<()>> {
+ self.update(cx, |this, cx| this.apply_update_proto(project, message, cx))
+ }
-// fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
-// if let Some(event) = event.downcast_ref() {
-// T::should_unfollow_on_event(event, cx)
-// } else {
-// false
-// }
-// }
+ fn should_unfollow_on_event(&self, event: &dyn Any, cx: &AppContext) -> bool {
+ if let Some(event) = event.downcast_ref() {
+ T::should_unfollow_on_event(event, cx)
+ } else {
+ false
+ }
+ }
-// fn is_project_item(&self, cx: &AppContext) -> bool {
-// self.read(cx).is_project_item(cx)
-// }
-// }
+ fn is_project_item(&self, cx: &AppContext) -> bool {
+ self.read(cx).is_project_item(cx)
+ }
+}
// #[cfg(any(test, feature = "test-support"))]
// pub mod test {
// use super::{Item, ItemEvent};
// use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId};
// use gpui2::{
-// elements::Empty, AnyElement, AppContext, Element, Entity, Handle, Task, View,
+// elements::Empty, AnyElement, AppContext, Element, Entity, Model, Task, View,
// ViewContext, View, WeakViewHandle,
// };
// use project2::{Project, ProjectEntryId, ProjectPath, WorktreeId};
@@ -824,7 +770,7 @@ pub trait FollowableItemHandle: ItemHandle {
// pub is_dirty: bool,
// pub is_singleton: bool,
// pub has_conflict: bool,
-// pub project_items: Vec<Handle<TestProjectItem>>,
+// pub project_items: Vec<Model<TestProjectItem>>,
// pub nav_history: Option<ItemNavHistory>,
// pub tab_descriptions: Option<Vec<&'static str>>,
// pub tab_detail: Cell<Option<usize>>,
@@ -869,7 +815,7 @@ pub trait FollowableItemHandle: ItemHandle {
// }
// impl TestProjectItem {
-// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Handle<Self> {
+// pub fn new(id: u64, path: &str, cx: &mut AppContext) -> Model<Self> {
// let entry_id = Some(ProjectEntryId::from_proto(id));
// let project_path = Some(ProjectPath {
// worktree_id: WorktreeId::from_usize(0),
@@ -881,7 +827,7 @@ pub trait FollowableItemHandle: ItemHandle {
// })
// }
-// pub fn new_untitled(cx: &mut AppContext) -> Handle<Self> {
+// pub fn new_untitled(cx: &mut AppContext) -> Model<Self> {
// cx.add_model(|_| Self {
// project_path: None,
// entry_id: None,
@@ -934,7 +880,7 @@ pub trait FollowableItemHandle: ItemHandle {
// self
// }
-// pub fn with_project_items(mut self, items: &[Handle<TestProjectItem>]) -> Self {
+// pub fn with_project_items(mut self, items: &[Model<TestProjectItem>]) -> Self {
// self.project_items.clear();
// self.project_items.extend(items.iter().cloned());
// self
@@ -1045,7 +991,7 @@ pub trait FollowableItemHandle: ItemHandle {
// fn save(
// &mut self,
-// _: Handle<Project>,
+// _: Model<Project>,
// _: &mut ViewContext<Self>,
// ) -> Task<anyhow::Result<()>> {
// self.save_count += 1;
@@ -0,0 +1,404 @@
+use crate::{Toast, Workspace};
+use collections::HashMap;
+use gpui2::{AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext};
+use std::{any::TypeId, ops::DerefMut};
+
+pub fn init(cx: &mut AppContext) {
+ cx.set_global(NotificationTracker::new());
+ // todo!()
+ // simple_message_notification::init(cx);
+}
+
+pub trait Notification: EventEmitter + Render {
+ fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool;
+}
+
+pub trait NotificationHandle: Send {
+ fn id(&self) -> EntityId;
+ fn to_any(&self) -> AnyView;
+}
+
+impl<T: Notification> NotificationHandle for View<T> {
+ fn id(&self) -> EntityId {
+ self.entity_id()
+ }
+
+ fn to_any(&self) -> AnyView {
+ self.clone().into()
+ }
+}
+
+impl From<&dyn NotificationHandle> for AnyView {
+ fn from(val: &dyn NotificationHandle) -> Self {
+ val.to_any()
+ }
+}
+
+pub(crate) struct NotificationTracker {
+ notifications_sent: HashMap<TypeId, Vec<usize>>,
+}
+
+impl std::ops::Deref for NotificationTracker {
+ type Target = HashMap<TypeId, Vec<usize>>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.notifications_sent
+ }
+}
+
+impl DerefMut for NotificationTracker {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.notifications_sent
+ }
+}
+
+impl NotificationTracker {
+ fn new() -> Self {
+ Self {
+ notifications_sent: Default::default(),
+ }
+ }
+}
+
+impl Workspace {
+ pub fn has_shown_notification_once<V: Notification>(
+ &self,
+ id: usize,
+ cx: &ViewContext<Self>,
+ ) -> bool {
+ cx.global::<NotificationTracker>()
+ .get(&TypeId::of::<V>())
+ .map(|ids| ids.contains(&id))
+ .unwrap_or(false)
+ }
+
+ pub fn show_notification_once<V: Notification>(
+ &mut self,
+ id: usize,
+ cx: &mut ViewContext<Self>,
+ build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
+ ) {
+ if !self.has_shown_notification_once::<V>(id, cx) {
+ let tracker = cx.global_mut::<NotificationTracker>();
+ let entry = tracker.entry(TypeId::of::<V>()).or_default();
+ entry.push(id);
+ self.show_notification::<V>(id, cx, build_notification)
+ }
+ }
+
+ pub fn show_notification<V: Notification>(
+ &mut self,
+ id: usize,
+ cx: &mut ViewContext<Self>,
+ build_notification: impl FnOnce(&mut ViewContext<Self>) -> View<V>,
+ ) {
+ let type_id = TypeId::of::<V>();
+ if self
+ .notifications
+ .iter()
+ .all(|(existing_type_id, existing_id, _)| {
+ (*existing_type_id, *existing_id) != (type_id, id)
+ })
+ {
+ let notification = build_notification(cx);
+ cx.subscribe(¬ification, move |this, handle, event, cx| {
+ if handle.read(cx).should_dismiss_notification_on_event(event) {
+ this.dismiss_notification_internal(type_id, id, cx);
+ }
+ })
+ .detach();
+ self.notifications
+ .push((type_id, id, Box::new(notification)));
+ cx.notify();
+ }
+ }
+
+ pub fn dismiss_notification<V: Notification>(&mut self, id: usize, cx: &mut ViewContext<Self>) {
+ let type_id = TypeId::of::<V>();
+
+ self.dismiss_notification_internal(type_id, id, cx)
+ }
+
+ pub fn show_toast(&mut self, toast: Toast, cx: &mut ViewContext<Self>) {
+ todo!()
+ // self.dismiss_notification::<simple_message_notification::MessageNotification>(toast.id, cx);
+ // self.show_notification(toast.id, cx, |cx| {
+ // cx.add_view(|_cx| match toast.on_click.as_ref() {
+ // Some((click_msg, on_click)) => {
+ // let on_click = on_click.clone();
+ // simple_message_notification::MessageNotification::new(toast.msg.clone())
+ // .with_click_message(click_msg.clone())
+ // .on_click(move |cx| on_click(cx))
+ // }
+ // None => simple_message_notification::MessageNotification::new(toast.msg.clone()),
+ // })
+ // })
+ }
+
+ pub fn dismiss_toast(&mut self, id: usize, cx: &mut ViewContext<Self>) {
+ todo!()
+ // self.dismiss_notification::<simple_message_notification::MessageNotification>(id, cx);
+ }
+
+ fn dismiss_notification_internal(
+ &mut self,
+ type_id: TypeId,
+ id: usize,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.notifications
+ .retain(|(existing_type_id, existing_id, _)| {
+ if (*existing_type_id, *existing_id) == (type_id, id) {
+ cx.notify();
+ false
+ } else {
+ true
+ }
+ });
+ }
+}
+
+pub mod simple_message_notification {
+ use super::Notification;
+ use gpui2::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext};
+ use serde::Deserialize;
+ use std::{borrow::Cow, sync::Arc};
+
+ // todo!()
+ // actions!(message_notifications, [CancelMessageNotification]);
+
+ #[derive(Clone, Default, Deserialize, PartialEq)]
+ pub struct OsOpen(pub Cow<'static, str>);
+
+ impl OsOpen {
+ pub fn new<I: Into<Cow<'static, str>>>(url: I) -> Self {
+ OsOpen(url.into())
+ }
+ }
+
+ // todo!()
+ // impl_actions!(message_notifications, [OsOpen]);
+ //
+ // todo!()
+ // pub fn init(cx: &mut AppContext) {
+ // cx.add_action(MessageNotification::dismiss);
+ // cx.add_action(
+ // |_workspace: &mut Workspace, open_action: &OsOpen, cx: &mut ViewContext<Workspace>| {
+ // cx.platform().open_url(open_action.0.as_ref());
+ // },
+ // )
+ // }
+
+ enum NotificationMessage {
+ Text(Cow<'static, str>),
+ Element(fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>),
+ }
+
+ pub struct MessageNotification {
+ message: NotificationMessage,
+ on_click: Option<Arc<dyn Fn(&mut ViewContext<Self>) + Send + Sync>>,
+ click_message: Option<Cow<'static, str>>,
+ }
+
+ pub enum MessageNotificationEvent {
+ Dismiss,
+ }
+
+ impl EventEmitter for MessageNotification {
+ type Event = MessageNotificationEvent;
+ }
+
+ impl MessageNotification {
+ pub fn new<S>(message: S) -> MessageNotification
+ where
+ S: Into<Cow<'static, str>>,
+ {
+ Self {
+ message: NotificationMessage::Text(message.into()),
+ on_click: None,
+ click_message: None,
+ }
+ }
+
+ // todo!()
+ // pub fn new_element(
+ // message: fn(TextStyle, &AppContext) -> AnyElement<MessageNotification>,
+ // ) -> MessageNotification {
+ // Self {
+ // message: NotificationMessage::Element(message),
+ // on_click: None,
+ // click_message: None,
+ // }
+ // }
+
+ // pub fn with_click_message<S>(mut self, message: S) -> Self
+ // where
+ // S: Into<Cow<'static, str>>,
+ // {
+ // self.click_message = Some(message.into());
+ // self
+ // }
+
+ // pub fn on_click<F>(mut self, on_click: F) -> Self
+ // where
+ // F: 'static + Fn(&mut ViewContext<Self>),
+ // {
+ // self.on_click = Some(Arc::new(on_click));
+ // self
+ // }
+
+ // pub fn dismiss(&mut self, _: &CancelMessageNotification, cx: &mut ViewContext<Self>) {
+ // cx.emit(MessageNotificationEvent::Dismiss);
+ // }
+ }
+
+ impl Render for MessageNotification {
+ type Element = Div<Self>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ todo!()
+ }
+ }
+ // todo!()
+ // impl View for MessageNotification {
+ // fn ui_name() -> &'static str {
+ // "MessageNotification"
+ // }
+
+ // fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> gpui::AnyElement<Self> {
+ // let theme = theme2::current(cx).clone();
+ // let theme = &theme.simple_message_notification;
+
+ // enum MessageNotificationTag {}
+
+ // let click_message = self.click_message.clone();
+ // let message = match &self.message {
+ // NotificationMessage::Text(text) => {
+ // Text::new(text.to_owned(), theme.message.text.clone()).into_any()
+ // }
+ // NotificationMessage::Element(e) => e(theme.message.text.clone(), cx),
+ // };
+ // let on_click = self.on_click.clone();
+ // let has_click_action = on_click.is_some();
+
+ // Flex::column()
+ // .with_child(
+ // Flex::row()
+ // .with_child(
+ // message
+ // .contained()
+ // .with_style(theme.message.container)
+ // .aligned()
+ // .top()
+ // .left()
+ // .flex(1., true),
+ // )
+ // .with_child(
+ // MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
+ // let style = theme.dismiss_button.style_for(state);
+ // Svg::new("icons/x.svg")
+ // .with_color(style.color)
+ // .constrained()
+ // .with_width(style.icon_width)
+ // .aligned()
+ // .contained()
+ // .with_style(style.container)
+ // .constrained()
+ // .with_width(style.button_width)
+ // .with_height(style.button_width)
+ // })
+ // .with_padding(Padding::uniform(5.))
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // this.dismiss(&Default::default(), cx);
+ // })
+ // .with_cursor_style(CursorStyle::PointingHand)
+ // .aligned()
+ // .constrained()
+ // .with_height(cx.font_cache().line_height(theme.message.text.font_size))
+ // .aligned()
+ // .top()
+ // .flex_float(),
+ // ),
+ // )
+ // .with_children({
+ // click_message
+ // .map(|click_message| {
+ // MouseEventHandler::new::<MessageNotificationTag, _>(
+ // 0,
+ // cx,
+ // |state, _| {
+ // let style = theme.action_message.style_for(state);
+
+ // Flex::row()
+ // .with_child(
+ // Text::new(click_message, style.text.clone())
+ // .contained()
+ // .with_style(style.container),
+ // )
+ // .contained()
+ // },
+ // )
+ // .on_click(MouseButton::Left, move |_, this, cx| {
+ // if let Some(on_click) = on_click.as_ref() {
+ // on_click(cx);
+ // this.dismiss(&Default::default(), cx);
+ // }
+ // })
+ // // Since we're not using a proper overlay, we have to capture these extra events
+ // .on_down(MouseButton::Left, |_, _, _| {})
+ // .on_up(MouseButton::Left, |_, _, _| {})
+ // .with_cursor_style(if has_click_action {
+ // CursorStyle::PointingHand
+ // } else {
+ // CursorStyle::Arrow
+ // })
+ // })
+ // .into_iter()
+ // })
+ // .into_any()
+ // }
+ // }
+
+ impl Notification for MessageNotification {
+ fn should_dismiss_notification_on_event(&self, event: &Self::Event) -> bool {
+ match event {
+ MessageNotificationEvent::Dismiss => true,
+ }
+ }
+ }
+}
+
+pub trait NotifyResultExt {
+ type Ok;
+
+ fn notify_err(
+ self,
+ workspace: &mut Workspace,
+ cx: &mut ViewContext<Workspace>,
+ ) -> Option<Self::Ok>;
+}
+
+impl<T, E> NotifyResultExt for Result<T, E>
+where
+ E: std::fmt::Debug,
+{
+ type Ok = T;
+
+ fn notify_err(self, workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Option<T> {
+ match self {
+ Ok(value) => Some(value),
+ Err(err) => {
+ log::error!("TODO {err:?}");
+ // todo!()
+ // workspace.show_notification(0, cx, |cx| {
+ // cx.add_view(|_cx| {
+ // simple_message_notification::MessageNotification::new(format!(
+ // "Error: {err:?}",
+ // ))
+ // })
+ // });
+ None
+ }
+ }
+ }
+}
@@ -1,48 +1,33 @@
// mod dragged_item_receiver;
-// use super::{ItemHandle, SplitDirection};
-// pub use crate::toolbar::Toolbar;
-// use crate::{
-// item::{ItemSettings, WeakItemHandle},
-// notify_of_new_dock, AutosaveSetting, Item, NewCenterTerminal, NewFile, NewSearch, ToggleZoom,
-// Workspace, WorkspaceSettings,
-// };
-// use anyhow::Result;
-// use collections::{HashMap, HashSet, VecDeque};
-// // use context_menu::{ContextMenu, ContextMenuItem};
-
-// use dragged_item_receiver::dragged_item_receiver;
-// use fs2::repository::GitFileStatus;
-// use futures::StreamExt;
-// use gpui2::{
-// actions,
-// elements::*,
-// geometry::{
-// rect::RectF,
-// vector::{vec2f, Vector2F},
-// },
-// impl_actions,
-// keymap_matcher::KeymapContext,
-// platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
-// Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
-// ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
-// WindowContext,
-// };
-// use project2::{Project, ProjectEntryId, ProjectPath};
+use crate::{
+ item::{Item, ItemHandle, ItemSettings, WeakItemHandle},
+ toolbar::Toolbar,
+ workspace_settings::{AutosaveSetting, WorkspaceSettings},
+ SplitDirection, Workspace,
+};
+use anyhow::Result;
+use collections::{HashMap, HashSet, VecDeque};
+use gpui2::{
+ AppContext, AsyncWindowContext, Component, Div, EntityId, EventEmitter, Model, PromptLevel,
+ Render, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+};
+use parking_lot::Mutex;
+use project2::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize;
-// use std::{
-// any::Any,
-// cell::RefCell,
-// cmp, mem,
-// path::{Path, PathBuf},
-// rc::Rc,
-// sync::{
-// atomic::{AtomicUsize, Ordering},
-// Arc,
-// },
-// };
-// use theme2::{Theme, ThemeSettings};
-// use util::truncate_and_remove_front;
+use settings2::Settings;
+use std::{
+ any::Any,
+ cmp, fmt, mem,
+ path::{Path, PathBuf},
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+};
+use ui::v_stack;
+use ui::{prelude::*, Icon, IconButton, IconColor, IconElement};
+use util::truncate_and_remove_front;
#[derive(PartialEq, Clone, Copy, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
@@ -69,19 +54,19 @@ pub enum SaveIntent {
// #[derive(Clone, PartialEq)]
// pub struct CloseItemById {
// pub item_id: usize,
-// pub pane: WeakViewHandle<Pane>,
+// pub pane: WeakView<Pane>,
// }
// #[derive(Clone, PartialEq)]
// pub struct CloseItemsToTheLeftById {
// pub item_id: usize,
-// pub pane: WeakViewHandle<Pane>,
+// pub pane: WeakView<Pane>,
// }
// #[derive(Clone, PartialEq)]
// pub struct CloseItemsToTheRightById {
// pub item_id: usize,
-// pub pane: WeakViewHandle<Pane>,
+// pub pane: WeakView<Pane>,
// }
// #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
@@ -96,6 +81,7 @@ pub enum SaveIntent {
// pub save_intent: Option<SaveIntent>,
// }
+// todo!()
// actions!(
// pane,
// [
@@ -118,40 +104,40 @@ pub enum SaveIntent {
// impl_actions!(pane, [ActivateItem, CloseActiveItem, CloseAllItems]);
-// const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
-
-// pub fn init(cx: &mut AppContext) {
-// cx.add_action(Pane::toggle_zoom);
-// cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
-// pane.activate_item(action.0, true, true, cx);
-// });
-// cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
-// pane.activate_item(pane.items.len() - 1, true, true, cx);
-// });
-// cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
-// pane.activate_prev_item(true, cx);
-// });
-// cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
-// pane.activate_next_item(true, cx);
-// });
-// cx.add_async_action(Pane::close_active_item);
-// cx.add_async_action(Pane::close_inactive_items);
-// cx.add_async_action(Pane::close_clean_items);
-// cx.add_async_action(Pane::close_items_to_the_left);
-// cx.add_async_action(Pane::close_items_to_the_right);
-// cx.add_async_action(Pane::close_all_items);
-// cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
-// cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
-// cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
-// cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
-// }
+const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
+
+pub fn init(cx: &mut AppContext) {
+ // todo!()
+ // cx.add_action(Pane::toggle_zoom);
+ // cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
+ // pane.activate_item(action.0, true, true, cx);
+ // });
+ // cx.add_action(|pane: &mut Pane, _: &ActivateLastItem, cx| {
+ // pane.activate_item(pane.items.len() - 1, true, true, cx);
+ // });
+ // cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
+ // pane.activate_prev_item(true, cx);
+ // });
+ // cx.add_action(|pane: &mut Pane, _: &ActivateNextItem, cx| {
+ // pane.activate_next_item(true, cx);
+ // });
+ // cx.add_async_action(Pane::close_active_item);
+ // cx.add_async_action(Pane::close_inactive_items);
+ // cx.add_async_action(Pane::close_clean_items);
+ // cx.add_async_action(Pane::close_items_to_the_left);
+ // cx.add_async_action(Pane::close_items_to_the_right);
+ // cx.add_async_action(Pane::close_all_items);
+ // cx.add_action(|pane: &mut Pane, _: &SplitLeft, cx| pane.split(SplitDirection::Left, cx));
+ // cx.add_action(|pane: &mut Pane, _: &SplitUp, cx| pane.split(SplitDirection::Up, cx));
+ // cx.add_action(|pane: &mut Pane, _: &SplitRight, cx| pane.split(SplitDirection::Right, cx));
+ // cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx));
+}
-#[derive(Debug)]
pub enum Event {
AddItem { item: Box<dyn ItemHandle> },
ActivateItem { local: bool },
Remove,
- RemoveItem { item_id: usize },
+ RemoveItem { item_id: EntityId },
Split(SplitDirection),
ChangeItemTitle,
Focus,
@@ -159,36 +145,45 @@ pub enum Event {
ZoomOut,
}
-use crate::{
- item::{ItemHandle, WeakItemHandle},
- SplitDirection,
-};
-use collections::{HashMap, VecDeque};
-use gpui2::{Handle, ViewContext, WeakView};
-use project2::{Project, ProjectEntryId, ProjectPath};
-use std::{
- any::Any,
- cell::RefCell,
- cmp, mem,
- path::PathBuf,
- rc::Rc,
- sync::{atomic::AtomicUsize, Arc},
-};
+impl fmt::Debug for Event {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Event::AddItem { item } => f.debug_struct("AddItem").field("item", &item.id()).finish(),
+ Event::ActivateItem { local } => f
+ .debug_struct("ActivateItem")
+ .field("local", local)
+ .finish(),
+ Event::Remove => f.write_str("Remove"),
+ Event::RemoveItem { item_id } => f
+ .debug_struct("RemoveItem")
+ .field("item_id", item_id)
+ .finish(),
+ Event::Split(direction) => f
+ .debug_struct("Split")
+ .field("direction", direction)
+ .finish(),
+ Event::ChangeItemTitle => f.write_str("ChangeItemTitle"),
+ Event::Focus => f.write_str("Focus"),
+ Event::ZoomIn => f.write_str("ZoomIn"),
+ Event::ZoomOut => f.write_str("ZoomOut"),
+ }
+ }
+}
pub struct Pane {
items: Vec<Box<dyn ItemHandle>>,
- // activation_history: Vec<usize>,
- // zoomed: bool,
- // active_item_index: usize,
+ activation_history: Vec<EntityId>,
+ zoomed: bool,
+ active_item_index: usize,
// last_focused_view_by_item: HashMap<usize, AnyWeakViewHandle>,
- // autoscroll: bool,
+ autoscroll: bool,
nav_history: NavHistory,
- // toolbar: ViewHandle<Toolbar>,
+ toolbar: View<Toolbar>,
// tab_bar_context_menu: TabBarContextMenu,
// tab_context_menu: ViewHandle<ContextMenu>,
- // workspace: WeakViewHandle<Workspace>,
- project: Handle<Project>,
- // has_focus: bool,
+ workspace: WeakView<Workspace>,
+ project: Model<Project>,
+ has_focus: bool,
// can_drop: Rc<dyn Fn(&DragAndDrop<Workspace>, &WindowContext) -> bool>,
// can_split: bool,
// render_tab_bar_buttons: Rc<dyn Fn(&mut Pane, &mut ViewContext<Pane>) -> AnyElement<Pane>>,
@@ -196,18 +191,18 @@ pub struct Pane {
pub struct ItemNavHistory {
history: NavHistory,
- item: Rc<dyn WeakItemHandle>,
+ item: Arc<dyn WeakItemHandle>,
}
#[derive(Clone)]
-pub struct NavHistory(Rc<RefCell<NavHistoryState>>);
+pub struct NavHistory(Arc<Mutex<NavHistoryState>>);
struct NavHistoryState {
mode: NavigationMode,
backward_stack: VecDeque<NavigationEntry>,
forward_stack: VecDeque<NavigationEntry>,
closed_stack: VecDeque<NavigationEntry>,
- paths_by_item: HashMap<usize, (ProjectPath, Option<PathBuf>)>,
+ paths_by_item: HashMap<EntityId, (ProjectPath, Option<PathBuf>)>,
pane: WeakView<Pane>,
next_timestamp: Arc<AtomicUsize>,
}
@@ -229,14 +224,14 @@ impl Default for NavigationMode {
}
pub struct NavigationEntry {
- pub item: Rc<dyn WeakItemHandle>,
- pub data: Option<Box<dyn Any>>,
+ pub item: Arc<dyn WeakItemHandle>,
+ pub data: Option<Box<dyn Any + Send>>,
pub timestamp: usize,
}
// pub struct DraggedItem {
// pub handle: Box<dyn ItemHandle>,
-// pub pane: WeakViewHandle<Pane>,
+// pub pane: WeakView<Pane>,
// }
// pub enum ReorderBehavior {
@@ -315,114 +310,119 @@ pub struct NavigationEntry {
// .into_any_named("nav button")
// }
-impl Pane {
- // pub fn new(
- // workspace: WeakViewHandle<Workspace>,
- // project: ModelHandle<Project>,
- // next_timestamp: Arc<AtomicUsize>,
- // cx: &mut ViewContext<Self>,
- // ) -> Self {
- // let pane_view_id = cx.view_id();
- // let handle = cx.weak_handle();
- // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
- // context_menu.update(cx, |menu, _| {
- // menu.set_position_mode(OverlayPositionMode::Local)
- // });
-
- // Self {
- // items: Vec::new(),
- // activation_history: Vec::new(),
- // zoomed: false,
- // active_item_index: 0,
- // last_focused_view_by_item: Default::default(),
- // autoscroll: false,
- // nav_history: NavHistory(Rc::new(RefCell::new(NavHistoryState {
- // mode: NavigationMode::Normal,
- // backward_stack: Default::default(),
- // forward_stack: Default::default(),
- // closed_stack: Default::default(),
- // paths_by_item: Default::default(),
- // pane: handle.clone(),
- // next_timestamp,
- // }))),
- // toolbar: cx.add_view(|_| Toolbar::new()),
- // tab_bar_context_menu: TabBarContextMenu {
- // kind: TabBarContextMenuKind::New,
- // handle: context_menu,
- // },
- // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
- // workspace,
- // project,
- // has_focus: false,
- // can_drop: Rc::new(|_, _| true),
- // can_split: true,
- // render_tab_bar_buttons: Rc::new(move |pane, cx| {
- // Flex::row()
- // // New menu
- // .with_child(Self::render_tab_bar_button(
- // 0,
- // "icons/plus.svg",
- // false,
- // Some(("New...".into(), None)),
- // cx,
- // |pane, cx| pane.deploy_new_menu(cx),
- // |pane, cx| {
- // pane.tab_bar_context_menu
- // .handle
- // .update(cx, |menu, _| menu.delay_cancel())
- // },
- // pane.tab_bar_context_menu
- // .handle_if_kind(TabBarContextMenuKind::New),
- // ))
- // .with_child(Self::render_tab_bar_button(
- // 1,
- // "icons/split.svg",
- // false,
- // Some(("Split Pane".into(), None)),
- // cx,
- // |pane, cx| pane.deploy_split_menu(cx),
- // |pane, cx| {
- // pane.tab_bar_context_menu
- // .handle
- // .update(cx, |menu, _| menu.delay_cancel())
- // },
- // pane.tab_bar_context_menu
- // .handle_if_kind(TabBarContextMenuKind::Split),
- // ))
- // .with_child({
- // let icon_path;
- // let tooltip_label;
- // if pane.is_zoomed() {
- // icon_path = "icons/minimize.svg";
- // tooltip_label = "Zoom In";
- // } else {
- // icon_path = "icons/maximize.svg";
- // tooltip_label = "Zoom In";
- // }
+impl EventEmitter for Pane {
+ type Event = Event;
+}
- // Pane::render_tab_bar_button(
- // 2,
- // icon_path,
- // pane.is_zoomed(),
- // Some((tooltip_label, Some(Box::new(ToggleZoom)))),
- // cx,
- // move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
- // move |_, _| {},
- // None,
- // )
- // })
- // .into_any()
- // }),
- // }
- // }
+impl Pane {
+ pub fn new(
+ workspace: WeakView<Workspace>,
+ project: Model<Project>,
+ next_timestamp: Arc<AtomicUsize>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ // todo!("context menu")
+ // let pane_view_id = cx.view_id();
+ // let context_menu = cx.add_view(|cx| ContextMenu::new(pane_view_id, cx));
+ // context_menu.update(cx, |menu, _| {
+ // menu.set_position_mode(OverlayPositionMode::Local)
+ // });
+
+ let handle = cx.view().downgrade();
+ Self {
+ items: Vec::new(),
+ activation_history: Vec::new(),
+ zoomed: false,
+ active_item_index: 0,
+ // last_focused_view_by_item: Default::default(),
+ autoscroll: false,
+ nav_history: NavHistory(Arc::new(Mutex::new(NavHistoryState {
+ mode: NavigationMode::Normal,
+ backward_stack: Default::default(),
+ forward_stack: Default::default(),
+ closed_stack: Default::default(),
+ paths_by_item: Default::default(),
+ pane: handle.clone(),
+ next_timestamp,
+ }))),
+ toolbar: cx.build_view(|_| Toolbar::new()),
+ // tab_bar_context_menu: TabBarContextMenu {
+ // kind: TabBarContextMenuKind::New,
+ // handle: context_menu,
+ // },
+ // tab_context_menu: cx.add_view(|cx| ContextMenu::new(pane_view_id, cx)),
+ workspace,
+ project,
+ has_focus: false,
+ // can_drop: Rc::new(|_, _| true),
+ // can_split: true,
+ // render_tab_bar_buttons: Rc::new(move |pane, cx| {
+ // Flex::row()
+ // // New menu
+ // .with_child(Self::render_tab_bar_button(
+ // 0,
+ // "icons/plus.svg",
+ // false,
+ // Some(("New...".into(), None)),
+ // cx,
+ // |pane, cx| pane.deploy_new_menu(cx),
+ // |pane, cx| {
+ // pane.tab_bar_context_menu
+ // .handle
+ // .update(cx, |menu, _| menu.delay_cancel())
+ // },
+ // pane.tab_bar_context_menu
+ // .handle_if_kind(TabBarContextMenuKind::New),
+ // ))
+ // .with_child(Self::render_tab_bar_button(
+ // 1,
+ // "icons/split.svg",
+ // false,
+ // Some(("Split Pane".into(), None)),
+ // cx,
+ // |pane, cx| pane.deploy_split_menu(cx),
+ // |pane, cx| {
+ // pane.tab_bar_context_menu
+ // .handle
+ // .update(cx, |menu, _| menu.delay_cancel())
+ // },
+ // pane.tab_bar_context_menu
+ // .handle_if_kind(TabBarContextMenuKind::Split),
+ // ))
+ // .with_child({
+ // let icon_path;
+ // let tooltip_label;
+ // if pane.is_zoomed() {
+ // icon_path = "icons/minimize.svg";
+ // tooltip_label = "Zoom In";
+ // } else {
+ // icon_path = "icons/maximize.svg";
+ // tooltip_label = "Zoom In";
+ // }
+
+ // Pane::render_tab_bar_button(
+ // 2,
+ // icon_path,
+ // pane.is_zoomed(),
+ // Some((tooltip_label, Some(Box::new(ToggleZoom)))),
+ // cx,
+ // move |pane, cx| pane.toggle_zoom(&Default::default(), cx),
+ // move |_, _| {},
+ // None,
+ // )
+ // })
+ // .into_any()
+ // }),
+ }
+ }
- // pub(crate) fn workspace(&self) -> &WeakViewHandle<Workspace> {
+ // pub(crate) fn workspace(&self) -> &WeakView<Workspace> {
// &self.workspace
// }
- // pub fn has_focus(&self) -> bool {
- // self.has_focus
- // }
+ pub fn has_focus(&self) -> bool {
+ self.has_focus
+ }
// pub fn active_item_index(&self) -> usize {
// self.active_item_index
@@ -455,40 +455,40 @@ impl Pane {
// cx.notify();
// }
- // pub fn nav_history_for_item<T: Item>(&self, item: &ViewHandle<T>) -> ItemNavHistory {
- // ItemNavHistory {
- // history: self.nav_history.clone(),
- // item: Rc::new(item.downgrade()),
- // }
- // }
+ pub fn nav_history_for_item<T: Item>(&self, item: &View<T>) -> ItemNavHistory {
+ ItemNavHistory {
+ history: self.nav_history.clone(),
+ item: Arc::new(item.downgrade()),
+ }
+ }
- // pub fn nav_history(&self) -> &NavHistory {
- // &self.nav_history
- // }
+ pub fn nav_history(&self) -> &NavHistory {
+ &self.nav_history
+ }
- // pub fn nav_history_mut(&mut self) -> &mut NavHistory {
- // &mut self.nav_history
- // }
+ pub fn nav_history_mut(&mut self) -> &mut NavHistory {
+ &mut self.nav_history
+ }
- // pub fn disable_history(&mut self) {
- // self.nav_history.disable();
- // }
+ pub fn disable_history(&mut self) {
+ self.nav_history.disable();
+ }
- // pub fn enable_history(&mut self) {
- // self.nav_history.enable();
- // }
+ pub fn enable_history(&mut self) {
+ self.nav_history.enable();
+ }
- // pub fn can_navigate_backward(&self) -> bool {
- // !self.nav_history.0.borrow().backward_stack.is_empty()
- // }
+ pub fn can_navigate_backward(&self) -> bool {
+ !self.nav_history.0.lock().backward_stack.is_empty()
+ }
- // pub fn can_navigate_forward(&self) -> bool {
- // !self.nav_history.0.borrow().forward_stack.is_empty()
- // }
+ pub fn can_navigate_forward(&self) -> bool {
+ !self.nav_history.0.lock().forward_stack.is_empty()
+ }
- // fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
- // self.toolbar.update(cx, |_, cx| cx.notify());
- // }
+ fn history_updated(&mut self, cx: &mut ViewContext<Self>) {
+ self.toolbar.update(cx, |_, cx| cx.notify());
+ }
pub(crate) fn open_item(
&mut self,
@@ -532,7 +532,7 @@ impl Pane {
let abs_path = project.absolute_path(&project_path, cx);
self.nav_history
.0
- .borrow_mut()
+ .lock()
.paths_by_item
.insert(item.id(), (project_path, abs_path));
}
@@ -615,13 +615,13 @@ impl Pane {
cx.emit(Event::AddItem { item });
}
- // pub fn items_len(&self) -> usize {
- // self.items.len()
- // }
+ pub fn items_len(&self) -> usize {
+ self.items.len()
+ }
- // pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
- // self.items.iter()
- // }
+ pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> + DoubleEndedIterator {
+ self.items.iter()
+ }
// pub fn items_of_type<T: View>(&self) -> impl '_ + Iterator<Item = ViewHandle<T>> {
// self.items
@@ -629,9 +629,9 @@ impl Pane {
// .filter_map(|item| item.as_any().clone().downcast())
// }
- // pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
- // self.items.get(self.active_item_index).cloned()
- // }
+ pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
+ self.items.get(self.active_item_index).cloned()
+ }
// pub fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Vector2F> {
// self.items
@@ -653,9 +653,9 @@ impl Pane {
// })
// }
- // pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
- // self.items.iter().position(|i| i.id() == item.id())
- // }
+ pub fn index_for_item(&self, item: &dyn ItemHandle) -> Option<usize> {
+ self.items.iter().position(|i| i.id() == item.id())
+ }
// pub fn toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
// // Potentially warn the user of the new keybinding
@@ -751,448 +751,445 @@ impl Pane {
// ))
// }
- // pub fn close_item_by_id(
- // &mut self,
- // item_id_to_close: usize,
- // save_intent: SaveIntent,
- // cx: &mut ViewContext<Self>,
- // ) -> Task<Result<()>> {
- // self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
+ pub fn close_item_by_id(
+ &mut self,
+ item_id_to_close: EntityId,
+ save_intent: SaveIntent,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Result<()>> {
+ self.close_items(cx, save_intent, move |view_id| view_id == item_id_to_close)
+ }
+
+ // pub fn close_inactive_items(
+ // &mut self,
+ // _: &CloseInactiveItems,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
// }
- // pub fn close_inactive_items(
- // &mut self,
- // _: &CloseInactiveItems,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // if self.items.is_empty() {
- // return None;
- // }
+ // let active_item_id = self.items[self.active_item_index].id();
+ // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_id != active_item_id
+ // }))
+ // }
- // let active_item_id = self.items[self.active_item_index].id();
- // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
- // item_id != active_item_id
- // }))
- // }
+ // pub fn close_clean_items(
+ // &mut self,
+ // _: &CloseCleanItems,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // let item_ids: Vec<_> = self
+ // .items()
+ // .filter(|item| !item.is_dirty(cx))
+ // .map(|item| item.id())
+ // .collect();
+ // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_ids.contains(&item_id)
+ // }))
+ // }
- // pub fn close_clean_items(
- // &mut self,
- // _: &CloseCleanItems,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // let item_ids: Vec<_> = self
- // .items()
- // .filter(|item| !item.is_dirty(cx))
- // .map(|item| item.id())
- // .collect();
- // Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
- // item_ids.contains(&item_id)
- // }))
+ // pub fn close_items_to_the_left(
+ // &mut self,
+ // _: &CloseItemsToTheLeft,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
// }
+ // let active_item_id = self.items[self.active_item_index].id();
+ // Some(self.close_items_to_the_left_by_id(active_item_id, cx))
+ // }
- // pub fn close_items_to_the_left(
- // &mut self,
- // _: &CloseItemsToTheLeft,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // if self.items.is_empty() {
- // return None;
- // }
- // let active_item_id = self.items[self.active_item_index].id();
- // Some(self.close_items_to_the_left_by_id(active_item_id, cx))
- // }
+ // pub fn close_items_to_the_left_by_id(
+ // &mut self,
+ // item_id: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // let item_ids: Vec<_> = self
+ // .items()
+ // .take_while(|item| item.id() != item_id)
+ // .map(|item| item.id())
+ // .collect();
+ // self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_ids.contains(&item_id)
+ // })
+ // }
- // pub fn close_items_to_the_left_by_id(
- // &mut self,
- // item_id: usize,
- // cx: &mut ViewContext<Self>,
- // ) -> Task<Result<()>> {
- // let item_ids: Vec<_> = self
- // .items()
- // .take_while(|item| item.id() != item_id)
- // .map(|item| item.id())
- // .collect();
- // self.close_items(cx, SaveIntent::Close, move |item_id| {
- // item_ids.contains(&item_id)
- // })
+ // pub fn close_items_to_the_right(
+ // &mut self,
+ // _: &CloseItemsToTheRight,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
// }
+ // let active_item_id = self.items[self.active_item_index].id();
+ // Some(self.close_items_to_the_right_by_id(active_item_id, cx))
+ // }
- // pub fn close_items_to_the_right(
- // &mut self,
- // _: &CloseItemsToTheRight,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // if self.items.is_empty() {
- // return None;
- // }
- // let active_item_id = self.items[self.active_item_index].id();
- // Some(self.close_items_to_the_right_by_id(active_item_id, cx))
- // }
+ // pub fn close_items_to_the_right_by_id(
+ // &mut self,
+ // item_id: usize,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Task<Result<()>> {
+ // let item_ids: Vec<_> = self
+ // .items()
+ // .rev()
+ // .take_while(|item| item.id() != item_id)
+ // .map(|item| item.id())
+ // .collect();
+ // self.close_items(cx, SaveIntent::Close, move |item_id| {
+ // item_ids.contains(&item_id)
+ // })
+ // }
- // pub fn close_items_to_the_right_by_id(
- // &mut self,
- // item_id: usize,
- // cx: &mut ViewContext<Self>,
- // ) -> Task<Result<()>> {
- // let item_ids: Vec<_> = self
- // .items()
- // .rev()
- // .take_while(|item| item.id() != item_id)
- // .map(|item| item.id())
- // .collect();
- // self.close_items(cx, SaveIntent::Close, move |item_id| {
- // item_ids.contains(&item_id)
- // })
+ // pub fn close_all_items(
+ // &mut self,
+ // action: &CloseAllItems,
+ // cx: &mut ViewContext<Self>,
+ // ) -> Option<Task<Result<()>>> {
+ // if self.items.is_empty() {
+ // return None;
// }
- // pub fn close_all_items(
- // &mut self,
- // action: &CloseAllItems,
- // cx: &mut ViewContext<Self>,
- // ) -> Option<Task<Result<()>>> {
- // if self.items.is_empty() {
- // return None;
- // }
+ // Some(
+ // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
+ // true
+ // }),
+ // )
+ // }
- // Some(
- // self.close_items(cx, action.save_intent.unwrap_or(SaveIntent::Close), |_| {
- // true
- // }),
- // )
- // }
+ pub(super) fn file_names_for_prompt(
+ items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
+ all_dirty_items: usize,
+ cx: &AppContext,
+ ) -> String {
+ /// Quantity of item paths displayed in prompt prior to cutoff..
+ const FILE_NAMES_CUTOFF_POINT: usize = 10;
+ let mut file_names: Vec<_> = items
+ .filter_map(|item| {
+ item.project_path(cx).and_then(|project_path| {
+ project_path
+ .path
+ .file_name()
+ .and_then(|name| name.to_str().map(ToOwned::to_owned))
+ })
+ })
+ .take(FILE_NAMES_CUTOFF_POINT)
+ .collect();
+ let should_display_followup_text =
+ all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
+ if should_display_followup_text {
+ let not_shown_files = all_dirty_items - file_names.len();
+ if not_shown_files == 1 {
+ file_names.push(".. 1 file not shown".into());
+ } else {
+ file_names.push(format!(".. {} files not shown", not_shown_files).into());
+ }
+ }
+ let file_names = file_names.join("\n");
+ format!(
+ "Do you want to save changes to the following {} files?\n{file_names}",
+ all_dirty_items
+ )
+ }
- // pub(super) fn file_names_for_prompt(
- // items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
- // all_dirty_items: usize,
- // cx: &AppContext,
- // ) -> String {
- // /// Quantity of item paths displayed in prompt prior to cutoff..
- // const FILE_NAMES_CUTOFF_POINT: usize = 10;
- // let mut file_names: Vec<_> = items
- // .filter_map(|item| {
- // item.project_path(cx).and_then(|project_path| {
- // project_path
- // .path
- // .file_name()
- // .and_then(|name| name.to_str().map(ToOwned::to_owned))
- // })
- // })
- // .take(FILE_NAMES_CUTOFF_POINT)
- // .collect();
- // let should_display_followup_text =
- // all_dirty_items > FILE_NAMES_CUTOFF_POINT || file_names.len() != all_dirty_items;
- // if should_display_followup_text {
- // let not_shown_files = all_dirty_items - file_names.len();
- // if not_shown_files == 1 {
- // file_names.push(".. 1 file not shown".into());
- // } else {
- // file_names.push(format!(".. {} files not shown", not_shown_files).into());
- // }
- // }
- // let file_names = file_names.join("\n");
- // format!(
- // "Do you want to save changes to the following {} files?\n{file_names}",
- // all_dirty_items
- // )
- // }
+ pub fn close_items(
+ &mut self,
+ cx: &mut ViewContext<Pane>,
+ mut save_intent: SaveIntent,
+ should_close: impl 'static + Fn(EntityId) -> bool,
+ ) -> Task<Result<()>> {
+ // Find the items to close.
+ let mut items_to_close = Vec::new();
+ let mut dirty_items = Vec::new();
+ for item in &self.items {
+ if should_close(item.id()) {
+ items_to_close.push(item.boxed_clone());
+ if item.is_dirty(cx) {
+ dirty_items.push(item.boxed_clone());
+ }
+ }
+ }
- // pub fn close_items(
- // &mut self,
- // cx: &mut ViewContext<Pane>,
- // mut save_intent: SaveIntent,
- // should_close: impl 'static + Fn(usize) -> bool,
- // ) -> Task<Result<()>> {
- // // Find the items to close.
- // let mut items_to_close = Vec::new();
- // let mut dirty_items = Vec::new();
- // for item in &self.items {
- // if should_close(item.id()) {
- // items_to_close.push(item.boxed_clone());
- // if item.is_dirty(cx) {
- // dirty_items.push(item.boxed_clone());
- // }
- // }
- // }
+ // If a buffer is open both in a singleton editor and in a multibuffer, make sure
+ // to focus the singleton buffer when prompting to save that buffer, as opposed
+ // to focusing the multibuffer, because this gives the user a more clear idea
+ // of what content they would be saving.
+ items_to_close.sort_by_key(|item| !item.is_singleton(cx));
+
+ let workspace = self.workspace.clone();
+ cx.spawn(|pane, mut cx| async move {
+ if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
+ let answer = pane.update(&mut cx, |_, cx| {
+ let prompt =
+ Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
+ cx.prompt(
+ PromptLevel::Warning,
+ &prompt,
+ &["Save all", "Discard all", "Cancel"],
+ )
+ })?;
+ match answer.await {
+ Ok(0) => save_intent = SaveIntent::SaveAll,
+ Ok(1) => save_intent = SaveIntent::Skip,
+ _ => {}
+ }
+ }
+ let mut saved_project_items_ids = HashSet::default();
+ for item in items_to_close.clone() {
+ // Find the item's current index and its set of project item models. Avoid
+ // storing these in advance, in case they have changed since this task
+ // was started.
+ let (item_ix, mut project_item_ids) = pane.update(&mut cx, |pane, cx| {
+ (pane.index_for_item(&*item), item.project_item_model_ids(cx))
+ })?;
+ let item_ix = if let Some(ix) = item_ix {
+ ix
+ } else {
+ continue;
+ };
+
+ // Check if this view has any project items that are not open anywhere else
+ // in the workspace, AND that the user has not already been prompted to save.
+ // If there are any such project entries, prompt the user to save this item.
+ let project = workspace.update(&mut cx, |workspace, cx| {
+ for item in workspace.items(cx) {
+ if !items_to_close
+ .iter()
+ .any(|item_to_close| item_to_close.id() == item.id())
+ {
+ let other_project_item_ids = item.project_item_model_ids(cx);
+ project_item_ids.retain(|id| !other_project_item_ids.contains(id));
+ }
+ }
+ workspace.project().clone()
+ })?;
+ let should_save = project_item_ids
+ .iter()
+ .any(|id| saved_project_items_ids.insert(*id));
+
+ if should_save
+ && !Self::save_item(
+ project.clone(),
+ &pane,
+ item_ix,
+ &*item,
+ save_intent,
+ &mut cx,
+ )
+ .await?
+ {
+ break;
+ }
- // // If a buffer is open both in a singleton editor and in a multibuffer, make sure
- // // to focus the singleton buffer when prompting to save that buffer, as opposed
- // // to focusing the multibuffer, because this gives the user a more clear idea
- // // of what content they would be saving.
- // items_to_close.sort_by_key(|item| !item.is_singleton(cx));
-
- // let workspace = self.workspace.clone();
- // cx.spawn(|pane, mut cx| async move {
- // if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
- // let mut answer = pane.update(&mut cx, |_, cx| {
- // let prompt =
- // Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
- // cx.prompt(
- // PromptLevel::Warning,
- // &prompt,
- // &["Save all", "Discard all", "Cancel"],
- // )
- // })?;
- // match answer.next().await {
- // Some(0) => save_intent = SaveIntent::SaveAll,
- // Some(1) => save_intent = SaveIntent::Skip,
- // _ => {}
- // }
- // }
- // let mut saved_project_items_ids = HashSet::default();
- // for item in items_to_close.clone() {
- // // Find the item's current index and its set of project item models. Avoid
- // // storing these in advance, in case they have changed since this task
- // // was started.
- // let (item_ix, mut project_item_ids) = pane.read_with(&cx, |pane, cx| {
- // (pane.index_for_item(&*item), item.project_item_model_ids(cx))
- // })?;
- // let item_ix = if let Some(ix) = item_ix {
- // ix
- // } else {
- // continue;
- // };
-
- // // Check if this view has any project items that are not open anywhere else
- // // in the workspace, AND that the user has not already been prompted to save.
- // // If there are any such project entries, prompt the user to save this item.
- // let project = workspace.read_with(&cx, |workspace, cx| {
- // for item in workspace.items(cx) {
- // if !items_to_close
- // .iter()
- // .any(|item_to_close| item_to_close.id() == item.id())
- // {
- // let other_project_item_ids = item.project_item_model_ids(cx);
- // project_item_ids.retain(|id| !other_project_item_ids.contains(id));
- // }
- // }
- // workspace.project().clone()
- // })?;
- // let should_save = project_item_ids
- // .iter()
- // .any(|id| saved_project_items_ids.insert(*id));
-
- // if should_save
- // && !Self::save_item(
- // project.clone(),
- // &pane,
- // item_ix,
- // &*item,
- // save_intent,
- // &mut cx,
- // )
- // .await?
- // {
- // break;
- // }
+ // Remove the item from the pane.
+ pane.update(&mut cx, |pane, cx| {
+ if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
+ pane.remove_item(item_ix, false, cx);
+ }
+ })?;
+ }
- // // Remove the item from the pane.
- // pane.update(&mut cx, |pane, cx| {
- // if let Some(item_ix) = pane.items.iter().position(|i| i.id() == item.id()) {
- // pane.remove_item(item_ix, false, cx);
- // }
- // })?;
- // }
+ pane.update(&mut cx, |_, cx| cx.notify())?;
+ Ok(())
+ })
+ }
- // pane.update(&mut cx, |_, cx| cx.notify())?;
- // Ok(())
- // })
- // }
+ pub fn remove_item(
+ &mut self,
+ item_index: usize,
+ activate_pane: bool,
+ cx: &mut ViewContext<Self>,
+ ) {
+ self.activation_history
+ .retain(|&history_entry| history_entry != self.items[item_index].id());
+
+ if item_index == self.active_item_index {
+ let index_to_activate = self
+ .activation_history
+ .pop()
+ .and_then(|last_activated_item| {
+ self.items.iter().enumerate().find_map(|(index, item)| {
+ (item.id() == last_activated_item).then_some(index)
+ })
+ })
+ // We didn't have a valid activation history entry, so fallback
+ // to activating the item to the left
+ .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
- // pub fn remove_item(
- // &mut self,
- // item_index: usize,
- // activate_pane: bool,
- // cx: &mut ViewContext<Self>,
- // ) {
- // self.activation_history
- // .retain(|&history_entry| history_entry != self.items[item_index].id());
-
- // if item_index == self.active_item_index {
- // let index_to_activate = self
- // .activation_history
- // .pop()
- // .and_then(|last_activated_item| {
- // self.items.iter().enumerate().find_map(|(index, item)| {
- // (item.id() == last_activated_item).then_some(index)
- // })
- // })
- // // We didn't have a valid activation history entry, so fallback
- // // to activating the item to the left
- // .unwrap_or_else(|| item_index.min(self.items.len()).saturating_sub(1));
-
- // let should_activate = activate_pane || self.has_focus;
- // self.activate_item(index_to_activate, should_activate, should_activate, cx);
- // }
+ let should_activate = activate_pane || self.has_focus;
+ self.activate_item(index_to_activate, should_activate, should_activate, cx);
+ }
- // let item = self.items.remove(item_index);
+ let item = self.items.remove(item_index);
- // cx.emit(Event::RemoveItem { item_id: item.id() });
- // if self.items.is_empty() {
- // item.deactivated(cx);
- // self.update_toolbar(cx);
- // cx.emit(Event::Remove);
- // }
+ cx.emit(Event::RemoveItem { item_id: item.id() });
+ if self.items.is_empty() {
+ item.deactivated(cx);
+ self.update_toolbar(cx);
+ cx.emit(Event::Remove);
+ }
- // if item_index < self.active_item_index {
- // self.active_item_index -= 1;
- // }
+ if item_index < self.active_item_index {
+ self.active_item_index -= 1;
+ }
- // self.nav_history.set_mode(NavigationMode::ClosingItem);
- // item.deactivated(cx);
- // self.nav_history.set_mode(NavigationMode::Normal);
-
- // if let Some(path) = item.project_path(cx) {
- // let abs_path = self
- // .nav_history
- // .0
- // .borrow()
- // .paths_by_item
- // .get(&item.id())
- // .and_then(|(_, abs_path)| abs_path.clone());
-
- // self.nav_history
- // .0
- // .borrow_mut()
- // .paths_by_item
- // .insert(item.id(), (path, abs_path));
- // } else {
- // self.nav_history
- // .0
- // .borrow_mut()
- // .paths_by_item
- // .remove(&item.id());
- // }
+ self.nav_history.set_mode(NavigationMode::ClosingItem);
+ item.deactivated(cx);
+ self.nav_history.set_mode(NavigationMode::Normal);
+
+ if let Some(path) = item.project_path(cx) {
+ let abs_path = self
+ .nav_history
+ .0
+ .lock()
+ .paths_by_item
+ .get(&item.id())
+ .and_then(|(_, abs_path)| abs_path.clone());
+
+ self.nav_history
+ .0
+ .lock()
+ .paths_by_item
+ .insert(item.id(), (path, abs_path));
+ } else {
+ self.nav_history.0.lock().paths_by_item.remove(&item.id());
+ }
- // if self.items.is_empty() && self.zoomed {
- // cx.emit(Event::ZoomOut);
- // }
+ if self.items.is_empty() && self.zoomed {
+ cx.emit(Event::ZoomOut);
+ }
- // cx.notify();
- // }
+ cx.notify();
+ }
- // pub async fn save_item(
- // project: ModelHandle<Project>,
- // pane: &WeakViewHandle<Pane>,
- // item_ix: usize,
- // item: &dyn ItemHandle,
- // save_intent: SaveIntent,
- // cx: &mut AsyncAppContext,
- // ) -> Result<bool> {
- // const CONFLICT_MESSAGE: &str =
- // "This file has changed on disk since you started editing it. Do you want to overwrite it?";
-
- // if save_intent == SaveIntent::Skip {
- // return Ok(true);
- // }
+ pub async fn save_item(
+ project: Model<Project>,
+ pane: &WeakView<Pane>,
+ item_ix: usize,
+ item: &dyn ItemHandle,
+ save_intent: SaveIntent,
+ cx: &mut AsyncWindowContext,
+ ) -> Result<bool> {
+ const CONFLICT_MESSAGE: &str =
+ "This file has changed on disk since you started editing it. Do you want to overwrite it?";
+
+ if save_intent == SaveIntent::Skip {
+ return Ok(true);
+ }
- // let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.read(|cx| {
- // (
- // item.has_conflict(cx),
- // item.is_dirty(cx),
- // item.can_save(cx),
- // item.is_singleton(cx),
- // )
- // });
+ let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|_, cx| {
+ (
+ item.has_conflict(cx),
+ item.is_dirty(cx),
+ item.can_save(cx),
+ item.is_singleton(cx),
+ )
+ })?;
- // // when saving a single buffer, we ignore whether or not it's dirty.
- // if save_intent == SaveIntent::Save {
- // is_dirty = true;
- // }
+ // when saving a single buffer, we ignore whether or not it's dirty.
+ if save_intent == SaveIntent::Save {
+ is_dirty = true;
+ }
- // if save_intent == SaveIntent::SaveAs {
- // is_dirty = true;
- // has_conflict = false;
- // can_save = false;
- // }
+ if save_intent == SaveIntent::SaveAs {
+ is_dirty = true;
+ has_conflict = false;
+ can_save = false;
+ }
- // if save_intent == SaveIntent::Overwrite {
- // has_conflict = false;
- // }
+ if save_intent == SaveIntent::Overwrite {
+ has_conflict = false;
+ }
- // if has_conflict && can_save {
- // let mut answer = pane.update(cx, |pane, cx| {
- // pane.activate_item(item_ix, true, true, cx);
- // cx.prompt(
- // PromptLevel::Warning,
- // CONFLICT_MESSAGE,
- // &["Overwrite", "Discard", "Cancel"],
- // )
- // })?;
- // match answer.next().await {
- // Some(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
- // Some(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
- // _ => return Ok(false),
- // }
- // } else if is_dirty && (can_save || can_save_as) {
- // if save_intent == SaveIntent::Close {
- // let will_autosave = cx.read(|cx| {
- // matches!(
- // settings::get::<WorkspaceSettings>(cx).autosave,
- // AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
- // ) && Self::can_autosave_item(&*item, cx)
- // });
- // if !will_autosave {
- // let mut answer = pane.update(cx, |pane, cx| {
- // pane.activate_item(item_ix, true, true, cx);
- // let prompt = dirty_message_for(item.project_path(cx));
- // cx.prompt(
- // PromptLevel::Warning,
- // &prompt,
- // &["Save", "Don't Save", "Cancel"],
- // )
- // })?;
- // match answer.next().await {
- // Some(0) => {}
- // Some(1) => return Ok(true), // Don't save his file
- // _ => return Ok(false), // Cancel
- // }
- // }
- // }
+ if has_conflict && can_save {
+ let answer = pane.update(cx, |pane, cx| {
+ pane.activate_item(item_ix, true, true, cx);
+ cx.prompt(
+ PromptLevel::Warning,
+ CONFLICT_MESSAGE,
+ &["Overwrite", "Discard", "Cancel"],
+ )
+ })?;
+ match answer.await {
+ Ok(0) => pane.update(cx, |_, cx| item.save(project, cx))?.await?,
+ Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
+ _ => return Ok(false),
+ }
+ } else if is_dirty && (can_save || can_save_as) {
+ if save_intent == SaveIntent::Close {
+ let will_autosave = cx.update(|_, cx| {
+ matches!(
+ WorkspaceSettings::get_global(cx).autosave,
+ AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
+ ) && Self::can_autosave_item(&*item, cx)
+ })?;
+ if !will_autosave {
+ let answer = pane.update(cx, |pane, cx| {
+ pane.activate_item(item_ix, true, true, cx);
+ let prompt = dirty_message_for(item.project_path(cx));
+ cx.prompt(
+ PromptLevel::Warning,
+ &prompt,
+ &["Save", "Don't Save", "Cancel"],
+ )
+ })?;
+ match answer.await {
+ Ok(0) => {}
+ Ok(1) => return Ok(true), // Don't save this file
+ _ => return Ok(false), // Cancel
+ }
+ }
+ }
- // if can_save {
- // pane.update(cx, |_, cx| item.save(project, cx))?.await?;
- // } else if can_save_as {
- // let start_abs_path = project
- // .read_with(cx, |project, cx| {
- // let worktree = project.visible_worktrees(cx).next()?;
- // Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
- // })
- // .unwrap_or_else(|| Path::new("").into());
-
- // let mut abs_path = cx.update(|cx| cx.prompt_for_new_path(&start_abs_path));
- // if let Some(abs_path) = abs_path.next().await.flatten() {
- // pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
- // .await?;
- // } else {
- // return Ok(false);
- // }
- // }
- // }
- // Ok(true)
- // }
+ if can_save {
+ pane.update(cx, |_, cx| item.save(project, cx))?.await?;
+ } else if can_save_as {
+ let start_abs_path = project
+ .update(cx, |project, cx| {
+ let worktree = project.visible_worktrees(cx).next()?;
+ Some(worktree.read(cx).as_local()?.abs_path().to_path_buf())
+ })?
+ .unwrap_or_else(|| Path::new("").into());
+
+ let abs_path = cx.update(|_, cx| cx.prompt_for_new_path(&start_abs_path))?;
+ if let Some(abs_path) = abs_path.await.ok().flatten() {
+ pane.update(cx, |_, cx| item.save_as(project, abs_path, cx))?
+ .await?;
+ } else {
+ return Ok(false);
+ }
+ }
+ }
+ Ok(true)
+ }
- // fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
- // let is_deleted = item.project_entry_ids(cx).is_empty();
- // item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
- // }
+ fn can_autosave_item(item: &dyn ItemHandle, cx: &AppContext) -> bool {
+ let is_deleted = item.project_entry_ids(cx).is_empty();
+ item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
+ }
- // pub fn autosave_item(
- // item: &dyn ItemHandle,
- // project: ModelHandle<Project>,
- // cx: &mut WindowContext,
- // ) -> Task<Result<()>> {
- // if Self::can_autosave_item(item, cx) {
- // item.save(project, cx)
- // } else {
- // Task::ready(Ok(()))
- // }
- // }
+ pub fn autosave_item(
+ item: &dyn ItemHandle,
+ project: Model<Project>,
+ cx: &mut WindowContext,
+ ) -> Task<Result<()>> {
+ if Self::can_autosave_item(item, cx) {
+ item.save(project, cx)
+ } else {
+ Task::ready(Ok(()))
+ }
+ }
- // pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
- // if let Some(active_item) = self.active_item() {
- // cx.focus(active_item.as_any());
- // }
- // }
+ pub fn focus_active_item(&mut self, cx: &mut ViewContext<Self>) {
+ todo!();
+ // if let Some(active_item) = self.active_item() {
+ // cx.focus(active_item.as_any());
+ // }
+ }
// pub fn split(&mut self, direction: SplitDirection, cx: &mut ViewContext<Self>) {
// cx.emit(Event::Split(direction));
@@ -0,0 +1,239 @@
+use super::DraggedItem;
+use crate::{Pane, SplitDirection, Workspace};
+use gpui2::{
+ color::Color,
+ elements::{Canvas, MouseEventHandler, ParentElement, Stack},
+ geometry::{rect::RectF, vector::Vector2F},
+ platform::MouseButton,
+ scene::MouseUp,
+ AppContext, Element, EventContext, MouseState, Quad, ViewContext, WeakViewHandle,
+};
+use project2::ProjectEntryId;
+
+pub fn dragged_item_receiver<Tag, D, F>(
+ pane: &Pane,
+ region_id: usize,
+ drop_index: usize,
+ allow_same_pane: bool,
+ split_margin: Option<f32>,
+ cx: &mut ViewContext<Pane>,
+ render_child: F,
+) -> MouseEventHandler<Pane>
+where
+ Tag: 'static,
+ D: Element<Pane>,
+ F: FnOnce(&mut MouseState, &mut ViewContext<Pane>) -> D,
+{
+ let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
+ let drag_position = if (pane.can_drop)(drag_and_drop, cx) {
+ drag_and_drop
+ .currently_dragged::<DraggedItem>(cx.window())
+ .map(|(drag_position, _)| drag_position)
+ .or_else(|| {
+ drag_and_drop
+ .currently_dragged::<ProjectEntryId>(cx.window())
+ .map(|(drag_position, _)| drag_position)
+ })
+ } else {
+ None
+ };
+
+ let mut handler = MouseEventHandler::above::<Tag, _>(region_id, cx, |state, cx| {
+ // Observing hovered will cause a render when the mouse enters regardless
+ // of if mouse position was accessed before
+ let drag_position = if state.dragging() {
+ drag_position
+ } else {
+ None
+ };
+ Stack::new()
+ .with_child(render_child(state, cx))
+ .with_children(drag_position.map(|drag_position| {
+ Canvas::new(move |bounds, _, _, cx| {
+ if bounds.contains_point(drag_position) {
+ let overlay_region = split_margin
+ .and_then(|split_margin| {
+ drop_split_direction(drag_position, bounds, split_margin)
+ .map(|dir| (dir, split_margin))
+ })
+ .map(|(dir, margin)| dir.along_edge(bounds, margin))
+ .unwrap_or(bounds);
+
+ cx.scene().push_stacking_context(None, None);
+ let background = overlay_color(cx);
+ cx.scene().push_quad(Quad {
+ bounds: overlay_region,
+ background: Some(background),
+ border: Default::default(),
+ corner_radii: Default::default(),
+ });
+ cx.scene().pop_stacking_context();
+ }
+ })
+ }))
+ });
+
+ if drag_position.is_some() {
+ handler = handler
+ .on_up(MouseButton::Left, {
+ move |event, pane, cx| {
+ let workspace = pane.workspace.clone();
+ let pane = cx.weak_handle();
+ handle_dropped_item(
+ event,
+ workspace,
+ &pane,
+ drop_index,
+ allow_same_pane,
+ split_margin,
+ cx,
+ );
+ cx.notify();
+ }
+ })
+ .on_move(|_, _, cx| {
+ let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
+
+ if drag_and_drop
+ .currently_dragged::<DraggedItem>(cx.window())
+ .is_some()
+ || drag_and_drop
+ .currently_dragged::<ProjectEntryId>(cx.window())
+ .is_some()
+ {
+ cx.notify();
+ } else {
+ cx.propagate_event();
+ }
+ })
+ }
+
+ handler
+}
+
+pub fn handle_dropped_item<V: 'static>(
+ event: MouseUp,
+ workspace: WeakViewHandle<Workspace>,
+ pane: &WeakViewHandle<Pane>,
+ index: usize,
+ allow_same_pane: bool,
+ split_margin: Option<f32>,
+ cx: &mut EventContext<V>,
+) {
+ enum Action {
+ Move(WeakViewHandle<Pane>, usize),
+ Open(ProjectEntryId),
+ }
+ let drag_and_drop = cx.global::<DragAndDrop<Workspace>>();
+ let action = if let Some((_, dragged_item)) =
+ drag_and_drop.currently_dragged::<DraggedItem>(cx.window())
+ {
+ Action::Move(dragged_item.pane.clone(), dragged_item.handle.id())
+ } else if let Some((_, project_entry)) =
+ drag_and_drop.currently_dragged::<ProjectEntryId>(cx.window())
+ {
+ Action::Open(*project_entry)
+ } else {
+ cx.propagate_event();
+ return;
+ };
+
+ if let Some(split_direction) =
+ split_margin.and_then(|margin| drop_split_direction(event.position, event.region, margin))
+ {
+ let pane_to_split = pane.clone();
+ match action {
+ Action::Move(from, item_id_to_move) => {
+ cx.window_context().defer(move |cx| {
+ if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ workspace.split_pane_with_item(
+ pane_to_split,
+ split_direction,
+ from,
+ item_id_to_move,
+ cx,
+ );
+ })
+ }
+ });
+ }
+ Action::Open(project_entry) => {
+ cx.window_context().defer(move |cx| {
+ if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ if let Some(task) = workspace.split_pane_with_project_entry(
+ pane_to_split,
+ split_direction,
+ project_entry,
+ cx,
+ ) {
+ task.detach_and_log_err(cx);
+ }
+ })
+ }
+ });
+ }
+ };
+ } else {
+ match action {
+ Action::Move(from, item_id) => {
+ if pane != &from || allow_same_pane {
+ let pane = pane.clone();
+ cx.window_context().defer(move |cx| {
+ if let Some(((workspace, from), to)) = workspace
+ .upgrade(cx)
+ .zip(from.upgrade(cx))
+ .zip(pane.upgrade(cx))
+ {
+ workspace.update(cx, |workspace, cx| {
+ workspace.move_item(from, to, item_id, index, cx);
+ })
+ }
+ });
+ } else {
+ cx.propagate_event();
+ }
+ }
+ Action::Open(project_entry) => {
+ let pane = pane.clone();
+ cx.window_context().defer(move |cx| {
+ if let Some(workspace) = workspace.upgrade(cx) {
+ workspace.update(cx, |workspace, cx| {
+ if let Some(path) =
+ workspace.project.read(cx).path_for_entry(project_entry, cx)
+ {
+ workspace
+ .open_path(path, Some(pane), true, cx)
+ .detach_and_log_err(cx);
+ }
+ });
+ }
+ });
+ }
+ }
+ }
+}
+
+fn drop_split_direction(
+ position: Vector2F,
+ region: RectF,
+ split_margin: f32,
+) -> Option<SplitDirection> {
+ let mut min_direction = None;
+ let mut min_distance = split_margin;
+ for direction in SplitDirection::all() {
+ let edge_distance = (direction.edge(region) - direction.axis().component(position)).abs();
+
+ if edge_distance < min_distance {
+ min_direction = Some(direction);
+ min_distance = edge_distance;
+ }
+ }
+
+ min_direction
+}
+
+fn overlay_color(cx: &AppContext) -> Color {
+ theme2::current(cx).workspace.drop_target_overlay_color
+}
@@ -1,23 +1,57 @@
use crate::{AppState, FollowerState, Pane, Workspace};
-use anyhow::{anyhow, Result};
+use anyhow::{anyhow, bail, Result};
use call2::ActiveCall;
use collections::HashMap;
-use gpui2::{size, AnyElement, AnyView, Bounds, Handle, Pixels, Point, View, ViewContext};
+use db2::sqlez::{
+ bindable::{Bind, Column, StaticColumnCount},
+ statement::Statement,
+};
+use gpui2::{
+ point, size, AnyElement, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext,
+};
+use parking_lot::Mutex;
use project2::Project;
use serde::Deserialize;
-use std::{cell::RefCell, rc::Rc, sync::Arc};
-use theme2::Theme;
+use std::sync::Arc;
+use ui::prelude::*;
const HANDLE_HITBOX_SIZE: f32 = 4.0;
const HORIZONTAL_MIN_SIZE: f32 = 80.;
const VERTICAL_MIN_SIZE: f32 = 100.;
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Axis {
Vertical,
Horizontal,
}
-#[derive(Clone, Debug, PartialEq)]
+impl StaticColumnCount for Axis {}
+impl Bind for Axis {
+ fn bind(&self, statement: &Statement, start_index: i32) -> anyhow::Result<i32> {
+ match self {
+ Axis::Horizontal => "Horizontal",
+ Axis::Vertical => "Vertical",
+ }
+ .bind(statement, start_index)
+ }
+}
+
+impl Column for Axis {
+ fn column(statement: &mut Statement, start_index: i32) -> anyhow::Result<(Self, i32)> {
+ String::column(statement, start_index).and_then(|(axis_text, next_index)| {
+ Ok((
+ match axis_text.as_str() {
+ "Horizontal" => Axis::Horizontal,
+ "Vertical" => Axis::Vertical,
+ _ => bail!("Stored serialized item kind is incorrect"),
+ },
+ next_index,
+ ))
+ })
+ }
+}
+
+#[derive(Clone, PartialEq)]
pub struct PaneGroup {
pub(crate) root: Member,
}
@@ -91,19 +125,17 @@ impl PaneGroup {
pub(crate) fn render(
&self,
- project: &Handle<Project>,
- theme: &Theme,
+ project: &Model<Project>,
follower_states: &HashMap<View<Pane>, FollowerState>,
- active_call: Option<&Handle<ActiveCall>>,
+ active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
- zoomed: Option<&AnyView>,
+ zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
- ) -> AnyElement<Workspace> {
+ ) -> impl Component<Workspace> {
self.root.render(
project,
0,
- theme,
follower_states,
active_call,
active_pane,
@@ -120,7 +152,7 @@ impl PaneGroup {
}
}
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone, PartialEq)]
pub(crate) enum Member {
Axis(PaneAxis),
Pane(View<Pane>),
@@ -153,17 +185,51 @@ impl Member {
pub fn render(
&self,
- project: &Handle<Project>,
+ project: &Model<Project>,
basis: usize,
- theme: &Theme,
follower_states: &HashMap<View<Pane>, FollowerState>,
- active_call: Option<&Handle<ActiveCall>>,
+ active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
- zoomed: Option<&AnyView>,
+ zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
- ) -> AnyElement<Workspace> {
- todo!()
+ ) -> impl Component<Workspace> {
+ match self {
+ Member::Pane(pane) => {
+ // todo!()
+ // let pane_element = if Some(pane.into()) == zoomed {
+ // None
+ // } else {
+ // Some(pane)
+ // };
+
+ div().child(pane.clone()).render()
+
+ // Stack::new()
+ // .with_child(pane_element.contained().with_border(leader_border))
+ // .with_children(leader_status_box)
+ // .into_any()
+
+ // let el = div()
+ // .flex()
+ // .flex_1()
+ // .gap_px()
+ // .w_full()
+ // .h_full()
+ // .bg(cx.theme().colors().editor)
+ // .children();
+ }
+ Member::Axis(axis) => axis.render(
+ project,
+ basis + 1,
+ follower_states,
+ active_call,
+ active_pane,
+ zoomed,
+ app_state,
+ cx,
+ ),
+ }
// enum FollowIntoExternalProject {}
@@ -305,18 +371,24 @@ impl Member {
}
}
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Clone)]
pub(crate) struct PaneAxis {
pub axis: Axis,
pub members: Vec<Member>,
- pub flexes: Rc<RefCell<Vec<f32>>>,
- pub bounding_boxes: Rc<RefCell<Vec<Option<Bounds<Pixels>>>>>,
+ pub flexes: Arc<Mutex<Vec<f32>>>,
+ pub bounding_boxes: Arc<Mutex<Vec<Option<Bounds<Pixels>>>>>,
+}
+
+impl PartialEq for PaneAxis {
+ fn eq(&self, other: &Self) -> bool {
+ todo!()
+ }
}
impl PaneAxis {
pub fn new(axis: Axis, members: Vec<Member>) -> Self {
- let flexes = Rc::new(RefCell::new(vec![1.; members.len()]));
- let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
+ let flexes = Arc::new(Mutex::new(vec![1.; members.len()]));
+ let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
Self {
axis,
members,
@@ -329,8 +401,8 @@ impl PaneAxis {
let flexes = flexes.unwrap_or_else(|| vec![1.; members.len()]);
debug_assert!(members.len() == flexes.len());
- let flexes = Rc::new(RefCell::new(flexes));
- let bounding_boxes = Rc::new(RefCell::new(vec![None; members.len()]));
+ let flexes = Arc::new(Mutex::new(flexes));
+ let bounding_boxes = Arc::new(Mutex::new(vec![None; members.len()]));
Self {
axis,
members,
@@ -360,7 +432,7 @@ impl PaneAxis {
}
self.members.insert(idx, Member::Pane(new_pane.clone()));
- *self.flexes.borrow_mut() = vec![1.; self.members.len()];
+ *self.flexes.lock() = vec![1.; self.members.len()];
} else {
*member =
Member::new_axis(old_pane.clone(), new_pane.clone(), direction);
@@ -400,12 +472,12 @@ impl PaneAxis {
if found_pane {
if let Some(idx) = remove_member {
self.members.remove(idx);
- *self.flexes.borrow_mut() = vec![1.; self.members.len()];
+ *self.flexes.lock() = vec![1.; self.members.len()];
}
if self.members.len() == 1 {
let result = self.members.pop();
- *self.flexes.borrow_mut() = vec![1.; self.members.len()];
+ *self.flexes.lock() = vec![1.; self.members.len()];
Ok(result)
} else {
Ok(None)
@@ -431,13 +503,13 @@ impl PaneAxis {
}
fn bounding_box_for_pane(&self, pane: &View<Pane>) -> Option<Bounds<Pixels>> {
- debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
+ debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
for (idx, member) in self.members.iter().enumerate() {
match member {
Member::Pane(found) => {
if pane == found {
- return self.bounding_boxes.borrow()[idx];
+ return self.bounding_boxes.lock()[idx];
}
}
Member::Axis(axis) => {
@@ -451,9 +523,9 @@ impl PaneAxis {
}
fn pane_at_pixel_position(&self, coordinate: Point<Pixels>) -> Option<&View<Pane>> {
- debug_assert!(self.members.len() == self.bounding_boxes.borrow().len());
+ debug_assert!(self.members.len() == self.bounding_boxes.lock().len());
- let bounding_boxes = self.bounding_boxes.borrow();
+ let bounding_boxes = self.bounding_boxes.lock();
for (idx, member) in self.members.iter().enumerate() {
if let Some(coordinates) = bounding_boxes[idx] {
@@ -470,17 +542,16 @@ impl PaneAxis {
fn render(
&self,
- project: &Handle<Project>,
+ project: &Model<Project>,
basis: usize,
- theme: &Theme,
follower_states: &HashMap<View<Pane>, FollowerState>,
- active_call: Option<&Handle<ActiveCall>>,
+ active_call: Option<&Model<ActiveCall>>,
active_pane: &View<Pane>,
- zoomed: Option<&AnyView>,
+ zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
- debug_assert!(self.members.len() == self.flexes.borrow().len());
+ debug_assert!(self.members.len() == self.flexes.lock().len());
todo!()
// let mut pane_axis = PaneAxisElement::new(
@@ -546,32 +617,32 @@ impl SplitDirection {
[Self::Up, Self::Down, Self::Left, Self::Right]
}
- pub fn edge(&self, rect: Bounds<Pixels>) -> f32 {
+ pub fn edge(&self, rect: Bounds<Pixels>) -> Pixels {
match self {
- Self::Up => rect.min_y(),
- Self::Down => rect.max_y(),
- Self::Left => rect.min_x(),
- Self::Right => rect.max_x(),
+ Self::Up => rect.origin.y,
+ Self::Down => rect.lower_left().y,
+ Self::Left => rect.lower_left().x,
+ Self::Right => rect.lower_right().x,
}
}
pub fn along_edge(&self, bounds: Bounds<Pixels>, length: Pixels) -> Bounds<Pixels> {
match self {
Self::Up => Bounds {
- origin: bounds.origin(),
- size: size(bounds.width(), length),
+ origin: bounds.origin,
+ size: size(bounds.size.width, length),
},
Self::Down => Bounds {
- origin: size(bounds.min_x(), bounds.max_y() - length),
- size: size(bounds.width(), length),
+ origin: point(bounds.lower_left().x, bounds.lower_left().y - length),
+ size: size(bounds.size.width, length),
},
Self::Left => Bounds {
- origin: bounds.origin(),
- size: size(length, bounds.height()),
+ origin: bounds.origin,
+ size: size(length, bounds.size.height),
},
Self::Right => Bounds {
- origin: size(bounds.max_x() - length, bounds.min_y()),
- size: size(length, bounds.height()),
+ origin: point(bounds.lower_right().x - length, bounds.lower_left().y),
+ size: size(length, bounds.size.height),
},
}
}
@@ -0,0 +1,973 @@
+#![allow(dead_code)]
+
+pub mod model;
+
+use std::path::Path;
+
+use anyhow::{anyhow, bail, Context, Result};
+use db2::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
+use gpui2::WindowBounds;
+
+use util::{unzip_option, ResultExt};
+use uuid::Uuid;
+
+use crate::{Axis, WorkspaceId};
+
+use model::{
+ GroupId, PaneId, SerializedItem, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
+ WorkspaceLocation,
+};
+
+use self::model::DockStructure;
+
+define_connection! {
+ // Current schema shape using pseudo-rust syntax:
+ //
+ // workspaces(
+ // workspace_id: usize, // Primary key for workspaces
+ // workspace_location: Bincode<Vec<PathBuf>>,
+ // dock_visible: bool, // Deprecated
+ // dock_anchor: DockAnchor, // Deprecated
+ // dock_pane: Option<usize>, // Deprecated
+ // left_sidebar_open: boolean,
+ // timestamp: String, // UTC YYYY-MM-DD HH:MM:SS
+ // window_state: String, // WindowBounds Discriminant
+ // window_x: Option<f32>, // WindowBounds::Fixed RectF x
+ // window_y: Option<f32>, // WindowBounds::Fixed RectF y
+ // window_width: Option<f32>, // WindowBounds::Fixed RectF width
+ // window_height: Option<f32>, // WindowBounds::Fixed RectF height
+ // display: Option<Uuid>, // Display id
+ // )
+ //
+ // pane_groups(
+ // group_id: usize, // Primary key for pane_groups
+ // workspace_id: usize, // References workspaces table
+ // parent_group_id: Option<usize>, // None indicates that this is the root node
+ // position: Optiopn<usize>, // None indicates that this is the root node
+ // axis: Option<Axis>, // 'Vertical', 'Horizontal'
+ // flexes: Option<Vec<f32>>, // A JSON array of floats
+ // )
+ //
+ // panes(
+ // pane_id: usize, // Primary key for panes
+ // workspace_id: usize, // References workspaces table
+ // active: bool,
+ // )
+ //
+ // center_panes(
+ // pane_id: usize, // Primary key for center_panes
+ // parent_group_id: Option<usize>, // References pane_groups. If none, this is the root
+ // position: Option<usize>, // None indicates this is the root
+ // )
+ //
+ // CREATE TABLE items(
+ // item_id: usize, // This is the item's view id, so this is not unique
+ // workspace_id: usize, // References workspaces table
+ // pane_id: usize, // References panes table
+ // kind: String, // Indicates which view this connects to. This is the key in the item_deserializers global
+ // position: usize, // Position of the item in the parent pane. This is equivalent to panes' position column
+ // active: bool, // Indicates if this item is the active one in the pane
+ // )
+ pub static ref DB: WorkspaceDb<()> =
+ &[sql!(
+ CREATE TABLE workspaces(
+ workspace_id INTEGER PRIMARY KEY,
+ workspace_location BLOB UNIQUE,
+ dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
+ dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
+ dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
+ left_sidebar_open INTEGER, // Boolean
+ 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,
+ parent_group_id INTEGER, // NULL indicates that this is a root node
+ position INTEGER, // NULL indicates that this is a root node
+ axis TEXT NOT NULL, // Enum: 'Vertical' / 'Horizontal'
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ ON DELETE CASCADE
+ 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,
+ active INTEGER NOT NULL, // Boolean
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ 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
+ position INTEGER, // NULL means that this is a root pane
+ FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
+ 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,
+ pane_id INTEGER NOT NULL,
+ kind TEXT NOT NULL,
+ position INTEGER NOT NULL,
+ active INTEGER NOT NULL,
+ FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+ ON DELETE CASCADE
+ ON UPDATE CASCADE,
+ FOREIGN KEY(pane_id) REFERENCES panes(pane_id)
+ ON DELETE CASCADE,
+ PRIMARY KEY(item_id, workspace_id)
+ ) STRICT;
+ ),
+ sql!(
+ 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;
+ ),
+ // Drop foreign key constraint from workspaces.dock_pane to panes table.
+ sql!(
+ CREATE TABLE workspaces_2(
+ workspace_id INTEGER PRIMARY KEY,
+ workspace_location BLOB UNIQUE,
+ dock_visible INTEGER, // Deprecated. Preserving so users can downgrade Zed.
+ dock_anchor TEXT, // Deprecated. Preserving so users can downgrade Zed.
+ dock_pane INTEGER, // Deprecated. Preserving so users can downgrade Zed.
+ left_sidebar_open INTEGER, // Boolean
+ timestamp TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ window_state TEXT,
+ window_x REAL,
+ window_y REAL,
+ window_width REAL,
+ window_height REAL,
+ display BLOB
+ ) STRICT;
+ INSERT INTO workspaces_2 SELECT * FROM workspaces;
+ DROP TABLE workspaces;
+ ALTER TABLE workspaces_2 RENAME TO workspaces;
+ ),
+ // Add panels related information
+ sql!(
+ ALTER TABLE workspaces ADD COLUMN left_dock_visible INTEGER; //bool
+ ALTER TABLE workspaces ADD COLUMN left_dock_active_panel TEXT;
+ ALTER TABLE workspaces ADD COLUMN right_dock_visible INTEGER; //bool
+ ALTER TABLE workspaces ADD COLUMN right_dock_active_panel TEXT;
+ ALTER TABLE workspaces ADD COLUMN bottom_dock_visible INTEGER; //bool
+ ALTER TABLE workspaces ADD COLUMN bottom_dock_active_panel TEXT;
+ ),
+ // Add panel zoom persistence
+ sql!(
+ ALTER TABLE workspaces ADD COLUMN left_dock_zoom INTEGER; //bool
+ ALTER TABLE workspaces ADD COLUMN right_dock_zoom INTEGER; //bool
+ ALTER TABLE workspaces ADD COLUMN bottom_dock_zoom INTEGER; //bool
+ ),
+ // Add pane group flex data
+ sql!(
+ ALTER TABLE pane_groups ADD COLUMN flexes TEXT;
+ )
+ ];
+}
+
+impl WorkspaceDb {
+ /// Returns a serialized workspace for the given worktree_roots. If the passed array
+ /// is empty, the most recent workspace is returned instead. If no workspace for the
+ /// passed roots is stored, returns none.
+ pub fn workspace_for_roots<P: AsRef<Path>>(
+ &self,
+ worktree_roots: &[P],
+ ) -> Option<SerializedWorkspace> {
+ 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, bounds, display, docks): (
+ WorkspaceId,
+ WorkspaceLocation,
+ Option<WindowBounds>,
+ Option<Uuid>,
+ DockStructure,
+ ) = self
+ .select_row_bound(sql! {
+ SELECT
+ workspace_id,
+ workspace_location,
+ window_state,
+ window_x,
+ window_y,
+ window_width,
+ window_height,
+ display,
+ left_dock_visible,
+ left_dock_active_panel,
+ left_dock_zoom,
+ right_dock_visible,
+ right_dock_active_panel,
+ right_dock_zoom,
+ bottom_dock_visible,
+ bottom_dock_active_panel,
+ bottom_dock_zoom
+ FROM workspaces
+ WHERE workspace_location = ?
+ })
+ .and_then(|mut prepared_statement| (prepared_statement)(&workspace_location))
+ .context("No workspaces found")
+ .warn_on_err()
+ .flatten()?;
+
+ Some(SerializedWorkspace {
+ id: workspace_id,
+ location: workspace_location.clone(),
+ center_group: self
+ .get_center_pane_group(workspace_id)
+ .context("Getting center group")
+ .log_err()?,
+ bounds,
+ display,
+ docks,
+ })
+ }
+
+ /// 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) {
+ self.write(move |conn| {
+ conn.with_savepoint("update_worktrees", || {
+ // Clear out panes and pane_groups
+ conn.exec_bound(sql!(
+ 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(
+ workspace_id,
+ workspace_location,
+ left_dock_visible,
+ left_dock_active_panel,
+ left_dock_zoom,
+ right_dock_visible,
+ right_dock_active_panel,
+ right_dock_zoom,
+ bottom_dock_visible,
+ bottom_dock_active_panel,
+ bottom_dock_zoom,
+ timestamp
+ )
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
+ ON CONFLICT DO
+ UPDATE SET
+ workspace_location = ?2,
+ left_dock_visible = ?3,
+ left_dock_active_panel = ?4,
+ left_dock_zoom = ?5,
+ right_dock_visible = ?6,
+ right_dock_active_panel = ?7,
+ right_dock_zoom = ?8,
+ bottom_dock_visible = ?9,
+ bottom_dock_active_panel = ?10,
+ bottom_dock_zoom = ?11,
+ timestamp = CURRENT_TIMESTAMP
+ ))?((workspace.id, &workspace.location, workspace.docks))
+ .context("Updating workspace")?;
+
+ // Save center pane group
+ Self::save_pane_group(conn, workspace.id, &workspace.center_group, None)
+ .context("save pane group in save workspace")?;
+
+ Ok(())
+ })
+ .log_err();
+ })
+ .await;
+ }
+
+ query! {
+ pub async fn next_id() -> Result<WorkspaceId> {
+ INSERT INTO workspaces DEFAULT VALUES RETURNING workspace_id
+ }
+ }
+
+ query! {
+ fn recent_workspaces() -> Result<Vec<(WorkspaceId, WorkspaceLocation)>> {
+ SELECT workspace_id, workspace_location
+ FROM workspaces
+ WHERE workspace_location IS NOT NULL
+ 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<Vec<(WorkspaceId, WorkspaceLocation)>> {
+ let mut result = Vec::new();
+ 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())
+ {
+ 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<Option<WorkspaceLocation>> {
+ Ok(self
+ .recent_workspaces_on_disk()
+ .await?
+ .into_iter()
+ .next()
+ .map(|(_, location)| location))
+ }
+
+ fn get_center_pane_group(&self, workspace_id: WorkspaceId) -> Result<SerializedPaneGroup> {
+ Ok(self
+ .get_pane_group(workspace_id, None)?
+ .into_iter()
+ .next()
+ .unwrap_or_else(|| {
+ SerializedPaneGroup::Pane(SerializedPane {
+ active: true,
+ children: vec![],
+ })
+ }))
+ }
+
+ fn get_pane_group(
+ &self,
+ workspace_id: WorkspaceId,
+ group_id: Option<GroupId>,
+ ) -> Result<Vec<SerializedPaneGroup>> {
+ type GroupKey = (Option<GroupId>, WorkspaceId);
+ type GroupOrPane = (
+ Option<GroupId>,
+ Option<Axis>,
+ Option<PaneId>,
+ Option<bool>,
+ Option<String>,
+ );
+ self.select_bound::<GroupKey, GroupOrPane>(sql!(
+ SELECT group_id, axis, pane_id, active, flexes
+ FROM (SELECT
+ group_id,
+ axis,
+ NULL as pane_id,
+ NULL as active,
+ position,
+ parent_group_id,
+ workspace_id,
+ flexes
+ FROM pane_groups
+ UNION
+ SELECT
+ NULL,
+ NULL,
+ center_panes.pane_id,
+ panes.active as active,
+ position,
+ parent_group_id,
+ panes.workspace_id as workspace_id,
+ NULL
+ FROM center_panes
+ JOIN panes ON center_panes.pane_id = panes.pane_id)
+ WHERE parent_group_id IS ? AND workspace_id = ?
+ ORDER BY position
+ ))?((group_id, workspace_id))?
+ .into_iter()
+ .map(|(group_id, axis, pane_id, active, flexes)| {
+ if let Some((group_id, axis)) = group_id.zip(axis) {
+ let flexes = flexes
+ .map(|flexes| serde_json::from_str::<Vec<f32>>(&flexes))
+ .transpose()?;
+
+ Ok(SerializedPaneGroup::Group {
+ axis,
+ children: self.get_pane_group(workspace_id, Some(group_id))?,
+ flexes,
+ })
+ } else if let Some((pane_id, active)) = pane_id.zip(active) {
+ Ok(SerializedPaneGroup::Pane(SerializedPane::new(
+ self.get_items(pane_id)?,
+ active,
+ )))
+ } else {
+ bail!("Pane Group Child was neither a pane group or a pane");
+ }
+ })
+ // Filter out panes and pane groups which don't have any children or items
+ .filter(|pane_group| match pane_group {
+ Ok(SerializedPaneGroup::Group { children, .. }) => !children.is_empty(),
+ Ok(SerializedPaneGroup::Pane(pane)) => !pane.children.is_empty(),
+ _ => true,
+ })
+ .collect::<Result<_>>()
+ }
+
+ fn save_pane_group(
+ conn: &Connection,
+ workspace_id: WorkspaceId,
+ pane_group: &SerializedPaneGroup,
+ parent: Option<(GroupId, usize)>,
+ ) -> Result<()> {
+ match pane_group {
+ SerializedPaneGroup::Group {
+ axis,
+ children,
+ flexes,
+ } => {
+ let (parent_id, position) = unzip_option(parent);
+
+ let flex_string = flexes
+ .as_ref()
+ .map(|flexes| serde_json::json!(flexes).to_string());
+
+ let group_id = conn.select_row_bound::<_, i64>(sql!(
+ INSERT INTO pane_groups(
+ workspace_id,
+ parent_group_id,
+ position,
+ axis,
+ flexes
+ )
+ VALUES (?, ?, ?, ?, ?)
+ RETURNING group_id
+ ))?((
+ workspace_id,
+ parent_id,
+ position,
+ *axis,
+ flex_string,
+ ))?
+ .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) => {
+ Self::save_pane(conn, workspace_id, &pane, parent)?;
+ Ok(())
+ }
+ }
+ }
+
+ fn save_pane(
+ conn: &Connection,
+ workspace_id: WorkspaceId,
+ pane: &SerializedPane,
+ parent: Option<(GroupId, usize)>,
+ ) -> Result<PaneId> {
+ let pane_id = conn.select_row_bound::<_, i64>(sql!(
+ INSERT INTO panes(workspace_id, active)
+ VALUES (?, ?)
+ RETURNING pane_id
+ ))?((workspace_id, pane.active))?
+ .ok_or_else(|| anyhow!("Could not retrieve inserted pane_id"))?;
+
+ let (parent_id, order) = unzip_option(parent);
+ conn.exec_bound(sql!(
+ INSERT INTO center_panes(pane_id, parent_group_id, position)
+ 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<Vec<SerializedItem>> {
+ Ok(self.select_bound(sql!(
+ SELECT kind, item_id, active FROM items
+ WHERE pane_id = ?
+ ORDER BY position
+ ))?(pane_id)?)
+ }
+
+ fn save_items(
+ conn: &Connection,
+ workspace_id: WorkspaceId,
+ pane_id: PaneId,
+ items: &[SerializedItem],
+ ) -> Result<()> {
+ let mut insert = conn.exec_bound(sql!(
+ INSERT INTO items(workspace_id, pane_id, position, kind, item_id, active) VALUES (?, ?, ?, ?, ?, ?)
+ )).context("Preparing insertion")?;
+ 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
+ SET timestamp = CURRENT_TIMESTAMP
+ WHERE workspace_id = ?
+ }
+ }
+
+ query! {
+ pub async fn set_window_bounds(workspace_id: WorkspaceId, bounds: WindowBounds, display: Uuid) -> Result<()> {
+ UPDATE workspaces
+ SET window_state = ?2,
+ window_x = ?3,
+ window_y = ?4,
+ window_width = ?5,
+ window_height = ?6,
+ display = ?7
+ WHERE workspace_id = ?1
+ }
+ }
+}
+
+// todo!()
+// #[cfg(test)]
+// mod tests {
+// use super::*;
+// use db::open_test_db;
+
+// #[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",
+// &[sql!(
+// CREATE TABLE test_table(
+// text TEXT,
+// workspace_id INTEGER,
+// FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
+// ON DELETE CASCADE
+// ) STRICT;
+// )],
+// )
+// .unwrap();
+// })
+// .await;
+
+// let id = db.next_id().await.unwrap();
+// // Assert the empty row got inserted
+// assert_eq!(
+// Some(id),
+// db.select_row_bound::<WorkspaceId, WorkspaceId>(sql!(
+// SELECT workspace_id FROM workspaces WHERE workspace_id = ?
+// ))
+// .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()
+// })
+// .await;
+
+// let test_text_1 = db
+// .select_row_bound::<_, String>(sql!(SELECT text FROM test_table WHERE workspace_id = ?))
+// .unwrap()(1)
+// .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",
+// &[sql!(
+// CREATE TABLE test_table(
+// text TEXT,
+// workspace_id INTEGER,
+// FOREIGN KEY(workspace_id)
+// REFERENCES workspaces(workspace_id)
+// ON DELETE CASCADE
+// ) STRICT;)],
+// )
+// })
+// .await
+// .unwrap();
+
+// let mut workspace_1 = SerializedWorkspace {
+// id: 1,
+// location: (["/tmp", "/tmp2"]).into(),
+// center_group: Default::default(),
+// bounds: Default::default(),
+// display: Default::default(),
+// docks: Default::default(),
+// };
+
+// let workspace_2 = SerializedWorkspace {
+// id: 2,
+// location: (["/tmp"]).into(),
+// center_group: Default::default(),
+// bounds: Default::default(),
+// display: Default::default(),
+// docks: 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();
+// })
+// .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();
+// })
+// .await;
+
+// workspace_1.location = (["/tmp", "/tmp3"]).into();
+// db.save_workspace(workspace_1.clone()).await;
+// db.save_workspace(workspace_1).await;
+// 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();
+// 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();
+// assert_eq!(test_text_1, "test-text-1");
+// }
+
+// fn group(axis: gpui::Axis, children: Vec<SerializedPaneGroup>) -> SerializedPaneGroup {
+// SerializedPaneGroup::Group {
+// axis,
+// flexes: None,
+// children,
+// }
+// }
+
+// #[gpui::test]
+// async fn test_full_workspace_serialization() {
+// env_logger::try_init().ok();
+
+// let db = WorkspaceDb(open_test_db("test_full_workspace_serialization").await);
+
+// // -----------------
+// // | 1,2 | 5,6 |
+// // | - - - | |
+// // | 3,4 | |
+// // -----------------
+// let center_group = group(
+// gpui::Axis::Horizontal,
+// vec![
+// group(
+// gpui::Axis::Vertical,
+// vec![
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 5, false),
+// SerializedItem::new("Terminal", 6, true),
+// ],
+// false,
+// )),
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 7, true),
+// SerializedItem::new("Terminal", 8, false),
+// ],
+// false,
+// )),
+// ],
+// ),
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 9, false),
+// SerializedItem::new("Terminal", 10, true),
+// ],
+// false,
+// )),
+// ],
+// );
+
+// let workspace = SerializedWorkspace {
+// id: 5,
+// location: (["/tmp", "/tmp2"]).into(),
+// center_group,
+// bounds: Default::default(),
+// display: Default::default(),
+// docks: 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(),
+// center_group: Default::default(),
+// bounds: Default::default(),
+// display: Default::default(),
+// docks: Default::default(),
+// };
+
+// let mut workspace_2 = SerializedWorkspace {
+// id: 2,
+// location: (["/tmp"]).into(),
+// center_group: Default::default(),
+// bounds: Default::default(),
+// display: Default::default(),
+// docks: 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(),
+// workspace_1
+// );
+// assert_eq!(
+// 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,
+// location: (&["/tmp", "/tmp2"]).into(),
+// center_group: Default::default(),
+// bounds: Default::default(),
+// display: Default::default(),
+// docks: 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;
+// assert_eq!(db.workspace_for_roots(&["/tmp2", "tmp"]), None);
+// assert_eq!(
+// db.workspace_for_roots(&["/tmp2", "/tmp3", "/tmp4"])
+// .unwrap(),
+// workspace_3
+// );
+// }
+
+// use crate::persistence::model::SerializedWorkspace;
+// use crate::persistence::model::{SerializedItem, SerializedPane, SerializedPaneGroup};
+
+// fn default_workspace<P: AsRef<Path>>(
+// workspace_id: &[P],
+// center_group: &SerializedPaneGroup,
+// ) -> SerializedWorkspace {
+// SerializedWorkspace {
+// id: 4,
+// location: workspace_id.into(),
+// center_group: center_group.clone(),
+// bounds: Default::default(),
+// display: Default::default(),
+// docks: Default::default(),
+// }
+// }
+
+// #[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 |
+// // | - - - | |
+// // | 3,4 | |
+// // -----------------
+// let center_pane = group(
+// gpui::Axis::Horizontal,
+// vec![
+// group(
+// gpui::Axis::Vertical,
+// vec![
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 1, false),
+// SerializedItem::new("Terminal", 2, true),
+// ],
+// false,
+// )),
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 4, false),
+// SerializedItem::new("Terminal", 3, true),
+// ],
+// true,
+// )),
+// ],
+// ),
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 5, true),
+// SerializedItem::new("Terminal", 6, false),
+// ],
+// false,
+// )),
+// ],
+// );
+
+// let workspace = default_workspace(&["/tmp"], ¢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 = group(
+// gpui::Axis::Horizontal,
+// vec![
+// group(
+// gpui::Axis::Vertical,
+// vec![
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 1, false),
+// SerializedItem::new("Terminal", 2, true),
+// ],
+// false,
+// )),
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 4, false),
+// SerializedItem::new("Terminal", 3, true),
+// ],
+// true,
+// )),
+// ],
+// ),
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 5, false),
+// SerializedItem::new("Terminal", 6, true),
+// ],
+// false,
+// )),
+// ],
+// );
+
+// let id = &["/tmp"];
+
+// let mut workspace = default_workspace(id, ¢er_pane);
+
+// db.save_workspace(workspace.clone()).await;
+
+// workspace.center_group = group(
+// gpui::Axis::Vertical,
+// vec![
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 1, false),
+// SerializedItem::new("Terminal", 2, true),
+// ],
+// false,
+// )),
+// SerializedPaneGroup::Pane(SerializedPane::new(
+// vec![
+// SerializedItem::new("Terminal", 4, true),
+// SerializedItem::new("Terminal", 3, false),
+// ],
+// true,
+// )),
+// ],
+// );
+
+// db.save_workspace(workspace.clone()).await;
+
+// let new_workspace = db.workspace_for_roots(id).unwrap();
+
+// assert_eq!(workspace.center_group, new_workspace.center_group);
+// }
+// }
@@ -7,7 +7,7 @@ use db2::sqlez::{
bindable::{Bind, Column, StaticColumnCount},
statement::Statement,
};
-use gpui2::{AsyncAppContext, Handle, Task, View, WeakView, WindowBounds};
+use gpui2::{AsyncWindowContext, Model, Task, View, WeakView, WindowBounds};
use project2::Project;
use std::{
path::{Path, PathBuf},
@@ -55,7 +55,7 @@ impl Column for WorkspaceLocation {
}
}
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
pub struct SerializedWorkspace {
pub id: WorkspaceId,
pub location: WorkspaceLocation,
@@ -127,7 +127,7 @@ impl Bind for DockData {
}
}
-#[derive(Debug, PartialEq, Clone)]
+#[derive(PartialEq, Clone)]
pub enum SerializedPaneGroup {
Group {
axis: Axis,
@@ -151,10 +151,10 @@ impl SerializedPaneGroup {
#[async_recursion(?Send)]
pub(crate) async fn deserialize(
self,
- project: &Handle<Project>,
+ project: &Model<Project>,
workspace_id: WorkspaceId,
- workspace: &WeakView<Workspace>,
- cx: &mut AsyncAppContext,
+ workspace: WeakView<Workspace>,
+ cx: &mut AsyncWindowContext,
) -> Option<(Member, Option<View<Pane>>, Vec<Option<Box<dyn ItemHandle>>>)> {
match self {
SerializedPaneGroup::Group {
@@ -167,7 +167,7 @@ impl SerializedPaneGroup {
let mut items = Vec::new();
for child in children {
if let Some((new_member, active_pane, new_items)) = child
- .deserialize(project, workspace_id, workspace, cx)
+ .deserialize(project, workspace_id, workspace.clone(), cx)
.await
{
members.push(new_member);
@@ -196,14 +196,11 @@ impl SerializedPaneGroup {
.log_err()?;
let active = serialized_pane.active;
let new_items = serialized_pane
- .deserialize_to(project, &pane, workspace_id, workspace, cx)
+ .deserialize_to(project, &pane, workspace_id, workspace.clone(), cx)
.await
.log_err()?;
- if pane
- .read_with(cx, |pane, _| pane.items_len() != 0)
- .log_err()?
- {
+ if pane.update(cx, |pane, _| pane.items_len() != 0).log_err()? {
let pane = pane.upgrade()?;
Some((Member::Pane(pane.clone()), active.then(|| pane), new_items))
} else {
@@ -231,11 +228,11 @@ impl SerializedPane {
pub async fn deserialize_to(
&self,
- project: &Handle<Project>,
+ project: &Model<Project>,
pane: &WeakView<Pane>,
workspace_id: WorkspaceId,
- workspace: &WeakView<Workspace>,
- cx: &mut AsyncAppContext,
+ workspace: WeakView<Workspace>,
+ cx: &mut AsyncWindowContext,
) -> Result<Vec<Option<Box<dyn ItemHandle>>>> {
let mut items = Vec::new();
let mut active_item_index = None;
@@ -289,15 +286,15 @@ pub struct SerializedItem {
pub active: bool,
}
-impl SerializedItem {
- pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool) -> Self {
- Self {
- kind: Arc::from(kind.as_ref()),
- item_id,
- active,
- }
- }
-}
+// impl SerializedItem {
+// pub fn new(kind: impl AsRef<str>, item_id: ItemId, active: bool) -> Self {
+// Self {
+// kind: Arc::from(kind.as_ref()),
+// item_id,
+// active,
+// }
+// }
+// }
#[cfg(test)]
impl Default for SerializedItem {
@@ -0,0 +1,285 @@
+use std::{any::Any, sync::Arc};
+
+use gpui2::{AnyView, AppContext, Subscription, Task, View, ViewContext, WindowContext};
+use project2::search::SearchQuery;
+
+use crate::{
+ item::{Item, WeakItemHandle},
+ ItemHandle,
+};
+
+#[derive(Debug)]
+pub enum SearchEvent {
+ MatchesInvalidated,
+ ActiveMatchChanged,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum Direction {
+ Prev,
+ Next,
+}
+
+#[derive(Clone, Copy, Debug, Default)]
+pub struct SearchOptions {
+ pub case: bool,
+ pub word: bool,
+ pub regex: bool,
+ /// Specifies whether the item supports search & replace.
+ pub replacement: bool,
+}
+
+pub trait SearchableItem: Item {
+ type Match: Any + Sync + Send + Clone;
+
+ fn supported_options() -> SearchOptions {
+ SearchOptions {
+ case: true,
+ word: true,
+ regex: true,
+ replacement: true,
+ }
+ }
+ fn to_search_event(
+ &mut self,
+ event: &Self::Event,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<SearchEvent>;
+ fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
+ fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
+ fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
+ fn activate_match(
+ &mut self,
+ index: usize,
+ matches: Vec<Self::Match>,
+ cx: &mut ViewContext<Self>,
+ );
+ fn select_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
+ fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
+ fn match_index_for_direction(
+ &mut self,
+ matches: &Vec<Self::Match>,
+ current_index: usize,
+ direction: Direction,
+ count: usize,
+ _: &mut ViewContext<Self>,
+ ) -> usize {
+ match direction {
+ Direction::Prev => {
+ let count = count % matches.len();
+ if current_index >= count {
+ current_index - count
+ } else {
+ matches.len() - (count - current_index)
+ }
+ }
+ Direction::Next => (current_index + count) % matches.len(),
+ }
+ }
+ fn find_matches(
+ &mut self,
+ query: Arc<SearchQuery>,
+ cx: &mut ViewContext<Self>,
+ ) -> Task<Vec<Self::Match>>;
+ fn active_match_index(
+ &mut self,
+ matches: Vec<Self::Match>,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<usize>;
+}
+
+pub trait SearchableItemHandle: ItemHandle {
+ fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
+ fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
+ fn supported_options(&self) -> SearchOptions;
+ fn subscribe_to_search_events(
+ &self,
+ cx: &mut WindowContext,
+ handler: Box<dyn Fn(SearchEvent, &mut WindowContext) + Send>,
+ ) -> Subscription;
+ fn clear_matches(&self, cx: &mut WindowContext);
+ fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
+ fn query_suggestion(&self, cx: &mut WindowContext) -> String;
+ fn activate_match(
+ &self,
+ index: usize,
+ matches: &Vec<Box<dyn Any + Send>>,
+ cx: &mut WindowContext,
+ );
+ fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext);
+ fn replace(&self, _: &Box<dyn Any + Send>, _: &SearchQuery, _: &mut WindowContext);
+ fn match_index_for_direction(
+ &self,
+ matches: &Vec<Box<dyn Any + Send>>,
+ current_index: usize,
+ direction: Direction,
+ count: usize,
+ cx: &mut WindowContext,
+ ) -> usize;
+ fn find_matches(
+ &self,
+ query: Arc<SearchQuery>,
+ cx: &mut WindowContext,
+ ) -> Task<Vec<Box<dyn Any + Send>>>;
+ fn active_match_index(
+ &self,
+ matches: &Vec<Box<dyn Any + Send>>,
+ cx: &mut WindowContext,
+ ) -> Option<usize>;
+}
+
+// todo!("here is where we need to use AnyWeakView");
+impl<T: SearchableItem> SearchableItemHandle for View<T> {
+ fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
+ // Box::new(self.downgrade())
+ todo!()
+ }
+
+ fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
+ Box::new(self.clone())
+ }
+
+ fn supported_options(&self) -> SearchOptions {
+ T::supported_options()
+ }
+
+ fn subscribe_to_search_events(
+ &self,
+ cx: &mut WindowContext,
+ handler: Box<dyn Fn(SearchEvent, &mut WindowContext) + Send>,
+ ) -> Subscription {
+ cx.subscribe(self, move |handle, event, cx| {
+ let search_event = handle.update(cx, |handle, cx| handle.to_search_event(event, cx));
+ if let Some(search_event) = search_event {
+ handler(search_event, cx)
+ }
+ })
+ }
+
+ fn clear_matches(&self, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| this.clear_matches(cx));
+ }
+ fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
+ let matches = downcast_matches(matches);
+ self.update(cx, |this, cx| this.update_matches(matches, cx));
+ }
+ fn query_suggestion(&self, cx: &mut WindowContext) -> String {
+ self.update(cx, |this, cx| this.query_suggestion(cx))
+ }
+ fn activate_match(
+ &self,
+ index: usize,
+ matches: &Vec<Box<dyn Any + Send>>,
+ cx: &mut WindowContext,
+ ) {
+ let matches = downcast_matches(matches);
+ self.update(cx, |this, cx| this.activate_match(index, matches, cx));
+ }
+
+ fn select_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut WindowContext) {
+ let matches = downcast_matches(matches);
+ self.update(cx, |this, cx| this.select_matches(matches, cx));
+ }
+
+ fn match_index_for_direction(
+ &self,
+ matches: &Vec<Box<dyn Any + Send>>,
+ current_index: usize,
+ direction: Direction,
+ count: usize,
+ cx: &mut WindowContext,
+ ) -> usize {
+ let matches = downcast_matches(matches);
+ self.update(cx, |this, cx| {
+ this.match_index_for_direction(&matches, current_index, direction, count, cx)
+ })
+ }
+ fn find_matches(
+ &self,
+ query: Arc<SearchQuery>,
+ cx: &mut WindowContext,
+ ) -> Task<Vec<Box<dyn Any + Send>>> {
+ let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
+ cx.spawn(|cx| async {
+ let matches = matches.await;
+ matches
+ .into_iter()
+ .map::<Box<dyn Any + Send>, _>(|range| Box::new(range))
+ .collect()
+ })
+ }
+ fn active_match_index(
+ &self,
+ matches: &Vec<Box<dyn Any + Send>>,
+ cx: &mut WindowContext,
+ ) -> Option<usize> {
+ let matches = downcast_matches(matches);
+ self.update(cx, |this, cx| this.active_match_index(matches, cx))
+ }
+
+ fn replace(&self, matches: &Box<dyn Any + Send>, query: &SearchQuery, cx: &mut WindowContext) {
+ let matches = matches.downcast_ref().unwrap();
+ self.update(cx, |this, cx| this.replace(matches, query, cx))
+ }
+}
+
+fn downcast_matches<T: Any + Clone>(matches: &Vec<Box<dyn Any + Send>>) -> Vec<T> {
+ matches
+ .iter()
+ .map(|range| range.downcast_ref::<T>().cloned())
+ .collect::<Option<Vec<_>>>()
+ .expect(
+ "SearchableItemHandle function called with vec of matches of a different type than expected",
+ )
+}
+
+impl From<Box<dyn SearchableItemHandle>> for AnyView {
+ fn from(this: Box<dyn SearchableItemHandle>) -> Self {
+ this.to_any().clone()
+ }
+}
+
+impl From<&Box<dyn SearchableItemHandle>> for AnyView {
+ fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
+ this.to_any().clone()
+ }
+}
+
+impl PartialEq for Box<dyn SearchableItemHandle> {
+ fn eq(&self, other: &Self) -> bool {
+ self.id() == other.id()
+ }
+}
+
+impl Eq for Box<dyn SearchableItemHandle> {}
+
+pub trait WeakSearchableItemHandle: WeakItemHandle {
+ fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
+
+ // fn into_any(self) -> AnyWeakView;
+}
+
+// todo!()
+// impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
+// fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
+// Some(Box::new(self.upgrade(cx)?))
+// }
+
+// // fn into_any(self) -> AnyView {
+// // self.into_any()
+// // }
+// }
+
+impl PartialEq for Box<dyn WeakSearchableItemHandle> {
+ fn eq(&self, other: &Self) -> bool {
+ self.id() == other.id()
+ }
+}
+
+impl Eq for Box<dyn WeakSearchableItemHandle> {}
+
+impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.id().hash(state)
+ }
+}
@@ -0,0 +1,151 @@
+use crate::{
+ item::{Item, ItemEvent},
+ ItemNavHistory, WorkspaceId,
+};
+use anyhow::Result;
+use call::participant::{Frame, RemoteVideoTrack};
+use client::{proto::PeerId, User};
+use futures::StreamExt;
+use gpui::{
+ elements::*,
+ geometry::{rect::RectF, vector::vec2f},
+ platform::MouseButton,
+ AppContext, Entity, Task, View, ViewContext,
+};
+use smallvec::SmallVec;
+use std::{
+ borrow::Cow,
+ sync::{Arc, Weak},
+};
+
+pub enum Event {
+ Close,
+}
+
+pub struct SharedScreen {
+ track: Weak<RemoteVideoTrack>,
+ frame: Option<Frame>,
+ pub peer_id: PeerId,
+ user: Arc<User>,
+ nav_history: Option<ItemNavHistory>,
+ _maintain_frame: Task<Result<()>>,
+}
+
+impl SharedScreen {
+ pub fn new(
+ track: &Arc<RemoteVideoTrack>,
+ peer_id: PeerId,
+ user: Arc<User>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ let mut frames = track.frames();
+ Self {
+ track: Arc::downgrade(track),
+ frame: None,
+ peer_id,
+ user,
+ nav_history: Default::default(),
+ _maintain_frame: cx.spawn(|this, mut cx| async move {
+ while let Some(frame) = frames.next().await {
+ this.update(&mut cx, |this, cx| {
+ this.frame = Some(frame);
+ cx.notify();
+ })?;
+ }
+ this.update(&mut cx, |_, cx| cx.emit(Event::Close))?;
+ Ok(())
+ }),
+ }
+ }
+}
+
+impl Entity for SharedScreen {
+ type Event = Event;
+}
+
+impl View for SharedScreen {
+ fn ui_name() -> &'static str {
+ "SharedScreen"
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+ enum Focus {}
+
+ let frame = self.frame.clone();
+ MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
+ Canvas::new(move |bounds, _, _, cx| {
+ if let Some(frame) = frame.clone() {
+ let size = constrain_size_preserving_aspect_ratio(
+ bounds.size(),
+ vec2f(frame.width() as f32, frame.height() as f32),
+ );
+ let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
+ cx.scene().push_surface(gpui::platform::mac::Surface {
+ bounds: RectF::new(origin, size),
+ image_buffer: frame.image(),
+ });
+ }
+ })
+ .contained()
+ .with_style(theme::current(cx).shared_screen)
+ })
+ .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
+ .into_any()
+ }
+}
+
+impl Item for SharedScreen {
+ fn tab_tooltip_text(&self, _: &AppContext) -> Option<Cow<str>> {
+ Some(format!("{}'s screen", self.user.github_login).into())
+ }
+ fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
+ if let Some(nav_history) = self.nav_history.as_mut() {
+ nav_history.push::<()>(None, cx);
+ }
+ }
+
+ fn tab_content<V: 'static>(
+ &self,
+ _: Option<usize>,
+ style: &theme::Tab,
+ _: &AppContext,
+ ) -> gpui::AnyElement<V> {
+ Flex::row()
+ .with_child(
+ Svg::new("icons/desktop.svg")
+ .with_color(style.label.text.color)
+ .constrained()
+ .with_width(style.type_icon_width)
+ .aligned()
+ .contained()
+ .with_margin_right(style.spacing),
+ )
+ .with_child(
+ Label::new(
+ format!("{}'s screen", self.user.github_login),
+ style.label.clone(),
+ )
+ .aligned(),
+ )
+ .into_any()
+ }
+
+ fn set_nav_history(&mut self, history: ItemNavHistory, _: &mut ViewContext<Self>) {
+ self.nav_history = Some(history);
+ }
+
+ fn clone_on_split(
+ &self,
+ _workspace_id: WorkspaceId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<Self> {
+ let track = self.track.upgrade()?;
+ Some(Self::new(&track, self.peer_id, self.user.clone(), cx))
+ }
+
+ fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+ match event {
+ Event::Close => smallvec::smallvec!(ItemEvent::CloseItem),
+ }
+ }
+}
@@ -0,0 +1,301 @@
+use std::any::TypeId;
+
+use crate::{ItemHandle, Pane};
+use gpui2::{
+ div, AnyView, Component, Div, ParentElement, Render, Styled, Subscription, View, ViewContext,
+ WindowContext,
+};
+use theme2::ActiveTheme;
+use util::ResultExt;
+
+pub trait StatusItemView: Render {
+ fn set_active_pane_item(
+ &mut self,
+ active_pane_item: Option<&dyn crate::ItemHandle>,
+ cx: &mut ViewContext<Self>,
+ );
+}
+
+trait StatusItemViewHandle: Send {
+ fn to_any(&self) -> AnyView;
+ fn set_active_pane_item(
+ &self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut WindowContext,
+ );
+ fn item_type(&self) -> TypeId;
+}
+
+pub struct StatusBar {
+ left_items: Vec<Box<dyn StatusItemViewHandle>>,
+ right_items: Vec<Box<dyn StatusItemViewHandle>>,
+ active_pane: View<Pane>,
+ _observe_active_pane: Subscription,
+}
+
+impl Render for StatusBar {
+ type Element = Div<Self>;
+
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ div()
+ .py_0p5()
+ .px_1()
+ .flex()
+ .items_center()
+ .justify_between()
+ .w_full()
+ .bg(cx.theme().colors().status_bar)
+ .child(self.render_left_tools(cx))
+ .child(self.render_right_tools(cx))
+ }
+}
+
+impl StatusBar {
+ fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+ div()
+ .flex()
+ .items_center()
+ .gap_1()
+ .children(self.left_items.iter().map(|item| item.to_any()))
+ }
+
+ fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+ div()
+ .flex()
+ .items_center()
+ .gap_2()
+ .children(self.right_items.iter().map(|item| item.to_any()))
+ }
+}
+
+// todo!()
+// impl View for StatusBar {
+// fn ui_name() -> &'static str {
+// "StatusBar"
+// }
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// let theme = &theme::current(cx).workspace.status_bar;
+
+// StatusBarElement {
+// left: Flex::row()
+// .with_children(self.left_items.iter().map(|i| {
+// ChildView::new(i.as_any(), cx)
+// .aligned()
+// .contained()
+// .with_margin_right(theme.item_spacing)
+// }))
+// .into_any(),
+// right: Flex::row()
+// .with_children(self.right_items.iter().rev().map(|i| {
+// ChildView::new(i.as_any(), cx)
+// .aligned()
+// .contained()
+// .with_margin_left(theme.item_spacing)
+// }))
+// .into_any(),
+// }
+// .contained()
+// .with_style(theme.container)
+// .constrained()
+// .with_height(theme.height)
+// .into_any()
+// }
+// }
+
+impl StatusBar {
+ pub fn new(active_pane: &View<Pane>, cx: &mut ViewContext<Self>) -> Self {
+ let mut this = Self {
+ left_items: Default::default(),
+ right_items: Default::default(),
+ active_pane: active_pane.clone(),
+ _observe_active_pane: cx
+ .observe(active_pane, |this, _, cx| this.update_active_pane_item(cx)),
+ };
+ this.update_active_pane_item(cx);
+ this
+ }
+
+ pub fn add_left_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
+ where
+ T: 'static + StatusItemView,
+ {
+ self.left_items.push(Box::new(item));
+ cx.notify();
+ }
+
+ pub fn item_of_type<T: StatusItemView>(&self) -> Option<View<T>> {
+ self.left_items
+ .iter()
+ .chain(self.right_items.iter())
+ .find_map(|item| item.to_any().clone().downcast().log_err())
+ }
+
+ pub fn position_of_item<T>(&self) -> Option<usize>
+ where
+ T: StatusItemView,
+ {
+ for (index, item) in self.left_items.iter().enumerate() {
+ if item.item_type() == TypeId::of::<T>() {
+ return Some(index);
+ }
+ }
+ for (index, item) in self.right_items.iter().enumerate() {
+ if item.item_type() == TypeId::of::<T>() {
+ return Some(index + self.left_items.len());
+ }
+ }
+ return None;
+ }
+
+ pub fn insert_item_after<T>(
+ &mut self,
+ position: usize,
+ item: View<T>,
+ cx: &mut ViewContext<Self>,
+ ) where
+ T: 'static + StatusItemView,
+ {
+ if position < self.left_items.len() {
+ self.left_items.insert(position + 1, Box::new(item))
+ } else {
+ self.right_items
+ .insert(position + 1 - self.left_items.len(), Box::new(item))
+ }
+ cx.notify()
+ }
+
+ pub fn remove_item_at(&mut self, position: usize, cx: &mut ViewContext<Self>) {
+ if position < self.left_items.len() {
+ self.left_items.remove(position);
+ } else {
+ self.right_items.remove(position - self.left_items.len());
+ }
+ cx.notify();
+ }
+
+ pub fn add_right_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
+ where
+ T: 'static + StatusItemView,
+ {
+ self.right_items.push(Box::new(item));
+ cx.notify();
+ }
+
+ pub fn set_active_pane(&mut self, active_pane: &View<Pane>, cx: &mut ViewContext<Self>) {
+ self.active_pane = active_pane.clone();
+ self._observe_active_pane =
+ cx.observe(active_pane, |this, _, cx| this.update_active_pane_item(cx));
+ self.update_active_pane_item(cx);
+ }
+
+ fn update_active_pane_item(&mut self, cx: &mut ViewContext<Self>) {
+ let active_pane_item = self.active_pane.read(cx).active_item();
+ for item in self.left_items.iter().chain(&self.right_items) {
+ item.set_active_pane_item(active_pane_item.as_deref(), cx);
+ }
+ }
+}
+
+impl<T: StatusItemView> StatusItemViewHandle for View<T> {
+ fn to_any(&self) -> AnyView {
+ self.clone().into()
+ }
+
+ fn set_active_pane_item(
+ &self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut WindowContext,
+ ) {
+ self.update(cx, |this, cx| {
+ this.set_active_pane_item(active_pane_item, cx)
+ });
+ }
+
+ fn item_type(&self) -> TypeId {
+ TypeId::of::<T>()
+ }
+}
+
+impl From<&dyn StatusItemViewHandle> for AnyView {
+ fn from(val: &dyn StatusItemViewHandle) -> Self {
+ val.to_any().clone()
+ }
+}
+
+// todo!()
+// struct StatusBarElement {
+// left: AnyElement<StatusBar>,
+// right: AnyElement<StatusBar>,
+// }
+
+// todo!()
+// impl Element<StatusBar> for StatusBarElement {
+// type LayoutState = ();
+// type PaintState = ();
+
+// fn layout(
+// &mut self,
+// mut constraint: SizeConstraint,
+// view: &mut StatusBar,
+// cx: &mut ViewContext<StatusBar>,
+// ) -> (Vector2F, Self::LayoutState) {
+// let max_width = constraint.max.x();
+// constraint.min = vec2f(0., constraint.min.y());
+
+// let right_size = self.right.layout(constraint, view, cx);
+// let constraint = SizeConstraint::new(
+// vec2f(0., constraint.min.y()),
+// vec2f(max_width - right_size.x(), constraint.max.y()),
+// );
+
+// self.left.layout(constraint, view, cx);
+
+// (vec2f(max_width, right_size.y()), ())
+// }
+
+// fn paint(
+// &mut self,
+// bounds: RectF,
+// visible_bounds: RectF,
+// _: &mut Self::LayoutState,
+// view: &mut StatusBar,
+// cx: &mut ViewContext<StatusBar>,
+// ) -> Self::PaintState {
+// let origin_y = bounds.upper_right().y();
+// let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
+
+// let left_origin = vec2f(bounds.lower_left().x(), origin_y);
+// self.left.paint(left_origin, visible_bounds, view, cx);
+
+// let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
+// self.right.paint(right_origin, visible_bounds, view, cx);
+// }
+
+// fn rect_for_text_range(
+// &self,
+// _: Range<usize>,
+// _: RectF,
+// _: RectF,
+// _: &Self::LayoutState,
+// _: &Self::PaintState,
+// _: &StatusBar,
+// _: &ViewContext<StatusBar>,
+// ) -> Option<RectF> {
+// None
+// }
+
+// fn debug(
+// &self,
+// bounds: RectF,
+// _: &Self::LayoutState,
+// _: &Self::PaintState,
+// _: &StatusBar,
+// _: &ViewContext<StatusBar>,
+// ) -> serde_json::Value {
+// json!({
+// "type": "StatusBarElement",
+// "bounds": bounds.to_json()
+// })
+// }
+// }
@@ -0,0 +1,298 @@
+use crate::ItemHandle;
+use gpui2::{
+ AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext,
+};
+
+pub trait ToolbarItemView: Render + EventEmitter {
+ fn set_active_pane_item(
+ &mut self,
+ active_pane_item: Option<&dyn crate::ItemHandle>,
+ cx: &mut ViewContext<Self>,
+ ) -> ToolbarItemLocation;
+
+ fn location_for_event(
+ &self,
+ _event: &Self::Event,
+ current_location: ToolbarItemLocation,
+ _cx: &AppContext,
+ ) -> ToolbarItemLocation {
+ current_location
+ }
+
+ fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext<Self>) {}
+
+ /// Number of times toolbar's height will be repeated to get the effective height.
+ /// Useful when multiple rows one under each other are needed.
+ /// The rows have the same width and act as a whole when reacting to resizes and similar events.
+ fn row_count(&self, _cx: &WindowContext) -> usize {
+ 1
+ }
+}
+
+trait ToolbarItemViewHandle: Send {
+ fn id(&self) -> EntityId;
+ fn to_any(&self) -> AnyView;
+ fn set_active_pane_item(
+ &self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut WindowContext,
+ ) -> ToolbarItemLocation;
+ fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext);
+ fn row_count(&self, cx: &WindowContext) -> usize;
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ToolbarItemLocation {
+ Hidden,
+ PrimaryLeft { flex: Option<(f32, bool)> },
+ PrimaryRight { flex: Option<(f32, bool)> },
+ Secondary,
+}
+
+pub struct Toolbar {
+ active_item: Option<Box<dyn ItemHandle>>,
+ hidden: bool,
+ can_navigate: bool,
+ items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
+}
+
+// todo!()
+// impl View for Toolbar {
+// fn ui_name() -> &'static str {
+// "Toolbar"
+// }
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// let theme = &theme::current(cx).workspace.toolbar;
+
+// let mut primary_left_items = Vec::new();
+// let mut primary_right_items = Vec::new();
+// let mut secondary_item = None;
+// let spacing = theme.item_spacing;
+// let mut primary_items_row_count = 1;
+
+// for (item, position) in &self.items {
+// match *position {
+// ToolbarItemLocation::Hidden => {}
+
+// ToolbarItemLocation::PrimaryLeft { flex } => {
+// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
+// let left_item = ChildView::new(item.as_any(), cx).aligned();
+// if let Some((flex, expanded)) = flex {
+// primary_left_items.push(left_item.flex(flex, expanded).into_any());
+// } else {
+// primary_left_items.push(left_item.into_any());
+// }
+// }
+
+// ToolbarItemLocation::PrimaryRight { flex } => {
+// primary_items_row_count = primary_items_row_count.max(item.row_count(cx));
+// let right_item = ChildView::new(item.as_any(), cx).aligned().flex_float();
+// if let Some((flex, expanded)) = flex {
+// primary_right_items.push(right_item.flex(flex, expanded).into_any());
+// } else {
+// primary_right_items.push(right_item.into_any());
+// }
+// }
+
+// ToolbarItemLocation::Secondary => {
+// secondary_item = Some(
+// ChildView::new(item.as_any(), cx)
+// .constrained()
+// .with_height(theme.height * item.row_count(cx) as f32)
+// .into_any(),
+// );
+// }
+// }
+// }
+
+// let container_style = theme.container;
+// let height = theme.height * primary_items_row_count as f32;
+
+// let mut primary_items = Flex::row().with_spacing(spacing);
+// primary_items.extend(primary_left_items);
+// primary_items.extend(primary_right_items);
+
+// let mut toolbar = Flex::column();
+// if !primary_items.is_empty() {
+// toolbar.add_child(primary_items.constrained().with_height(height));
+// }
+// if let Some(secondary_item) = secondary_item {
+// toolbar.add_child(secondary_item);
+// }
+
+// if toolbar.is_empty() {
+// toolbar.into_any_named("toolbar")
+// } else {
+// toolbar
+// .contained()
+// .with_style(container_style)
+// .into_any_named("toolbar")
+// }
+// }
+// }
+
+// <<<<<<< HEAD
+// =======
+// #[allow(clippy::too_many_arguments)]
+// fn nav_button<A: Action, F: 'static + Fn(&mut Toolbar, &mut ViewContext<Toolbar>)>(
+// svg_path: &'static str,
+// style: theme::Interactive<theme::IconButton>,
+// nav_button_height: f32,
+// tooltip_style: TooltipStyle,
+// enabled: bool,
+// spacing: f32,
+// on_click: F,
+// tooltip_action: A,
+// action_name: &'static str,
+// cx: &mut ViewContext<Toolbar>,
+// ) -> AnyElement<Toolbar> {
+// MouseEventHandler::new::<A, _>(0, cx, |state, _| {
+// let style = if enabled {
+// style.style_for(state)
+// } else {
+// style.disabled_style()
+// };
+// Svg::new(svg_path)
+// .with_color(style.color)
+// .constrained()
+// .with_width(style.icon_width)
+// .aligned()
+// .contained()
+// .with_style(style.container)
+// .constrained()
+// .with_width(style.button_width)
+// .with_height(nav_button_height)
+// .aligned()
+// .top()
+// })
+// .with_cursor_style(if enabled {
+// CursorStyle::PointingHand
+// } else {
+// CursorStyle::default()
+// })
+// .on_click(MouseButton::Left, move |_, toolbar, cx| {
+// on_click(toolbar, cx)
+// })
+// .with_tooltip::<A>(
+// 0,
+// action_name,
+// Some(Box::new(tooltip_action)),
+// tooltip_style,
+// cx,
+// )
+// .contained()
+// .with_margin_right(spacing)
+// .into_any_named("nav button")
+// }
+
+// >>>>>>> 139cbbfd3aebd0863a7d51b0c12d748764cf0b2e
+impl Toolbar {
+ pub fn new() -> Self {
+ Self {
+ active_item: None,
+ items: Default::default(),
+ hidden: false,
+ can_navigate: true,
+ }
+ }
+
+ pub fn set_can_navigate(&mut self, can_navigate: bool, cx: &mut ViewContext<Self>) {
+ self.can_navigate = can_navigate;
+ cx.notify();
+ }
+
+ pub fn add_item<T>(&mut self, item: View<T>, cx: &mut ViewContext<Self>)
+ where
+ T: 'static + ToolbarItemView,
+ {
+ let location = item.set_active_pane_item(self.active_item.as_deref(), cx);
+ cx.subscribe(&item, |this, item, event, cx| {
+ if let Some((_, current_location)) =
+ this.items.iter_mut().find(|(i, _)| i.id() == item.id())
+ {
+ let new_location = item
+ .read(cx)
+ .location_for_event(event, *current_location, cx);
+ if new_location != *current_location {
+ *current_location = new_location;
+ cx.notify();
+ }
+ }
+ })
+ .detach();
+ self.items.push((Box::new(item), location));
+ cx.notify();
+ }
+
+ pub fn set_active_item(&mut self, item: Option<&dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+ self.active_item = item.map(|item| item.boxed_clone());
+ self.hidden = self
+ .active_item
+ .as_ref()
+ .map(|item| !item.show_toolbar(cx))
+ .unwrap_or(false);
+
+ for (toolbar_item, current_location) in self.items.iter_mut() {
+ let new_location = toolbar_item.set_active_pane_item(item, cx);
+ if new_location != *current_location {
+ *current_location = new_location;
+ cx.notify();
+ }
+ }
+ }
+
+ pub fn focus_changed(&mut self, focused: bool, cx: &mut ViewContext<Self>) {
+ for (toolbar_item, _) in self.items.iter_mut() {
+ toolbar_item.focus_changed(focused, cx);
+ }
+ }
+
+ pub fn item_of_type<T: ToolbarItemView>(&self) -> Option<View<T>> {
+ self.items
+ .iter()
+ .find_map(|(item, _)| item.to_any().downcast().ok())
+ }
+
+ pub fn hidden(&self) -> bool {
+ self.hidden
+ }
+}
+
+impl<T: ToolbarItemView> ToolbarItemViewHandle for View<T> {
+ fn id(&self) -> EntityId {
+ self.entity_id()
+ }
+
+ fn to_any(&self) -> AnyView {
+ self.clone().into()
+ }
+
+ fn set_active_pane_item(
+ &self,
+ active_pane_item: Option<&dyn ItemHandle>,
+ cx: &mut WindowContext,
+ ) -> ToolbarItemLocation {
+ self.update(cx, |this, cx| {
+ this.set_active_pane_item(active_pane_item, cx)
+ })
+ }
+
+ fn focus_changed(&mut self, pane_focused: bool, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| {
+ this.pane_focus_update(pane_focused, cx);
+ cx.notify();
+ });
+ }
+
+ fn row_count(&self, cx: &WindowContext) -> usize {
+ self.read(cx).row_count(cx)
+ }
+}
+
+// todo!()
+// impl From<&dyn ToolbarItemViewHandle> for AnyViewHandle {
+// fn from(val: &dyn ToolbarItemViewHandle) -> Self {
+// val.as_any().clone()
+// }
+// }
@@ -1,86 +1,82 @@
-// pub mod dock;
+#![allow(unused_variables, dead_code, unused_mut)]
+// todo!() this is to make transition easier.
+
+pub mod dock;
pub mod item;
-// pub mod notifications;
+pub mod notifications;
pub mod pane;
pub mod pane_group;
mod persistence;
pub mod searchable;
// pub mod shared_screen;
-// mod status_bar;
+mod status_bar;
mod toolbar;
mod workspace_settings;
-use anyhow::{anyhow, Result};
-// use call2::ActiveCall;
-// use client2::{
-// proto::{self, PeerId},
-// Client, Status, TypedEnvelope, UserStore,
-// };
-// use collections::{hash_map, HashMap, HashSet};
-// use futures::{
-// channel::{mpsc, oneshot},
-// future::try_join_all,
-// FutureExt, StreamExt,
-// };
-// use gpui2::{
-// actions,
-// elements::*,
-// geometry::{
-// rect::RectF,
-// vector::{vec2f, Vector2F},
-// },
-// impl_actions,
-// platform::{
-// CursorStyle, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel,
-// WindowBounds, WindowOptions,
-// },
-// AnyModelHandle, AnyViewHandle, AnyWeakViewHandle, AnyWindowHandle, AppContext, AsyncAppContext,
-// Entity, ModelContext, ModelHandle, SizeConstraint, Subscription, Task, View, ViewContext,
-// View, WeakViewHandle, WindowContext, WindowHandle,
-// };
-// use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
-// use itertools::Itertools;
-// use language2::{LanguageRegistry, Rope};
-// use node_runtime::NodeRuntime;// //
-
-use futures::channel::oneshot;
-// use crate::{
-// notifications::{simple_message_notification::MessageNotification, NotificationTracker},
-// persistence::model::{
-// DockData, DockStructure, SerializedPane, SerializedPaneGroup, SerializedWorkspace,
-// },
-// };
-// use dock::{Dock, DockPosition, Panel, PanelButtons, PanelHandle};
-// use lazy_static::lazy_static;
-// use notifications::{NotificationHandle, NotifyResultExt};
+use crate::persistence::model::{
+ DockData, DockStructure, SerializedItem, SerializedPane, SerializedPaneGroup,
+ SerializedWorkspace,
+};
+use anyhow::{anyhow, Context as _, Result};
+use call2::ActiveCall;
+use client2::{
+ proto::{self, PeerId},
+ Client, TypedEnvelope, UserStore,
+};
+use collections::{HashMap, HashSet};
+use dock::{Dock, DockPosition, PanelButtons};
+use futures::{
+ channel::{mpsc, oneshot},
+ future::try_join_all,
+ Future, FutureExt, StreamExt,
+};
+use gpui2::{
+ div, point, size, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
+ AsyncWindowContext, Bounds, Component, Div, EntityId, EventEmitter, GlobalPixels, Model,
+ ModelContext, ParentElement, Point, Render, Size, StatefulInteractive, Styled, Subscription,
+ Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
+ WindowOptions,
+};
+use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
+use language2::LanguageRegistry;
+use lazy_static::lazy_static;
+use node_runtime::NodeRuntime;
+use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
pub use pane::*;
pub use pane_group::*;
-// use persistence::{model::SerializedItem, DB};
-// pub use persistence::{
-// model::{ItemId, WorkspaceLocation},
-// WorkspaceDb, DB as WORKSPACE_DB,
-// };
-// use postage::prelude::Stream;
-// use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
-// use serde::Deserialize;
-// use shared_screen::SharedScreen;
-// use status_bar::StatusBar;
-// pub use status_bar::StatusItemView;
-// use theme::{Theme, ThemeSettings};
+use persistence::{
+ model::{ItemId, WorkspaceLocation},
+ DB,
+};
+use postage::stream::Stream;
+use project2::{Project, ProjectEntryId, ProjectPath, Worktree};
+use serde::Deserialize;
+use settings2::Settings;
+use status_bar::StatusBar;
+use std::{
+ any::TypeId,
+ borrow::Cow,
+ env,
+ path::{Path, PathBuf},
+ sync::{atomic::AtomicUsize, Arc},
+ time::Duration,
+};
+use theme2::ActiveTheme;
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
-// use util::ResultExt;
-// pub use workspace_settings::{AutosaveSetting, GitGutterSetting, WorkspaceSettings};
-
-// lazy_static! {
-// static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
-// .ok()
-// .as_deref()
-// .and_then(parse_pixel_position_env_var);
-// static ref ZED_WINDOW_POSITION: Option<Vector2F> = env::var("ZED_WINDOW_POSITION")
-// .ok()
-// .as_deref()
-// .and_then(parse_pixel_position_env_var);
-// }
+use util::ResultExt;
+use uuid::Uuid;
+use workspace_settings::{AutosaveSetting, WorkspaceSettings};
+
+lazy_static! {
+ static ref ZED_WINDOW_SIZE: Option<Size<GlobalPixels>> = env::var("ZED_WINDOW_SIZE")
+ .ok()
+ .as_deref()
+ .and_then(parse_pixel_size_env_var);
+ static ref ZED_WINDOW_POSITION: Option<Point<GlobalPixels>> = env::var("ZED_WINDOW_POSITION")
+ .ok()
+ .as_deref()
+ .and_then(parse_pixel_position_env_var);
+}
// pub trait Modal: View {
// fn has_focus(&self) -> bool;
@@ -170,13 +166,13 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
// pub save_intent: Option<SaveIntent>,
// }
-// #[derive(Deserialize)]
-// pub struct Toast {
-// id: usize,
-// msg: Cow<'static, str>,
-// #[serde(skip)]
-// on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
-// }
+#[derive(Deserialize)]
+pub struct Toast {
+ id: usize,
+ msg: Cow<'static, str>,
+ #[serde(skip)]
+ on_click: Option<(Cow<'static, str>, Arc<dyn Fn(&mut WindowContext)>)>,
+}
// impl Toast {
// pub fn new<I: Into<Cow<'static, str>>>(id: usize, msg: I) -> Self {
@@ -237,143 +233,142 @@ pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
pub type WorkspaceId = i64;
-// pub fn init_settings(cx: &mut AppContext) {
-// settings::register::<WorkspaceSettings>(cx);
-// settings::register::<item::ItemSettings>(cx);
-// }
-
-// pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
-// init_settings(cx);
-// pane::init(cx);
-// notifications::init(cx);
-
-// cx.add_global_action({
-// let app_state = Arc::downgrade(&app_state);
-// move |_: &Open, cx: &mut AppContext| {
-// let mut paths = cx.prompt_for_paths(PathPromptOptions {
-// files: true,
-// directories: true,
-// multiple: true,
-// });
-
-// if let Some(app_state) = app_state.upgrade() {
-// cx.spawn(move |mut cx| async move {
-// if let Some(paths) = paths.recv().await.flatten() {
-// cx.update(|cx| {
-// open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
-// });
-// }
-// })
-// .detach();
-// }
-// }
-// });
-// cx.add_async_action(Workspace::open);
-
-// cx.add_async_action(Workspace::follow_next_collaborator);
-// cx.add_async_action(Workspace::close);
-// cx.add_async_action(Workspace::close_inactive_items_and_panes);
-// cx.add_async_action(Workspace::close_all_items_and_panes);
-// cx.add_global_action(Workspace::close_global);
-// cx.add_global_action(restart);
-// cx.add_async_action(Workspace::save_all);
-// cx.add_action(Workspace::add_folder_to_project);
-// cx.add_action(
-// |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
-// let pane = workspace.active_pane().clone();
-// workspace.unfollow(&pane, cx);
-// },
-// );
-// cx.add_action(
-// |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
-// workspace
-// .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
-// .detach_and_log_err(cx);
-// },
-// );
-// cx.add_action(
-// |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
-// workspace
-// .save_active_item(SaveIntent::SaveAs, cx)
-// .detach_and_log_err(cx);
-// },
-// );
-// cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
-// workspace.activate_previous_pane(cx)
-// });
-// cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
-// workspace.activate_next_pane(cx)
-// });
+pub fn init_settings(cx: &mut AppContext) {
+ WorkspaceSettings::register(cx);
+ ItemSettings::register(cx);
+}
-// cx.add_action(
-// |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
-// workspace.activate_pane_in_direction(action.0, cx)
-// },
-// );
+pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
+ init_settings(cx);
+ pane::init(cx);
+ notifications::init(cx);
+
+ // cx.add_global_action({
+ // let app_state = Arc::downgrade(&app_state);
+ // move |_: &Open, cx: &mut AppContext| {
+ // let mut paths = cx.prompt_for_paths(PathPromptOptions {
+ // files: true,
+ // directories: true,
+ // multiple: true,
+ // });
-// cx.add_action(
-// |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
-// workspace.swap_pane_in_direction(action.0, cx)
-// },
-// );
+ // if let Some(app_state) = app_state.upgrade() {
+ // cx.spawn(move |mut cx| async move {
+ // if let Some(paths) = paths.recv().await.flatten() {
+ // cx.update(|cx| {
+ // open_paths(&paths, &app_state, None, cx).detach_and_log_err(cx)
+ // });
+ // }
+ // })
+ // .detach();
+ // }
+ // }
+ // });
+ // cx.add_async_action(Workspace::open);
+
+ // cx.add_async_action(Workspace::follow_next_collaborator);
+ // cx.add_async_action(Workspace::close);
+ // cx.add_async_action(Workspace::close_inactive_items_and_panes);
+ // cx.add_async_action(Workspace::close_all_items_and_panes);
+ // cx.add_global_action(Workspace::close_global);
+ // cx.add_global_action(restart);
+ // cx.add_async_action(Workspace::save_all);
+ // cx.add_action(Workspace::add_folder_to_project);
+ // cx.add_action(
+ // |workspace: &mut Workspace, _: &Unfollow, cx: &mut ViewContext<Workspace>| {
+ // let pane = workspace.active_pane().clone();
+ // workspace.unfollow(&pane, cx);
+ // },
+ // );
+ // cx.add_action(
+ // |workspace: &mut Workspace, action: &Save, cx: &mut ViewContext<Workspace>| {
+ // workspace
+ // .save_active_item(action.save_intent.unwrap_or(SaveIntent::Save), cx)
+ // .detach_and_log_err(cx);
+ // },
+ // );
+ // cx.add_action(
+ // |workspace: &mut Workspace, _: &SaveAs, cx: &mut ViewContext<Workspace>| {
+ // workspace
+ // .save_active_item(SaveIntent::SaveAs, cx)
+ // .detach_and_log_err(cx);
+ // },
+ // );
+ // cx.add_action(|workspace: &mut Workspace, _: &ActivatePreviousPane, cx| {
+ // workspace.activate_previous_pane(cx)
+ // });
+ // cx.add_action(|workspace: &mut Workspace, _: &ActivateNextPane, cx| {
+ // workspace.activate_next_pane(cx)
+ // });
+
+ // cx.add_action(
+ // |workspace: &mut Workspace, action: &ActivatePaneInDirection, cx| {
+ // workspace.activate_pane_in_direction(action.0, cx)
+ // },
+ // );
+
+ // cx.add_action(
+ // |workspace: &mut Workspace, action: &SwapPaneInDirection, cx| {
+ // workspace.swap_pane_in_direction(action.0, cx)
+ // },
+ // );
+
+ // cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
+ // workspace.toggle_dock(DockPosition::Left, cx);
+ // });
+ // cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
+ // workspace.toggle_dock(DockPosition::Right, cx);
+ // });
+ // cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
+ // workspace.toggle_dock(DockPosition::Bottom, cx);
+ // });
+ // cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
+ // workspace.close_all_docks(cx);
+ // });
+ // cx.add_action(Workspace::activate_pane_at_index);
+ // cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
+ // workspace.reopen_closed_item(cx).detach();
+ // });
+ // cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
+ // workspace
+ // .go_back(workspace.active_pane().downgrade(), cx)
+ // .detach();
+ // });
+ // cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
+ // workspace
+ // .go_forward(workspace.active_pane().downgrade(), cx)
+ // .detach();
+ // });
-// cx.add_action(|workspace: &mut Workspace, _: &ToggleLeftDock, cx| {
-// workspace.toggle_dock(DockPosition::Left, cx);
-// });
-// cx.add_action(|workspace: &mut Workspace, _: &ToggleRightDock, cx| {
-// workspace.toggle_dock(DockPosition::Right, cx);
-// });
-// cx.add_action(|workspace: &mut Workspace, _: &ToggleBottomDock, cx| {
-// workspace.toggle_dock(DockPosition::Bottom, cx);
-// });
-// cx.add_action(|workspace: &mut Workspace, _: &CloseAllDocks, cx| {
-// workspace.close_all_docks(cx);
-// });
-// cx.add_action(Workspace::activate_pane_at_index);
-// cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| {
-// workspace.reopen_closed_item(cx).detach();
-// });
-// cx.add_action(|workspace: &mut Workspace, _: &GoBack, cx| {
-// workspace
-// .go_back(workspace.active_pane().downgrade(), cx)
-// .detach();
-// });
-// cx.add_action(|workspace: &mut Workspace, _: &GoForward, cx| {
-// workspace
-// .go_forward(workspace.active_pane().downgrade(), cx)
-// .detach();
-// });
+ // cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
+ // cx.spawn(|workspace, mut cx| async move {
+ // let err = install_cli::install_cli(&cx)
+ // .await
+ // .context("Failed to create CLI symlink");
-// cx.add_action(|_: &mut Workspace, _: &install_cli::Install, cx| {
-// cx.spawn(|workspace, mut cx| async move {
-// let err = install_cli::install_cli(&cx)
-// .await
-// .context("Failed to create CLI symlink");
-
-// workspace.update(&mut cx, |workspace, cx| {
-// if matches!(err, Err(_)) {
-// err.notify_err(workspace, cx);
-// } else {
-// workspace.show_notification(1, cx, |cx| {
-// cx.add_view(|_| {
-// MessageNotification::new("Successfully installed the `zed` binary")
-// })
-// });
-// }
-// })
-// })
-// .detach();
-// });
-// }
+ // workspace.update(&mut cx, |workspace, cx| {
+ // if matches!(err, Err(_)) {
+ // err.notify_err(workspace, cx);
+ // } else {
+ // workspace.show_notification(1, cx, |cx| {
+ // cx.build_view(|_| {
+ // MessageNotification::new("Successfully installed the `zed` binary")
+ // })
+ // });
+ // }
+ // })
+ // })
+ // .detach();
+ // });
+}
type ProjectItemBuilders =
- HashMap<TypeId, fn(Handle<Project>, AnyHandle, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
+ HashMap<TypeId, fn(Model<Project>, AnyModel, &mut ViewContext<Pane>) -> Box<dyn ItemHandle>>;
pub fn register_project_item<I: ProjectItem>(cx: &mut AppContext) {
- cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
- builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
- let item = model.downcast::<I::Item>().unwrap();
- Box::new(cx.add_view(|cx| I::for_project_item(project, item, cx)))
- });
+ let builders = cx.default_global::<ProjectItemBuilders>();
+ builders.insert(TypeId::of::<I::Item>(), |project, model, cx| {
+ let item = model.downcast::<I::Item>().unwrap();
+ Box::new(cx.build_view(|cx| I::for_project_item(project, item, cx)))
});
}
@@ -392,26 +387,25 @@ type FollowableItemBuilders = HashMap<
),
>;
pub fn register_followable_item<I: FollowableItem>(cx: &mut AppContext) {
- cx.update_default_global(|builders: &mut FollowableItemBuilders, _| {
- builders.insert(
- TypeId::of::<I>(),
- (
- |pane, workspace, id, state, cx| {
- I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
- cx.foreground()
- .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
- })
- },
- |this| Box::new(this.clone().downcast::<I>().unwrap()),
- ),
- );
- });
+ let builders = cx.default_global::<FollowableItemBuilders>();
+ builders.insert(
+ TypeId::of::<I>(),
+ (
+ |pane, workspace, id, state, cx| {
+ I::from_state_proto(pane, workspace, id, state, cx).map(|task| {
+ cx.foreground_executor()
+ .spawn(async move { Ok(Box::new(task.await?) as Box<_>) })
+ })
+ },
+ |this| Box::new(this.clone().downcast::<I>().unwrap()),
+ ),
+ );
}
type ItemDeserializers = HashMap<
Arc<str>,
fn(
- Handle<Project>,
+ Model<Project>,
WeakView<Workspace>,
WorkspaceId,
ItemId,
@@ -419,13 +413,13 @@ type ItemDeserializers = HashMap<
) -> Task<Result<Box<dyn ItemHandle>>>,
>;
pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
- cx.update_default_global(|deserializers: &mut ItemDeserializers, _cx| {
+ cx.update_global(|deserializers: &mut ItemDeserializers, _cx| {
if let Some(serialized_item_kind) = I::serialized_item_kind() {
deserializers.insert(
Arc::from(serialized_item_kind),
|project, workspace, workspace_id, item_id, cx| {
let task = I::deserialize(project, workspace, workspace_id, item_id, cx);
- cx.foreground()
+ cx.foreground_executor()
.spawn(async { Ok(Box::new(task.await?) as Box<_>) })
},
);
@@ -436,18 +430,22 @@ pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
pub struct AppState {
pub languages: Arc<LanguageRegistry>,
pub client: Arc<Client>,
- pub user_store: Handle<UserStore>,
- pub workspace_store: Handle<WorkspaceStore>,
+ pub user_store: Model<UserStore>,
+ pub workspace_store: Model<WorkspaceStore>,
pub fs: Arc<dyn fs2::Fs>,
pub build_window_options:
- fn(Option<WindowBounds>, Option<DisplayId>, &MainThread<AppContext>) -> WindowOptions,
- pub initialize_workspace:
- fn(WeakHandle<Workspace>, bool, Arc<AppState>, AsyncAppContext) -> Task<anyhow::Result<()>>,
+ fn(Option<WindowBounds>, Option<Uuid>, &mut AppContext) -> WindowOptions,
+ pub initialize_workspace: fn(
+ WeakView<Workspace>,
+ bool,
+ Arc<AppState>,
+ AsyncWindowContext,
+ ) -> Task<anyhow::Result<()>>,
pub node_runtime: Arc<dyn NodeRuntime>,
}
pub struct WorkspaceStore {
- workspaces: HashSet<WeakHandle<Workspace>>,
+ workspaces: HashSet<WindowHandle<Workspace>>,
followers: Vec<Follower>,
client: Arc<Client>,
_subscriptions: Vec<client2::Subscription>,
@@ -459,40 +457,42 @@ struct Follower {
peer_id: PeerId,
}
-// impl AppState {
-// #[cfg(any(test, feature = "test-support"))]
-// pub fn test(cx: &mut AppContext) -> Arc<Self> {
-// use node_runtime::FakeNodeRuntime;
-// use settings::SettingsStore;
+impl AppState {
+ #[cfg(any(test, feature = "test-support"))]
+ pub fn test(cx: &mut AppContext) -> Arc<Self> {
+ use gpui2::Context;
+ use node_runtime::FakeNodeRuntime;
+ use settings2::SettingsStore;
-// if !cx.has_global::<SettingsStore>() {
-// cx.set_global(SettingsStore::test(cx));
-// }
+ if !cx.has_global::<SettingsStore>() {
+ let settings_store = SettingsStore::test(cx);
+ cx.set_global(settings_store);
+ }
-// let fs = fs::FakeFs::new(cx.background().clone());
-// let languages = Arc::new(LanguageRegistry::test());
-// let http_client = util::http::FakeHttpClient::with_404_response();
-// let client = Client::new(http_client.clone(), cx);
-// let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-// let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
-
-// theme::init((), cx);
-// client::init(&client, cx);
-// crate::init_settings(cx);
-
-// Arc::new(Self {
-// client,
-// fs,
-// languages,
-// user_store,
-// // channel_store,
-// workspace_store,
-// node_runtime: FakeNodeRuntime::new(),
-// initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
-// build_window_options: |_, _, _| Default::default(),
-// })
-// }
-// }
+ let fs = fs2::FakeFs::new(cx.background_executor().clone());
+ let languages = Arc::new(LanguageRegistry::test());
+ let http_client = util::http::FakeHttpClient::with_404_response();
+ let client = Client::new(http_client.clone(), cx);
+ let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http_client, cx));
+ let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
+
+ // todo!()
+ // theme::init((), cx);
+ client2::init(&client, cx);
+ crate::init_settings(cx);
+
+ Arc::new(Self {
+ client,
+ fs,
+ languages,
+ user_store,
+ workspace_store,
+ node_runtime: FakeNodeRuntime::new(),
+ initialize_workspace: |_, _, _, _| Task::ready(Ok(())),
+ build_window_options: |_, _, _| Default::default(),
+ })
+ }
+}
struct DelayedDebouncedEditAction {
task: Option<Task<()>>,
@@ -509,7 +509,7 @@ impl DelayedDebouncedEditAction {
fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Workspace>, func: F)
where
- F: 'static + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
+ F: 'static + Send + FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> Task<Result<()>>,
{
if let Some(channel) = self.cancel_channel.take() {
_ = channel.send(());
@@ -519,8 +519,8 @@ impl DelayedDebouncedEditAction {
self.cancel_channel = Some(sender);
let previous_task = self.task.take();
- self.task = Some(cx.spawn(|workspace, mut cx| async move {
- let mut timer = cx.background().timer(delay).fuse();
+ self.task = Some(cx.spawn(move |workspace, mut cx| async move {
+ let mut timer = cx.background_executor().timer(delay).fuse();
if let Some(previous_task) = previous_task {
previous_task.await;
}
@@ -540,41 +540,41 @@ impl DelayedDebouncedEditAction {
}
}
-// pub enum Event {
-// PaneAdded(View<Pane>),
-// ContactRequestedJoin(u64),
-// }
+pub enum Event {
+ PaneAdded(View<Pane>),
+ ContactRequestedJoin(u64),
+}
pub struct Workspace {
- weak_self: WeakHandle<Self>,
+ weak_self: WeakView<Self>,
// modal: Option<ActiveModal>,
- // zoomed: Option<AnyWeakViewHandle>,
+ zoomed: Option<AnyWeakView>,
// zoomed_position: Option<DockPosition>,
- // center: PaneGroup,
- // left_dock: View<Dock>,
- // bottom_dock: View<Dock>,
- // right_dock: View<Dock>,
+ center: PaneGroup,
+ left_dock: View<Dock>,
+ bottom_dock: View<Dock>,
+ right_dock: View<Dock>,
panes: Vec<View<Pane>>,
- // panes_by_item: HashMap<usize, WeakViewHandle<Pane>>,
- // active_pane: View<Pane>,
+ panes_by_item: HashMap<EntityId, WeakView<Pane>>,
+ active_pane: View<Pane>,
last_active_center_pane: Option<WeakView<Pane>>,
- // last_active_view_id: Option<proto::ViewId>,
- // status_bar: View<StatusBar>,
+ last_active_view_id: Option<proto::ViewId>,
+ status_bar: View<StatusBar>,
// titlebar_item: Option<AnyViewHandle>,
- // notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
- project: Handle<Project>,
- // follower_states: HashMap<View<Pane>, FollowerState>,
- // last_leaders_by_pane: HashMap<WeakViewHandle<Pane>, PeerId>,
- // window_edited: bool,
- // active_call: Option<(ModelHandle<ActiveCall>, Vec<Subscription>)>,
- // leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
- // database_id: WorkspaceId,
+ notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
+ project: Model<Project>,
+ follower_states: HashMap<View<Pane>, FollowerState>,
+ last_leaders_by_pane: HashMap<WeakView<Pane>, PeerId>,
+ window_edited: bool,
+ active_call: Option<(Model<ActiveCall>, Vec<Subscription>)>,
+ leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
+ database_id: WorkspaceId,
app_state: Arc<AppState>,
- // subscriptions: Vec<Subscription>,
- // _apply_leader_updates: Task<Result<()>>,
- // _observe_current_user: Task<Result<()>>,
- // _schedule_serialize: Option<Task<()>>,
- // pane_history_timestamp: Arc<AtomicUsize>,
+ subscriptions: Vec<Subscription>,
+ _apply_leader_updates: Task<Result<()>>,
+ _observe_current_user: Task<Result<()>>,
+ _schedule_serialize: Option<Task<()>>,
+ pane_history_timestamp: Arc<AtomicUsize>,
}
// struct ActiveModal {
@@ -582,11 +582,11 @@ pub struct Workspace {
// previously_focused_view_id: Option<usize>,
// }
-// #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-// pub struct ViewId {
-// pub creator: PeerId,
-// pub id: u64,
-// }
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub struct ViewId {
+ pub creator: PeerId,
+ pub id: u64,
+}
#[derive(Default)]
struct FollowerState {
@@ -595,353 +595,362 @@ struct FollowerState {
items_by_leader_view_id: HashMap<ViewId, Box<dyn FollowableItemHandle>>,
}
-// enum WorkspaceBounds {}
+enum WorkspaceBounds {}
impl Workspace {
- // pub fn new(
- // workspace_id: WorkspaceId,
- // project: ModelHandle<Project>,
- // app_state: Arc<AppState>,
- // cx: &mut ViewContext<Self>,
- // ) -> Self {
- // cx.observe(&project, |_, _, cx| cx.notify()).detach();
- // cx.subscribe(&project, move |this, _, event, cx| {
- // match event {
- // project::Event::RemoteIdChanged(_) => {
- // this.update_window_title(cx);
- // }
-
- // project::Event::CollaboratorLeft(peer_id) => {
- // this.collaborator_left(*peer_id, cx);
- // }
-
- // project::Event::WorktreeRemoved(_) | project::Event::WorktreeAdded => {
- // this.update_window_title(cx);
- // this.serialize_workspace(cx);
- // }
-
- // project::Event::DisconnectedFromHost => {
- // this.update_window_edited(cx);
- // cx.blur();
- // }
-
- // project::Event::Closed => {
- // cx.remove_window();
- // }
-
- // project::Event::DeletedEntry(entry_id) => {
- // for pane in this.panes.iter() {
- // pane.update(cx, |pane, cx| {
- // pane.handle_deleted_project_item(*entry_id, cx)
- // });
- // }
- // }
-
- // project::Event::Notification(message) => this.show_notification(0, cx, |cx| {
- // cx.add_view(|_| MessageNotification::new(message.clone()))
- // }),
-
- // _ => {}
- // }
- // cx.notify()
- // })
- // .detach();
-
- // let weak_handle = cx.weak_handle();
- // let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
-
- // let center_pane = cx.add_view(|cx| {
- // Pane::new(
- // weak_handle.clone(),
- // project.clone(),
- // pane_history_timestamp.clone(),
- // cx,
- // )
- // });
- // cx.subscribe(¢er_pane, Self::handle_pane_event).detach();
- // cx.focus(¢er_pane);
- // cx.emit(Event::PaneAdded(center_pane.clone()));
-
- // app_state.workspace_store.update(cx, |store, _| {
- // store.workspaces.insert(weak_handle.clone());
- // });
-
- // let mut current_user = app_state.user_store.read(cx).watch_current_user();
- // let mut connection_status = app_state.client.status();
- // let _observe_current_user = cx.spawn(|this, mut cx| async move {
- // current_user.recv().await;
- // connection_status.recv().await;
- // let mut stream =
- // Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
+ pub fn new(
+ workspace_id: WorkspaceId,
+ project: Model<Project>,
+ app_state: Arc<AppState>,
+ cx: &mut ViewContext<Self>,
+ ) -> Self {
+ cx.observe(&project, |_, _, cx| cx.notify()).detach();
+ cx.subscribe(&project, move |this, _, event, cx| {
+ match event {
+ project2::Event::RemoteIdChanged(_) => {
+ this.update_window_title(cx);
+ }
+
+ project2::Event::CollaboratorLeft(peer_id) => {
+ this.collaborator_left(*peer_id, cx);
+ }
+
+ project2::Event::WorktreeRemoved(_) | project2::Event::WorktreeAdded => {
+ this.update_window_title(cx);
+ this.serialize_workspace(cx);
+ }
+
+ project2::Event::DisconnectedFromHost => {
+ this.update_window_edited(cx);
+ cx.blur();
+ }
+
+ project2::Event::Closed => {
+ // cx.remove_window();
+ }
+
+ project2::Event::DeletedEntry(entry_id) => {
+ for pane in this.panes.iter() {
+ pane.update(cx, |pane, cx| {
+ pane.handle_deleted_project_item(*entry_id, cx)
+ });
+ }
+ }
- // while stream.recv().await.is_some() {
- // this.update(&mut cx, |_, cx| cx.notify())?;
- // }
- // anyhow::Ok(())
- // });
+ project2::Event::Notification(message) => this.show_notification(0, cx, |cx| {
+ cx.build_view(|_| MessageNotification::new(message.clone()))
+ }),
- // // All leader updates are enqueued and then processed in a single task, so
- // // that each asynchronous operation can be run in order.
- // let (leader_updates_tx, mut leader_updates_rx) =
- // mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
- // let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
- // while let Some((leader_id, update)) = leader_updates_rx.next().await {
- // Self::process_leader_update(&this, leader_id, update, &mut cx)
- // .await
- // .log_err();
- // }
+ _ => {}
+ }
+ cx.notify()
+ })
+ .detach();
+
+ let weak_handle = cx.view().downgrade();
+ let pane_history_timestamp = Arc::new(AtomicUsize::new(0));
+
+ let center_pane = cx.build_view(|cx| {
+ Pane::new(
+ weak_handle.clone(),
+ project.clone(),
+ pane_history_timestamp.clone(),
+ cx,
+ )
+ });
+ cx.subscribe(¢er_pane, Self::handle_pane_event).detach();
+ // todo!()
+ // cx.focus(¢er_pane);
+ cx.emit(Event::PaneAdded(center_pane.clone()));
+
+ let window_handle = cx.window_handle().downcast::<Workspace>().unwrap();
+ app_state.workspace_store.update(cx, |store, _| {
+ store.workspaces.insert(window_handle);
+ });
- // Ok(())
- // });
+ let mut current_user = app_state.user_store.read(cx).watch_current_user();
+ let mut connection_status = app_state.client.status();
+ let _observe_current_user = cx.spawn(|this, mut cx| async move {
+ current_user.next().await;
+ connection_status.next().await;
+ let mut stream =
+ Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
- // cx.emit_global(WorkspaceCreated(weak_handle.clone()));
-
- // let left_dock = cx.add_view(|_| Dock::new(DockPosition::Left));
- // let bottom_dock = cx.add_view(|_| Dock::new(DockPosition::Bottom));
- // let right_dock = cx.add_view(|_| Dock::new(DockPosition::Right));
- // let left_dock_buttons =
- // cx.add_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
- // let bottom_dock_buttons =
- // cx.add_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
- // let right_dock_buttons =
- // cx.add_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
- // let status_bar = cx.add_view(|cx| {
- // let mut status_bar = StatusBar::new(¢er_pane.clone(), cx);
- // status_bar.add_left_item(left_dock_buttons, cx);
- // status_bar.add_right_item(right_dock_buttons, cx);
- // status_bar.add_right_item(bottom_dock_buttons, cx);
- // status_bar
- // });
+ while stream.recv().await.is_some() {
+ this.update(&mut cx, |_, cx| cx.notify())?;
+ }
+ anyhow::Ok(())
+ });
- // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
- // drag_and_drop.register_container(weak_handle.clone());
- // });
+ // All leader updates are enqueued and then processed in a single task, so
+ // that each asynchronous operation can be run in order.
+ let (leader_updates_tx, mut leader_updates_rx) =
+ mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
+ let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
+ while let Some((leader_id, update)) = leader_updates_rx.next().await {
+ Self::process_leader_update(&this, leader_id, update, &mut cx)
+ .await
+ .log_err();
+ }
- // let mut active_call = None;
- // if cx.has_global::<ModelHandle<ActiveCall>>() {
- // let call = cx.global::<ModelHandle<ActiveCall>>().clone();
- // let mut subscriptions = Vec::new();
- // subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
- // active_call = Some((call, subscriptions));
- // }
+ Ok(())
+ });
- // let subscriptions = vec![
- // cx.observe_fullscreen(|_, _, cx| cx.notify()),
- // cx.observe_window_activation(Self::on_window_activation_changed),
- // cx.observe_window_bounds(move |_, mut bounds, display, cx| {
- // // Transform fixed bounds to be stored in terms of the containing display
- // if let WindowBounds::Fixed(mut window_bounds) = bounds {
- // if let Some(screen) = cx.platform().screen_by_id(display) {
- // let screen_bounds = screen.bounds();
- // window_bounds
- // .set_origin_x(window_bounds.origin_x() - screen_bounds.origin_x());
- // window_bounds
- // .set_origin_y(window_bounds.origin_y() - screen_bounds.origin_y());
- // bounds = WindowBounds::Fixed(window_bounds);
- // }
- // }
+ // todo!("replace with a different mechanism")
+ // cx.emit_global(WorkspaceCreated(weak_handle.clone()));
+
+ let left_dock = cx.build_view(|_| Dock::new(DockPosition::Left));
+ let bottom_dock = cx.build_view(|_| Dock::new(DockPosition::Bottom));
+ let right_dock = cx.build_view(|_| Dock::new(DockPosition::Right));
+ let left_dock_buttons =
+ cx.build_view(|cx| PanelButtons::new(left_dock.clone(), weak_handle.clone(), cx));
+ let bottom_dock_buttons =
+ cx.build_view(|cx| PanelButtons::new(bottom_dock.clone(), weak_handle.clone(), cx));
+ let right_dock_buttons =
+ cx.build_view(|cx| PanelButtons::new(right_dock.clone(), weak_handle.clone(), cx));
+ let status_bar = cx.build_view(|cx| {
+ let mut status_bar = StatusBar::new(¢er_pane.clone(), cx);
+ status_bar.add_left_item(left_dock_buttons, cx);
+ status_bar.add_right_item(right_dock_buttons, cx);
+ status_bar.add_right_item(bottom_dock_buttons, cx);
+ status_bar
+ });
- // cx.background()
- // .spawn(DB.set_window_bounds(workspace_id, bounds, display))
- // .detach_and_log_err(cx);
- // }),
- // cx.observe(&left_dock, |this, _, cx| {
- // this.serialize_workspace(cx);
- // cx.notify();
- // }),
- // cx.observe(&bottom_dock, |this, _, cx| {
- // this.serialize_workspace(cx);
- // cx.notify();
- // }),
- // cx.observe(&right_dock, |this, _, cx| {
- // this.serialize_workspace(cx);
- // cx.notify();
- // }),
- // ];
-
- // cx.defer(|this, cx| this.update_window_title(cx));
- // Workspace {
- // weak_self: weak_handle.clone(),
- // modal: None,
- // zoomed: None,
- // zoomed_position: None,
- // center: PaneGroup::new(center_pane.clone()),
- // panes: vec![center_pane.clone()],
- // panes_by_item: Default::default(),
- // active_pane: center_pane.clone(),
- // last_active_center_pane: Some(center_pane.downgrade()),
- // last_active_view_id: None,
- // status_bar,
- // titlebar_item: None,
- // notifications: Default::default(),
- // left_dock,
- // bottom_dock,
- // right_dock,
- // project: project.clone(),
- // follower_states: Default::default(),
- // last_leaders_by_pane: Default::default(),
- // window_edited: false,
- // active_call,
- // database_id: workspace_id,
- // app_state,
- // _observe_current_user,
- // _apply_leader_updates,
- // _schedule_serialize: None,
- // leader_updates_tx,
- // subscriptions,
- // pane_history_timestamp,
- // }
- // }
+ // todo!()
+ // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
+ // drag_and_drop.register_container(weak_handle.clone());
+ // });
+
+ let mut active_call = None;
+ if cx.has_global::<Model<ActiveCall>>() {
+ let call = cx.global::<Model<ActiveCall>>().clone();
+ let mut subscriptions = Vec::new();
+ subscriptions.push(cx.subscribe(&call, Self::on_active_call_event));
+ active_call = Some((call, subscriptions));
+ }
- // fn new_local(
- // abs_paths: Vec<PathBuf>,
- // app_state: Arc<AppState>,
- // requesting_window: Option<WindowHandle<Workspace>>,
- // cx: &mut AppContext,
- // ) -> Task<(
- // WeakViewHandle<Workspace>,
- // Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
- // )> {
- // let project_handle = Project::local(
- // app_state.client.clone(),
- // app_state.node_runtime.clone(),
- // app_state.user_store.clone(),
- // app_state.languages.clone(),
- // app_state.fs.clone(),
- // cx,
- // );
+ let subscriptions = vec![
+ cx.observe_window_activation(Self::on_window_activation_changed),
+ cx.observe_window_bounds(move |_, cx| {
+ if let Some(display) = cx.display() {
+ // Transform fixed bounds to be stored in terms of the containing display
+ let mut bounds = cx.window_bounds();
+ if let WindowBounds::Fixed(window_bounds) = &mut bounds {
+ let display_bounds = display.bounds();
+ window_bounds.origin.x -= display_bounds.origin.x;
+ window_bounds.origin.y -= display_bounds.origin.y;
+ }
- // cx.spawn(|mut cx| async move {
- // let serialized_workspace = persistence::DB.workspace_for_roots(&abs_paths.as_slice());
-
- // let paths_to_open = Arc::new(abs_paths);
-
- // // Get project paths for all of the abs_paths
- // let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
- // let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
- // Vec::with_capacity(paths_to_open.len());
- // for path in paths_to_open.iter().cloned() {
- // if let Some((worktree, project_entry)) = cx
- // .update(|cx| {
- // Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
- // })
- // .await
- // .log_err()
- // {
- // worktree_roots.insert(worktree.read_with(&mut cx, |tree, _| tree.abs_path()));
- // project_paths.push((path, Some(project_entry)));
- // } else {
- // project_paths.push((path, None));
- // }
- // }
+ if let Some(display_uuid) = display.uuid().log_err() {
+ cx.background_executor()
+ .spawn(DB.set_window_bounds(workspace_id, bounds, display_uuid))
+ .detach_and_log_err(cx);
+ }
+ }
+ cx.notify();
+ }),
+ cx.observe(&left_dock, |this, _, cx| {
+ this.serialize_workspace(cx);
+ cx.notify();
+ }),
+ cx.observe(&bottom_dock, |this, _, cx| {
+ this.serialize_workspace(cx);
+ cx.notify();
+ }),
+ cx.observe(&right_dock, |this, _, cx| {
+ this.serialize_workspace(cx);
+ cx.notify();
+ }),
+ ];
+
+ cx.defer(|this, cx| this.update_window_title(cx));
+ Workspace {
+ weak_self: weak_handle.clone(),
+ // modal: None,
+ zoomed: None,
+ // zoomed_position: None,
+ center: PaneGroup::new(center_pane.clone()),
+ panes: vec![center_pane.clone()],
+ panes_by_item: Default::default(),
+ active_pane: center_pane.clone(),
+ last_active_center_pane: Some(center_pane.downgrade()),
+ last_active_view_id: None,
+ status_bar,
+ // titlebar_item: None,
+ notifications: Default::default(),
+ left_dock,
+ bottom_dock,
+ right_dock,
+ project: project.clone(),
+ follower_states: Default::default(),
+ last_leaders_by_pane: Default::default(),
+ window_edited: false,
+ active_call,
+ database_id: workspace_id,
+ app_state,
+ _observe_current_user,
+ _apply_leader_updates,
+ _schedule_serialize: None,
+ leader_updates_tx,
+ subscriptions,
+ pane_history_timestamp,
+ }
+ }
- // let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
- // serialized_workspace.id
- // } else {
- // DB.next_id().await.unwrap_or(0)
- // };
+ fn new_local(
+ abs_paths: Vec<PathBuf>,
+ app_state: Arc<AppState>,
+ _requesting_window: Option<WindowHandle<Workspace>>,
+ cx: &mut AppContext,
+ ) -> Task<
+ anyhow::Result<(
+ WindowHandle<Workspace>,
+ Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
+ )>,
+ > {
+ let project_handle = Project::local(
+ app_state.client.clone(),
+ app_state.node_runtime.clone(),
+ app_state.user_store.clone(),
+ app_state.languages.clone(),
+ app_state.fs.clone(),
+ cx,
+ );
- // let window = if let Some(window) = requesting_window {
- // window.replace_root(&mut cx, |cx| {
- // Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
- // });
- // window
- // } else {
- // {
- // let window_bounds_override = window_bounds_env_override(&cx);
- // let (bounds, display) = if let Some(bounds) = window_bounds_override {
- // (Some(bounds), None)
- // } else {
- // serialized_workspace
- // .as_ref()
- // .and_then(|serialized_workspace| {
- // let display = serialized_workspace.display?;
- // let mut bounds = serialized_workspace.bounds?;
-
- // // Stored bounds are relative to the containing display.
- // // So convert back to global coordinates if that screen still exists
- // if let WindowBounds::Fixed(mut window_bounds) = bounds {
- // if let Some(screen) = cx.platform().screen_by_id(display) {
- // let screen_bounds = screen.bounds();
- // window_bounds.set_origin_x(
- // window_bounds.origin_x() + screen_bounds.origin_x(),
- // );
- // window_bounds.set_origin_y(
- // window_bounds.origin_y() + screen_bounds.origin_y(),
- // );
- // bounds = WindowBounds::Fixed(window_bounds);
- // } else {
- // // Screen no longer exists. Return none here.
- // return None;
- // }
- // }
-
- // Some((bounds, display))
- // })
- // .unzip()
- // };
-
- // // Use the serialized workspace to construct the new window
- // cx.add_window(
- // (app_state.build_window_options)(bounds, display, cx.platform().as_ref()),
- // |cx| {
- // Workspace::new(
- // workspace_id,
- // project_handle.clone(),
- // app_state.clone(),
- // cx,
- // )
- // },
- // )
- // }
- // };
+ cx.spawn(|mut cx| async move {
+ let serialized_workspace: Option<SerializedWorkspace> = None; //persistence::DB.workspace_for_roots(&abs_paths.as_slice());
+
+ let paths_to_open = Arc::new(abs_paths);
+
+ // Get project paths for all of the abs_paths
+ let mut worktree_roots: HashSet<Arc<Path>> = Default::default();
+ let mut project_paths: Vec<(PathBuf, Option<ProjectPath>)> =
+ Vec::with_capacity(paths_to_open.len());
+ for path in paths_to_open.iter().cloned() {
+ if let Some((worktree, project_entry)) = cx
+ .update(|cx| {
+ Workspace::project_path_for_path(project_handle.clone(), &path, true, cx)
+ })?
+ .await
+ .log_err()
+ {
+ worktree_roots.extend(worktree.update(&mut cx, |tree, _| tree.abs_path()).ok());
+ project_paths.push((path, Some(project_entry)));
+ } else {
+ project_paths.push((path, None));
+ }
+ }
- // // We haven't yielded the main thread since obtaining the window handle,
- // // so the window exists.
- // let workspace = window.root(&cx).unwrap();
+ let workspace_id = if let Some(serialized_workspace) = serialized_workspace.as_ref() {
+ serialized_workspace.id
+ } else {
+ DB.next_id().await.unwrap_or(0)
+ };
- // (app_state.initialize_workspace)(
- // workspace.downgrade(),
- // serialized_workspace.is_some(),
- // app_state.clone(),
- // cx.clone(),
- // )
- // .await
- // .log_err();
+ // todo!()
+ let window = /*if let Some(window) = requesting_window {
+ cx.update_window(window.into(), |old_workspace, cx| {
+ cx.replace_root_view(|cx| {
+ Workspace::new(workspace_id, project_handle.clone(), app_state.clone(), cx)
+ });
+ });
+ window
+ } else */ {
+ let window_bounds_override = window_bounds_env_override(&cx);
+ let (bounds, display) = if let Some(bounds) = window_bounds_override {
+ (Some(bounds), None)
+ } else {
+ serialized_workspace
+ .as_ref()
+ .and_then(|serialized_workspace| {
+ let serialized_display = serialized_workspace.display?;
+ let mut bounds = serialized_workspace.bounds?;
+
+ // Stored bounds are relative to the containing display.
+ // So convert back to global coordinates if that screen still exists
+ if let WindowBounds::Fixed(mut window_bounds) = bounds {
+ let screen =
+ cx.update(|cx|
+ cx.displays()
+ .into_iter()
+ .find(|display| display.uuid().ok() == Some(serialized_display))
+ ).ok()??;
+ let screen_bounds = screen.bounds();
+ window_bounds.origin.x += screen_bounds.origin.x;
+ window_bounds.origin.y += screen_bounds.origin.y;
+ bounds = WindowBounds::Fixed(window_bounds);
+ }
+
+ Some((bounds, serialized_display))
+ })
+ .unzip()
+ };
- // window.update(&mut cx, |cx| cx.activate_window());
+ // Use the serialized workspace to construct the new window
+ let options =
+ cx.update(|cx| (app_state.build_window_options)(bounds, display, cx))?;
+
+ cx.open_window(options, {
+ let app_state = app_state.clone();
+ let workspace_id = workspace_id.clone();
+ let project_handle = project_handle.clone();
+ move |cx| {
+ cx.build_view(|cx| {
+ Workspace::new(workspace_id, project_handle, app_state, cx)
+ })
+ }
+ })?
+ };
- // let workspace = workspace.downgrade();
- // notify_if_database_failed(&workspace, &mut cx);
- // let opened_items = open_items(
- // serialized_workspace,
- // &workspace,
- // project_paths,
- // app_state,
- // cx,
- // )
- // .await
- // .unwrap_or_default();
+ // todo!() Ask how to do this
+ let weak_view = window.update(&mut cx, |_, cx| cx.view().downgrade())?;
+ let async_cx = window.update(&mut cx, |_, cx| cx.to_async())?;
+
+ (app_state.initialize_workspace)(
+ weak_view,
+ serialized_workspace.is_some(),
+ app_state.clone(),
+ async_cx,
+ )
+ .await
+ .log_err();
+
+ window
+ .update(&mut cx, |_, cx| cx.activate_window())
+ .log_err();
+
+ notify_if_database_failed(window, &mut cx);
+ let opened_items = window
+ .update(&mut cx, |_workspace, cx| {
+ open_items(
+ serialized_workspace,
+ project_paths,
+ app_state,
+ cx,
+ )
+ })?
+ .await
+ .unwrap_or_default();
- // (workspace, opened_items)
- // })
- // }
+ Ok((window, opened_items))
+ })
+ }
- // pub fn weak_handle(&self) -> WeakViewHandle<Self> {
- // self.weak_self.clone()
- // }
+ pub fn weak_handle(&self) -> WeakView<Self> {
+ self.weak_self.clone()
+ }
- // pub fn left_dock(&self) -> &View<Dock> {
- // &self.left_dock
- // }
+ pub fn left_dock(&self) -> &View<Dock> {
+ &self.left_dock
+ }
- // pub fn bottom_dock(&self) -> &View<Dock> {
- // &self.bottom_dock
- // }
+ pub fn bottom_dock(&self) -> &View<Dock> {
+ &self.bottom_dock
+ }
- // pub fn right_dock(&self) -> &View<Dock> {
- // &self.right_dock
- // }
+ pub fn right_dock(&self) -> &View<Dock> {
+ &self.right_dock
+ }
// pub fn add_panel<T: Panel>(&mut self, panel: View<T>, cx: &mut ViewContext<Self>)
// where
@@ -0,0 +1,56 @@
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings2::Settings;
+
+#[derive(Deserialize)]
+pub struct WorkspaceSettings {
+ pub active_pane_magnification: f32,
+ pub confirm_quit: bool,
+ pub show_call_status_icon: bool,
+ pub autosave: AutosaveSetting,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+pub struct WorkspaceSettingsContent {
+ pub active_pane_magnification: Option<f32>,
+ pub confirm_quit: Option<bool>,
+ pub show_call_status_icon: Option<bool>,
+ pub autosave: Option<AutosaveSetting>,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum AutosaveSetting {
+ Off,
+ AfterDelay { milliseconds: u64 },
+ OnFocusChange,
+ OnWindowChange,
+}
+
+#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+pub struct GitSettings {
+ pub git_gutter: Option<GitGutterSetting>,
+ pub gutter_debounce: Option<u64>,
+}
+
+#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum GitGutterSetting {
+ #[default]
+ TrackedFiles,
+ Hide,
+}
+
+impl Settings for WorkspaceSettings {
+ const KEY: Option<&'static str> = None;
+
+ type FileContent = WorkspaceSettingsContent;
+
+ fn load(
+ default_value: &Self::FileContent,
+ user_values: &[&Self::FileContent],
+ _: &mut gpui2::AppContext,
+ ) -> anyhow::Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+}
@@ -70,7 +70,7 @@ theme = { package = "theme2", path = "../theme2" }
util = { path = "../util" }
# semantic_index = { path = "../semantic_index" }
# vim = { path = "../vim" }
-# workspace = { path = "../workspace" }
+workspace2 = { path = "../workspace2" }
# welcome = { path = "../welcome" }
# zed-actions = {path = "../zed-actions"}
anyhow.workspace = true
@@ -1,3 +1,6 @@
+#![allow(unused_variables, dead_code, unused_mut)]
+// todo!() this is to make transition easier.
+
// Allow binary to be called Zed for a nice application menu when running executable directly
#![allow(non_snake_case)]
@@ -12,7 +15,7 @@ use client::UserStore;
use db::kvp::KEY_VALUE_STORE;
use fs::RealFs;
use futures::{channel::mpsc, SinkExt, StreamExt};
-use gpui::{App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
+use gpui::{Action, App, AppContext, AsyncAppContext, Context, SemanticVersion, Task};
use isahc::{prelude::Configurable, Request};
use language::LanguageRegistry;
use log::LevelFilter;
@@ -40,13 +43,15 @@ use std::{
time::{SystemTime, UNIX_EPOCH},
};
use util::{
+ async_maybe,
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
http::{self, HttpClient},
paths, ResultExt,
};
use uuid::Uuid;
-use zed2::languages;
-use zed2::{ensure_only_instance, AppState, Assets, IsOnlyInstance};
+use workspace2::{AppState, WorkspaceStore};
+use zed2::{build_window_options, initialize_workspace, languages};
+use zed2::{ensure_only_instance, Assets, IsOnlyInstance};
mod open_listener;
@@ -125,7 +130,7 @@ fn main() {
language::init(cx);
languages::init(languages.clone(), node_runtime.clone(), cx);
let user_store = cx.build_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
- // let workspace_store = cx.add_model(|cx| WorkspaceStore::new(client.clone(), cx));
+ let workspace_store = cx.build_model(|cx| WorkspaceStore::new(client.clone(), cx));
cx.set_global(client.clone());
@@ -170,26 +175,23 @@ fn main() {
// client.telemetry().start(installation_id, session_id, cx);
- // todo!("app_state")
- let app_state = Arc::new(AppState { client, user_store });
- // let app_state = Arc::new(AppState {
- // languages,
- // client: client.clone(),
- // user_store,
- // fs,
- // build_window_options,
- // initialize_workspace,
- // background_actions,
- // workspace_store,
- // node_runtime,
- // });
- // cx.set_global(Arc::downgrade(&app_state));
+ let app_state = Arc::new(AppState {
+ languages,
+ client: client.clone(),
+ user_store,
+ fs,
+ build_window_options,
+ initialize_workspace,
+ // background_actions: todo!("ask Mikayla"),
+ workspace_store,
+ node_runtime,
+ });
+ cx.set_global(Arc::downgrade(&app_state));
// audio::init(Assets, cx);
// auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
- // todo!("workspace")
- // workspace::init(app_state.clone(), cx);
+ workspace2::init(app_state.clone(), cx);
// recent_projects::init(cx);
// journal2::init(app_state.clone(), cx);
@@ -320,22 +322,30 @@ async fn installation_id() -> Result<String> {
}
}
-async fn restore_or_create_workspace(_app_state: &Arc<AppState>, mut _cx: AsyncAppContext) {
- todo!("workspace")
- // if let Some(location) = workspace::last_opened_workspace_paths().await {
- // cx.update(|cx| workspace::open_paths(location.paths().as_ref(), app_state, None, cx))
- // .await
- // .log_err();
- // } else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
- // cx.update(|cx| show_welcome_experience(app_state, cx));
- // } else {
- // cx.update(|cx| {
- // workspace::open_new(app_state, cx, |workspace, cx| {
- // Editor::new_file(workspace, &Default::default(), cx)
- // })
- // .detach();
- // });
- // }
+async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncAppContext) {
+ async_maybe!({
+ if let Some(location) = workspace2::last_opened_workspace_paths().await {
+ cx.update(|cx| workspace2::open_paths(location.paths().as_ref(), app_state, None, cx))?
+ .await
+ .log_err();
+ } else if matches!(KEY_VALUE_STORE.read_kvp("******* THIS IS A BAD KEY PLEASE UNCOMMENT BELOW TO FIX THIS VERY LONG LINE *******"), Ok(None)) {
+ // todo!(welcome)
+ //} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
+ //todo!()
+ // cx.update(|cx| show_welcome_experience(app_state, cx));
+ } else {
+ cx.update(|cx| {
+ workspace2::open_new(app_state, cx, |workspace, cx| {
+ // todo!(editor)
+ // Editor::new_file(workspace, &Default::default(), cx)
+ })
+ .detach();
+ })?;
+ }
+ anyhow::Ok(())
+ })
+ .await
+ .log_err();
}
fn init_paths() {
@@ -919,11 +929,13 @@ async fn handle_cli_connection(
}
}
-// pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
-// &[
-// ("Go to file", &file_finder::Toggle),
-// ("Open command palette", &command_palette::Toggle),
-// ("Open recent projects", &recent_projects::OpenRecent),
-// ("Change your settings", &zed_actions::OpenSettings),
-// ]
-// }
+pub fn background_actions() -> &'static [(&'static str, &'static dyn Action)] {
+ // &[
+ // ("Go to file", &file_finder::Toggle),
+ // ("Open command palette", &command_palette::Toggle),
+ // ("Open recent projects", &recent_projects::OpenRecent),
+ // ("Change your settings", &zed_actions::OpenSettings),
+ // ]
+ // todo!()
+ &[]
+}
@@ -1,11 +1,17 @@
+#![allow(unused_variables, dead_code, unused_mut)]
+// todo!() this is to make transition easier.
+
mod assets;
pub mod languages;
mod only_instance;
mod open_listener;
pub use assets::*;
-use client::{Client, UserStore};
-use gpui::{AsyncAppContext, Model};
+use collections::HashMap;
+use gpui::{
+ point, px, AppContext, AsyncAppContext, AsyncWindowContext, Point, Task, TitlebarOptions,
+ WeakView, WindowBounds, WindowKind, WindowOptions,
+};
pub use only_instance::*;
pub use open_listener::*;
@@ -14,8 +20,14 @@ use cli::{
ipc::{self, IpcSender},
CliRequest, CliResponse, IpcHandshake,
};
-use futures::{channel::mpsc, SinkExt, StreamExt};
-use std::{sync::Arc, thread};
+use futures::{
+ channel::{mpsc, oneshot},
+ FutureExt, SinkExt, StreamExt,
+};
+use std::{path::Path, sync::Arc, thread, time::Duration};
+use util::{paths::PathLikeWithPosition, ResultExt};
+use uuid::Uuid;
+use workspace2::{AppState, Workspace};
pub fn connect_to_cli(
server_name: &str,
@@ -46,163 +58,350 @@ pub fn connect_to_cli(
Ok((async_request_rx, response_tx))
}
-pub struct AppState {
- pub client: Arc<Client>,
- pub user_store: Model<UserStore>,
-}
-
pub async fn handle_cli_connection(
- (mut requests, _responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
- _app_state: Arc<AppState>,
- mut _cx: AsyncAppContext,
+ (mut requests, responses): (mpsc::Receiver<CliRequest>, IpcSender<CliResponse>),
+ app_state: Arc<AppState>,
+ mut cx: AsyncAppContext,
) {
if let Some(request) = requests.next().await {
match request {
- CliRequest::Open { paths: _, wait: _ } => {
- // let mut caret_positions = HashMap::new();
-
- // let paths = if paths.is_empty() {
- // todo!()
- // workspace::last_opened_workspace_paths()
- // .await
- // .map(|location| location.paths().to_vec())
- // .unwrap_or_default()
- // } else {
- // paths
- // .into_iter()
- // .filter_map(|path_with_position_string| {
- // let path_with_position = PathLikeWithPosition::parse_str(
- // &path_with_position_string,
- // |path_str| {
- // Ok::<_, std::convert::Infallible>(
- // Path::new(path_str).to_path_buf(),
- // )
- // },
- // )
- // .expect("Infallible");
- // let path = path_with_position.path_like;
- // if let Some(row) = path_with_position.row {
- // if path.is_file() {
- // let row = row.saturating_sub(1);
- // let col =
- // path_with_position.column.unwrap_or(0).saturating_sub(1);
- // caret_positions.insert(path.clone(), Point::new(row, col));
- // }
- // }
- // Some(path)
- // })
- // .collect()
- // };
-
- // let mut errored = false;
- // todo!("workspace")
- // match cx
- // .update(|cx| workspace::open_paths(&paths, &app_state, None, cx))
- // .await
- // {
- // Ok((workspace, items)) => {
- // let mut item_release_futures = Vec::new();
-
- // for (item, path) in items.into_iter().zip(&paths) {
- // match item {
- // Some(Ok(item)) => {
- // if let Some(point) = caret_positions.remove(path) {
- // if let Some(active_editor) = item.downcast::<Editor>() {
- // active_editor
- // .downgrade()
- // .update(&mut cx, |editor, cx| {
- // let snapshot =
- // editor.snapshot(cx).display_snapshot;
- // let point = snapshot
- // .buffer_snapshot
- // .clip_point(point, Bias::Left);
- // editor.change_selections(
- // Some(Autoscroll::center()),
- // cx,
- // |s| s.select_ranges([point..point]),
- // );
- // })
- // .log_err();
- // }
- // }
-
- // let released = oneshot::channel();
- // cx.update(|cx| {
- // item.on_release(
- // cx,
- // Box::new(move |_| {
- // let _ = released.0.send(());
- // }),
- // )
- // .detach();
- // });
- // item_release_futures.push(released.1);
- // }
- // Some(Err(err)) => {
- // responses
- // .send(CliResponse::Stderr {
- // message: format!("error opening {:?}: {}", path, err),
- // })
- // .log_err();
- // errored = true;
- // }
- // None => {}
- // }
- // }
-
- // if wait {
- // let background = cx.background();
- // let wait = async move {
- // if paths.is_empty() {
- // let (done_tx, done_rx) = oneshot::channel();
- // if let Some(workspace) = workspace.upgrade(&cx) {
- // let _subscription = cx.update(|cx| {
- // cx.observe_release(&workspace, move |_, _| {
- // let _ = done_tx.send(());
- // })
- // });
- // drop(workspace);
- // let _ = done_rx.await;
- // }
- // } else {
- // let _ =
- // futures::future::try_join_all(item_release_futures).await;
- // };
- // }
- // .fuse();
- // futures::pin_mut!(wait);
-
- // loop {
- // // Repeatedly check if CLI is still open to avoid wasting resources
- // // waiting for files or workspaces to close.
- // let mut timer = background.timer(Duration::from_secs(1)).fuse();
- // futures::select_biased! {
- // _ = wait => break,
- // _ = timer => {
- // if responses.send(CliResponse::Ping).is_err() {
- // break;
- // }
- // }
- // }
- // }
- // }
- // }
- // Err(error) => {
- // errored = true;
- // responses
- // .send(CliResponse::Stderr {
- // message: format!("error opening {:?}: {}", paths, error),
- // })
- // .log_err();
- // }
- // }
-
- // responses
- // .send(CliResponse::Exit {
- // status: i32::from(errored),
- // })
- // .log_err();
+ CliRequest::Open { paths, wait } => {
+ let mut caret_positions = HashMap::default();
+
+ let paths = if paths.is_empty() {
+ todo!()
+ // workspace::last_opened_workspace_paths()
+ // .await
+ // .map(|location| location.paths().to_vec())
+ // .unwrap_or_default()
+ } else {
+ paths
+ .into_iter()
+ .filter_map(|path_with_position_string| {
+ let path_with_position = PathLikeWithPosition::parse_str(
+ &path_with_position_string,
+ |path_str| {
+ Ok::<_, std::convert::Infallible>(
+ Path::new(path_str).to_path_buf(),
+ )
+ },
+ )
+ .expect("Infallible");
+ let path = path_with_position.path_like;
+ if let Some(row) = path_with_position.row {
+ if path.is_file() {
+ let row = row.saturating_sub(1);
+ let col =
+ path_with_position.column.unwrap_or(0).saturating_sub(1);
+ caret_positions.insert(path.clone(), Point::new(row, col));
+ }
+ }
+ Some(path)
+ })
+ .collect::<Vec<_>>()
+ };
+
+ let mut errored = false;
+
+ if let Some(open_paths_task) = cx
+ .update(|cx| workspace2::open_paths(&paths, &app_state, None, cx))
+ .log_err()
+ {
+ match open_paths_task.await {
+ Ok((workspace, items)) => {
+ let mut item_release_futures = Vec::new();
+
+ for (item, path) in items.into_iter().zip(&paths) {
+ match item {
+ Some(Ok(mut item)) => {
+ if let Some(point) = caret_positions.remove(path) {
+ todo!()
+ // if let Some(active_editor) = item.downcast::<Editor>() {
+ // active_editor
+ // .downgrade()
+ // .update(&mut cx, |editor, cx| {
+ // let snapshot =
+ // editor.snapshot(cx).display_snapshot;
+ // let point = snapshot
+ // .buffer_snapshot
+ // .clip_point(point, Bias::Left);
+ // editor.change_selections(
+ // Some(Autoscroll::center()),
+ // cx,
+ // |s| s.select_ranges([point..point]),
+ // );
+ // })
+ // .log_err();
+ // }
+ }
+
+ let released = oneshot::channel();
+ cx.update(move |cx| {
+ item.on_release(
+ cx,
+ Box::new(move |_| {
+ let _ = released.0.send(());
+ }),
+ )
+ .detach();
+ })
+ .ok();
+ item_release_futures.push(released.1);
+ }
+ Some(Err(err)) => {
+ responses
+ .send(CliResponse::Stderr {
+ message: format!(
+ "error opening {:?}: {}",
+ path, err
+ ),
+ })
+ .log_err();
+ errored = true;
+ }
+ None => {}
+ }
+ }
+
+ if wait {
+ let executor = cx.background_executor().clone();
+ let wait = async move {
+ if paths.is_empty() {
+ let (done_tx, done_rx) = oneshot::channel();
+ let _subscription =
+ workspace.update(&mut cx, move |_, cx| {
+ cx.on_release(|_, _| {
+ let _ = done_tx.send(());
+ })
+ });
+ let _ = done_rx.await;
+ } else {
+ let _ = futures::future::try_join_all(item_release_futures)
+ .await;
+ };
+ }
+ .fuse();
+ futures::pin_mut!(wait);
+
+ loop {
+ // Repeatedly check if CLI is still open to avoid wasting resources
+ // waiting for files or workspaces to close.
+ let mut timer = executor.timer(Duration::from_secs(1)).fuse();
+ futures::select_biased! {
+ _ = wait => break,
+ _ = timer => {
+ if responses.send(CliResponse::Ping).is_err() {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ Err(error) => {
+ errored = true;
+ responses
+ .send(CliResponse::Stderr {
+ message: format!("error opening {:?}: {}", paths, error),
+ })
+ .log_err();
+ }
+ }
+
+ responses
+ .send(CliResponse::Exit {
+ status: i32::from(errored),
+ })
+ .log_err();
+ }
}
}
}
}
+
+pub fn build_window_options(
+ bounds: Option<WindowBounds>,
+ display_uuid: Option<Uuid>,
+ cx: &mut AppContext,
+) -> WindowOptions {
+ let bounds = bounds.unwrap_or(WindowBounds::Maximized);
+ let display = display_uuid.and_then(|uuid| {
+ cx.displays()
+ .into_iter()
+ .find(|display| display.uuid().ok() == Some(uuid))
+ });
+
+ WindowOptions {
+ bounds,
+ titlebar: Some(TitlebarOptions {
+ title: None,
+ appears_transparent: true,
+ traffic_light_position: Some(point(px(8.), px(8.))),
+ }),
+ center: false,
+ focus: false,
+ show: false,
+ kind: WindowKind::Normal,
+ is_movable: false,
+ display_id: display.map(|display| display.id()),
+ }
+}
+
+pub fn initialize_workspace(
+ workspace_handle: WeakView<Workspace>,
+ was_deserialized: bool,
+ app_state: Arc<AppState>,
+ cx: AsyncWindowContext,
+) -> Task<Result<()>> {
+ cx.spawn(|mut cx| async move {
+ workspace_handle.update(&mut cx, |workspace, cx| {
+ let workspace_handle = cx.view();
+ cx.subscribe(&workspace_handle, {
+ move |workspace, _, event, cx| {
+ if let workspace2::Event::PaneAdded(pane) = event {
+ pane.update(cx, |pane, cx| {
+ // todo!()
+ // pane.toolbar().update(cx, |toolbar, cx| {
+ // let breadcrumbs = cx.add_view(|_| Breadcrumbs::new(workspace));
+ // toolbar.add_item(breadcrumbs, cx);
+ // let buffer_search_bar = cx.add_view(BufferSearchBar::new);
+ // toolbar.add_item(buffer_search_bar.clone(), cx);
+ // let quick_action_bar = cx.add_view(|_| {
+ // QuickActionBar::new(buffer_search_bar, workspace)
+ // });
+ // toolbar.add_item(quick_action_bar, cx);
+ // let diagnostic_editor_controls =
+ // cx.add_view(|_| diagnostics2::ToolbarControls::new());
+ // toolbar.add_item(diagnostic_editor_controls, cx);
+ // let project_search_bar = cx.add_view(|_| ProjectSearchBar::new());
+ // toolbar.add_item(project_search_bar, cx);
+ // let submit_feedback_button =
+ // cx.add_view(|_| SubmitFeedbackButton::new());
+ // toolbar.add_item(submit_feedback_button, cx);
+ // let feedback_info_text = cx.add_view(|_| FeedbackInfoText::new());
+ // toolbar.add_item(feedback_info_text, cx);
+ // let lsp_log_item =
+ // cx.add_view(|_| language_tools::LspLogToolbarItemView::new());
+ // toolbar.add_item(lsp_log_item, cx);
+ // let syntax_tree_item = cx
+ // .add_view(|_| language_tools::SyntaxTreeToolbarItemView::new());
+ // toolbar.add_item(syntax_tree_item, cx);
+ // })
+ });
+ }
+ }
+ })
+ .detach();
+
+ // cx.emit(workspace2::Event::PaneAdded(
+ // workspace.active_pane().clone(),
+ // ));
+
+ // let collab_titlebar_item =
+ // cx.add_view(|cx| CollabTitlebarItem::new(workspace, &workspace_handle, cx));
+ // workspace.set_titlebar_item(collab_titlebar_item.into_any(), cx);
+
+ // let copilot =
+ // cx.add_view(|cx| copilot_button::CopilotButton::new(app_state.fs.clone(), cx));
+ // let diagnostic_summary =
+ // cx.add_view(|cx| diagnostics::items::DiagnosticIndicator::new(workspace, cx));
+ // let activity_indicator = activity_indicator::ActivityIndicator::new(
+ // workspace,
+ // app_state.languages.clone(),
+ // cx,
+ // );
+ // let active_buffer_language =
+ // cx.add_view(|_| language_selector::ActiveBufferLanguage::new(workspace));
+ // let vim_mode_indicator = cx.add_view(|cx| vim::ModeIndicator::new(cx));
+ // let feedback_button = cx.add_view(|_| {
+ // feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
+ // });
+ // let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+ // workspace.status_bar().update(cx, |status_bar, cx| {
+ // status_bar.add_left_item(diagnostic_summary, cx);
+ // status_bar.add_left_item(activity_indicator, cx);
+
+ // status_bar.add_right_item(feedback_button, cx);
+ // status_bar.add_right_item(copilot, cx);
+ // status_bar.add_right_item(active_buffer_language, cx);
+ // status_bar.add_right_item(vim_mode_indicator, cx);
+ // status_bar.add_right_item(cursor_position, cx);
+ // });
+
+ // auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
+
+ // vim::observe_keystrokes(cx);
+
+ // cx.on_window_should_close(|workspace, cx| {
+ // if let Some(task) = workspace.close(&Default::default(), cx) {
+ // task.detach_and_log_err(cx);
+ // }
+ // false
+ // });
+ // })?;
+
+ // let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
+ // let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
+ // let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
+ // let channels_panel =
+ // collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
+ // let chat_panel =
+ // collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
+ // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
+ // workspace_handle.clone(),
+ // cx.clone(),
+ // );
+ // let (
+ // project_panel,
+ // terminal_panel,
+ // assistant_panel,
+ // channels_panel,
+ // chat_panel,
+ // notification_panel,
+ // ) = futures::try_join!(
+ // project_panel,
+ // terminal_panel,
+ // assistant_panel,
+ // channels_panel,
+ // chat_panel,
+ // notification_panel,
+ // )?;
+ // workspace_handle.update(&mut cx, |workspace, cx| {
+ // let project_panel_position = project_panel.position(cx);
+ // workspace.add_panel_with_extra_event_handler(
+ // project_panel,
+ // cx,
+ // |workspace, _, event, cx| match event {
+ // project_panel::Event::NewSearchInDirectory { dir_entry } => {
+ // search::ProjectSearchView::new_search_in_directory(workspace, dir_entry, cx)
+ // }
+ // project_panel::Event::ActivatePanel => {
+ // workspace.focus_panel::<ProjectPanel>(cx);
+ // }
+ // _ => {}
+ // },
+ // );
+ // workspace.add_panel(terminal_panel, cx);
+ // workspace.add_panel(assistant_panel, cx);
+ // workspace.add_panel(channels_panel, cx);
+ // workspace.add_panel(chat_panel, cx);
+ // workspace.add_panel(notification_panel, cx);
+
+ // if !was_deserialized
+ // && workspace
+ // .project()
+ // .read(cx)
+ // .visible_worktrees(cx)
+ // .any(|tree| {
+ // tree.read(cx)
+ // .root_entry()
+ // .map_or(false, |entry| entry.is_dir())
+ // })
+ // {
+ // workspace.toggle_dock(project_panel_position, cx);
+ // }
+ // cx.focus_self();
+ })?;
+ Ok(())
+ })
+}