Generated
+39
@@ -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]]
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+354
-408
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1246
-1088
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
// }
|
||||
// }
|
||||
+2051
-1918
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
+56
-44
@@ -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!()
|
||||
&[]
|
||||
}
|
||||
|
||||
+348
-149
@@ -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();
|
||||
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()
|
||||
// };
|
||||
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;
|
||||
// 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();
|
||||
let mut errored = false;
|
||||
|
||||
// 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();
|
||||
// }
|
||||
// }
|
||||
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();
|
||||
|
||||
// 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 => {}
|
||||
// }
|
||||
// }
|
||||
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();
|
||||
// }
|
||||
}
|
||||
|
||||
// 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);
|
||||
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 => {}
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
// }
|
||||
// }
|
||||
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);
|
||||
|
||||
// responses
|
||||
// .send(CliResponse::Exit {
|
||||
// status: i32::from(errored),
|
||||
// })
|
||||
// .log_err();
|
||||
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(())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user