go to line2 (#3261)

Conrad Irwin created

- MODAL
- center a div
- MOAR CODE
- Beautiful go to line modal


Release Notes:
- N/A

Change summary

Cargo.lock                                    |   1 
crates/editor2/src/editor.rs                  |  73 ++-
crates/go_to_line2/src/go_to_line.rs          | 391 +++++++++-----------
crates/gpui2/src/action.rs                    |   1 
crates/gpui2/src/app.rs                       |  25 +
crates/gpui2/src/app/async_context.rs         |   2 
crates/gpui2/src/gpui2.rs                     |   2 
crates/gpui2/src/interactive.rs               |   4 
crates/gpui2/src/styled.rs                    |  19 
crates/gpui2/src/window.rs                    |  15 
crates/menu2/src/menu2.rs                     |   5 
crates/theme2/src/theme2.rs                   |   2 
crates/ui2/src/components.rs                  |   2 
crates/ui2/src/components/elevated_surface.rs |  28 +
crates/ui2/src/elevation.rs                   |  27 +
crates/workspace2/src/dock.rs                 |   8 
crates/workspace2/src/modal_layer.rs          | 103 +++--
crates/workspace2/src/toolbar.rs              |  11 
crates/workspace2/src/workspace2.rs           |  19 
crates/zed2/Cargo.toml                        |   1 
crates/zed2/src/main.rs                       |   4 
styles/src/style_tree/assistant.ts            | 114 +++++-
styles/src/style_tree/status_bar.ts           |  18 
styles/src/themes/rose-pine/rose-pine-dawn.ts |   2 
24 files changed, 537 insertions(+), 340 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -11352,6 +11352,7 @@ dependencies = [
  "libc",
  "log",
  "lsp2",
+ "menu2",
  "node_runtime",
  "num_cpus",
  "parking_lot 0.11.2",

crates/editor2/src/editor.rs 🔗

@@ -39,11 +39,11 @@ use futures::FutureExt;
 use fuzzy::{StringMatch, StringMatchCandidate};
 use git::diff_hunk_to_display;
 use gpui::{
-    action, actions, div, px, relative, AnyElement, AppContext, BackgroundExecutor, ClipboardItem,
-    Context, DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle, FontStyle,
-    FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels, PlatformInputHandler, Render,
-    Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakView,
-    WindowContext,
+    action, actions, div, px, relative, rems, AnyElement, AppContext, BackgroundExecutor,
+    ClipboardItem, Context, DispatchContext, Div, Element, Entity, EventEmitter, FocusHandle,
+    FontFeatures, FontStyle, FontWeight, HighlightStyle, Hsla, InputHandler, Model, Pixels,
+    PlatformInputHandler, Render, Styled, Subscription, Task, TextStyle, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -57,6 +57,7 @@ use language::{
     Diagnostic, IndentKind, IndentSize, Language, LanguageRegistry, LanguageServerName,
     OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
 };
+use lazy_static::lazy_static;
 use link_go_to_definition::{GoToDefinitionLink, InlayHighlight, LinkGoToDefinitionState};
 use lsp::{DiagnosticSeverity, Documentation, LanguageServerId};
 use movement::TextLayoutDetails;
@@ -66,7 +67,7 @@ pub use multi_buffer::{
     ToPoint,
 };
 use ordered_float::OrderedFloat;
-use parking_lot::RwLock;
+use parking_lot::{Mutex, RwLock};
 use project::{FormatTrigger, Location, Project};
 use rand::prelude::*;
 use rpc::proto::*;
@@ -2178,14 +2179,14 @@ impl Editor {
     //         self.collaboration_hub = Some(hub);
     //     }
 
-    //     pub fn set_placeholder_text(
-    //         &mut self,
-    //         placeholder_text: impl Into<Arc<str>>,
-    //         cx: &mut ViewContext<Self>,
-    //     ) {
-    //         self.placeholder_text = Some(placeholder_text.into());
-    //         cx.notify();
-    //     }
+    pub fn set_placeholder_text(
+        &mut self,
+        placeholder_text: impl Into<Arc<str>>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        self.placeholder_text = Some(placeholder_text.into());
+        cx.notify();
+    }
 
     //     pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
     //         self.cursor_shape = cursor_shape;
@@ -9416,18 +9417,42 @@ impl Render for Editor {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let settings = ThemeSettings::get_global(cx);
-        let text_style = TextStyle {
-            color: cx.theme().colors().text,
-            font_family: settings.buffer_font.family.clone(),
-            font_features: settings.buffer_font.features,
-            font_size: settings.buffer_font_size.into(),
-            font_weight: FontWeight::NORMAL,
-            font_style: FontStyle::Normal,
-            line_height: relative(settings.buffer_line_height.value()),
-            underline: None,
+        let text_style = match self.mode {
+            EditorMode::SingleLine => {
+                TextStyle {
+                    color: cx.theme().colors().text,
+                    font_family: "Zed Sans".into(), // todo!()
+                    font_features: FontFeatures::default(),
+                    font_size: rems(1.0).into(),
+                    font_weight: FontWeight::NORMAL,
+                    font_style: FontStyle::Normal,
+                    line_height: relative(1.3).into(), // TODO relative(settings.buffer_line_height.value()),
+                    underline: None,
+                }
+            }
+
+            EditorMode::AutoHeight { max_lines } => todo!(),
+
+            EditorMode::Full => TextStyle {
+                color: cx.theme().colors().text,
+                font_family: settings.buffer_font.family.clone(),
+                font_features: settings.buffer_font.features,
+                font_size: settings.buffer_font_size.into(),
+                font_weight: FontWeight::NORMAL,
+                font_style: FontStyle::Normal,
+                line_height: relative(settings.buffer_line_height.value()),
+                underline: None,
+            },
+        };
+
+        let background = match self.mode {
+            EditorMode::SingleLine => cx.theme().system().transparent,
+            EditorMode::AutoHeight { max_lines } => cx.theme().system().transparent,
+            EditorMode::Full => cx.theme().colors().editor_background,
         };
+
         EditorElement::new(EditorStyle {
-            background: cx.theme().colors().editor_background,
+            background,
             local_player: cx.theme().players().local(),
             text: text_style,
             scrollbar_width: px(12.),

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -1,220 +1,195 @@
-use gpui::{actions, div, px, red, AppContext, Div, Render, Styled, ViewContext, VisualContext};
-use workspace::ModalRegistry;
+use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
+use gpui::{
+    actions, div, AppContext, Div, EventEmitter, ParentElement, Render, SharedString,
+    StatefulInteractivity, StatelessInteractive, Styled, Subscription, View, ViewContext,
+    VisualContext, WindowContext,
+};
+use text::{Bias, Point};
+use theme::ActiveTheme;
+use ui::{h_stack, modal, v_stack, Label, LabelColor};
+use util::paths::FILE_ROW_COLUMN_DELIMITER;
+use workspace::{Modal, ModalEvent, Workspace};
 
 actions!(Toggle);
 
 pub fn init(cx: &mut AppContext) {
-    cx.global_mut::<ModalRegistry>()
-        .register_modal(Toggle, |_, cx| {
-            // if let Some(editor) = workspace
-            //     .active_item(cx)
-            //     .and_then(|active_item| active_item.downcast::<Editor>())
-            // {
-            //     cx.build_view(|cx| GoToLine::new(editor, cx))
-            // }
-            let view = cx.build_view(|_| GoToLine);
-            view
-        });
+    cx.observe_new_views(
+        |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
+            workspace
+                .modal_layer()
+                .register_modal(Toggle, |workspace, cx| {
+                    let editor = workspace
+                        .active_item(cx)
+                        .and_then(|active_item| active_item.downcast::<Editor>())?;
+
+                    Some(cx.build_view(|cx| GoToLine::new(editor, cx)))
+                });
+        },
+    )
+    .detach();
+}
+
+pub struct GoToLine {
+    line_editor: View<Editor>,
+    active_editor: View<Editor>,
+    current_text: SharedString,
+    prev_scroll_position: Option<gpui::Point<f32>>,
+    _subscriptions: Vec<Subscription>,
+}
 
-    // cx.add_action(GoToLine::toggle);
-    // cx.add_action(GoToLine::confirm);
-    // cx.add_action(GoToLine::cancel);
+pub enum Event {
+    Dismissed,
 }
 
-pub struct GoToLine;
+impl EventEmitter for GoToLine {
+    type Event = Event;
+}
 
-impl Render for GoToLine {
-    type Element = Div<Self>;
+impl GoToLine {
+    pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
+        let line_editor = cx.build_view(|cx| {
+            let editor = Editor::single_line(cx);
+            editor.focus(cx);
+            editor
+        });
+        let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
+
+        let editor = active_editor.read(cx);
+        let cursor = editor.selections.last::<Point>(cx).head();
+        let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
+        let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
+
+        let current_text = format!(
+            "line {} of {} (column {})",
+            cursor.row + 1,
+            last_line + 1,
+            cursor.column + 1,
+        );
+
+        Self {
+            line_editor,
+            active_editor,
+            current_text: current_text.into(),
+            prev_scroll_position: Some(scroll_position),
+            _subscriptions: vec![line_editor_change, cx.on_release(Self::release)],
+        }
+    }
+
+    fn release(&mut self, cx: &mut WindowContext) {
+        let scroll_position = self.prev_scroll_position.take();
+        self.active_editor.update(cx, |editor, cx| {
+            editor.focus(cx);
+            editor.highlight_rows(None);
+            if let Some(scroll_position) = scroll_position {
+                editor.set_scroll_position(scroll_position, cx);
+            }
+            cx.notify();
+        })
+    }
+
+    fn on_line_editor_event(
+        &mut self,
+        _: View<Editor>,
+        event: &editor::Event,
+        cx: &mut ViewContext<Self>,
+    ) {
+        match event {
+            // todo!() this isn't working...
+            editor::Event::Blurred => cx.emit(Event::Dismissed),
+            editor::Event::BufferEdited { .. } => self.highlight_current_line(cx),
+            _ => {}
+        }
+    }
 
-    fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
-        div().bg(red()).w(px(100.0)).h(px(100.0))
+    fn highlight_current_line(&mut self, cx: &mut ViewContext<Self>) {
+        if let Some(point) = self.point_from_query(cx) {
+            self.active_editor.update(cx, |active_editor, cx| {
+                let snapshot = active_editor.snapshot(cx).display_snapshot;
+                let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
+                let display_point = point.to_display_point(&snapshot);
+                let row = display_point.row();
+                active_editor.highlight_rows(Some(row..row + 1));
+                active_editor.request_autoscroll(Autoscroll::center(), cx);
+            });
+            cx.notify();
+        }
+    }
+
+    fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
+        let line_editor = self.line_editor.read(cx).text(cx);
+        let mut components = line_editor
+            .splitn(2, FILE_ROW_COLUMN_DELIMITER)
+            .map(str::trim)
+            .fuse();
+        let row = components.next().and_then(|row| row.parse::<u32>().ok())?;
+        let column = components.next().and_then(|col| col.parse::<u32>().ok());
+        Some(Point::new(
+            row.saturating_sub(1),
+            column.unwrap_or(0).saturating_sub(1),
+        ))
+    }
+
+    fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
+        cx.emit(Event::Dismissed);
+    }
+
+    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
+        if let Some(point) = self.point_from_query(cx) {
+            self.active_editor.update(cx, |active_editor, cx| {
+                let snapshot = active_editor.snapshot(cx).display_snapshot;
+                let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
+                active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
+                    s.select_ranges([point..point])
+                });
+            });
+            self.prev_scroll_position.take();
+        }
+
+        cx.emit(Event::Dismissed);
     }
 }
 
-// pub struct GoToLine {
-//     //line_editor: View<Editor>,
-//     active_editor: View<Editor>,
-//     prev_scroll_position: Option<gpui::Point<Pixels>>,
-//     cursor_point: Point,
-//     max_point: Point,
-//     has_focus: bool,
-// }
-
-// pub enum Event {
-//     Dismissed,
-// }
-
-// impl GoToLine {
-//     pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
-//         // let line_editor = cx.build_view(|cx| {
-//         //     Editor::single_line(
-//         //         Some(Arc::new(|theme| theme.picker.input_editor.clone())),
-//         //         cx,
-//         //     )
-//         // });
-//         // cx.subscribe(&line_editor, Self::on_line_editor_event)
-//         //     .detach();
-
-//         let (scroll_position, cursor_point, max_point) = active_editor.update(cx, |editor, cx| {
-//             let scroll_position = editor.scroll_position(cx);
-//             let buffer = editor.buffer().read(cx).snapshot(cx);
-//             (
-//                 Some(scroll_position),
-//                 editor.selections.newest(cx).head(),
-//                 buffer.max_point(),
-//             )
-//         });
-
-//         cx.on_release(|_, on_release| {}).detach();
-
-//         Self {
-//             //line_editor,
-//             active_editor,
-//             prev_scroll_position: scroll_position,
-//             cursor_point,
-//             max_point,
-//             has_focus: false,
-//         }
-//     }
-
-//     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-//         cx.emit(Event::Dismissed);
-//     }
-
-//     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-//         self.prev_scroll_position.take();
-//         if let Some(point) = self.point_from_query(cx) {
-//             self.active_editor.update(cx, |active_editor, cx| {
-//                 let snapshot = active_editor.snapshot(cx).display_snapshot;
-//                 let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-//                 active_editor.change_selections(Some(Autoscroll::center()), cx, |s| {
-//                     s.select_ranges([point..point])
-//                 });
-//             });
-//         }
-
-//         cx.emit(Event::Dismissed);
-//     }
-
-//     fn on_line_editor_event(
-//         &mut self,
-//         _: View<Editor>,
-//         event: &editor::Event,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         match event {
-//             editor::Event::Blurred => cx.emit(Event::Dismissed),
-//             editor::Event::BufferEdited { .. } => {
-//                 if let Some(point) = self.point_from_query(cx) {
-//                     // todo!()
-//                     // self.active_editor.update(cx, |active_editor, cx| {
-//                     //     let snapshot = active_editor.snapshot(cx).display_snapshot;
-//                     //     let point = snapshot.buffer_snapshot.clip_point(point, Bias::Left);
-//                     //     let display_point = point.to_display_point(&snapshot);
-//                     //     let row = display_point.row();
-//                     //     active_editor.highlight_rows(Some(row..row + 1));
-//                     //     active_editor.request_autoscroll(Autoscroll::center(), cx);
-//                     // });
-//                     cx.notify();
-//                 }
-//             }
-//             _ => {}
-//         }
-//     }
-
-//     fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
-//         return None;
-//         // todo!()
-//         // let line_editor = self.line_editor.read(cx).text(cx);
-//         // let mut components = line_editor
-//         //     .splitn(2, FILE_ROW_COLUMN_DELIMITER)
-//         //     .map(str::trim)
-//         //     .fuse();
-//         // let row = components.next().and_then(|row| row.parse::<u32>().ok())?;
-//         // let column = components.next().and_then(|col| col.parse::<u32>().ok());
-//         // Some(Point::new(
-//         //     row.saturating_sub(1),
-//         //     column.unwrap_or(0).saturating_sub(1),
-//         // ))
-//     }
-// }
-
-// impl EventEmitter for GoToLine {
-//     type Event = Event;
-// }
-
-// impl Entity for GoToLine {
-//     fn release(&mut self, cx: &mut AppContext) {
-//         let scroll_position = self.prev_scroll_position.take();
-//         self.active_editor.window().update(cx, |cx| {
-//             self.active_editor.update(cx, |editor, cx| {
-//                 editor.highlight_rows(None);
-//                 if let Some(scroll_position) = scroll_position {
-//                     editor.set_scroll_position(scroll_position, cx);
-//                 }
-//             })
-//         });
-//     }
-// }
-
-// impl Render for GoToLine {
-//     type Element = Div<Self>;
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-//         // todo!()
-//         div()
-//     }
-// }
-
-// impl View for GoToLine {
-//     fn ui_name() -> &'static str {
-//         "GoToLine"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         let theme = &theme::current(cx).picker;
-
-//         let label = format!(
-//             "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines",
-//             self.cursor_point.row + 1,
-//             self.cursor_point.column + 1,
-//             self.max_point.row + 1
-//         );
-
-//         Flex::new(Axis::Vertical)
-//             .with_child(
-//                 ChildView::new(&self.line_editor, cx)
-//                     .contained()
-//                     .with_style(theme.input_editor.container),
-//             )
-//             .with_child(
-//                 Label::new(label, theme.no_matches.label.clone())
-//                     .contained()
-//                     .with_style(theme.no_matches.container),
-//             )
-//             .contained()
-//             .with_style(theme.container)
-//             .constrained()
-//             .with_max_width(500.0)
-//             .into_any_named("go to line")
-//     }
-
-//     fn focus_in(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
-//         self.has_focus = true;
-//         cx.focus(&self.line_editor);
-//     }
-
-//     fn focus_out(&mut self, _: AnyView, _: &mut ViewContext<Self>) {
-//         self.has_focus = false;
-//     }
-// }
-
-// impl Modal for GoToLine {
-//     fn has_focus(&self) -> bool {
-//         self.has_focus
-//     }
-
-//     fn dismiss_on_event(event: &Self::Event) -> bool {
-//         matches!(event, Event::Dismissed)
-//     }
-// }
+impl Modal for GoToLine {
+    fn to_modal_event(&self, e: &Self::Event) -> Option<ModalEvent> {
+        match e {
+            Event::Dismissed => Some(ModalEvent::Dismissed),
+        }
+    }
+}
+
+impl Render for GoToLine {
+    type Element = Div<Self, StatefulInteractivity<Self>>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        modal(cx)
+            .id("go to line")
+            .on_action(Self::cancel)
+            .on_action(Self::confirm)
+            .w_96()
+            .child(
+                v_stack()
+                    .px_1()
+                    .pt_0p5()
+                    .gap_px()
+                    .child(
+                        v_stack()
+                            .py_0p5()
+                            .px_1()
+                            .child(div().px_1().py_0p5().child(self.line_editor.clone())),
+                    )
+                    .child(
+                        div()
+                            .h_px()
+                            .w_full()
+                            .bg(cx.theme().colors().element_background),
+                    )
+                    .child(
+                        h_stack()
+                            .justify_between()
+                            .px_2()
+                            .py_1()
+                            .child(Label::new(self.current_text.clone()).color(LabelColor::Muted)),
+                    ),
+            )
+    }
+}

crates/gpui2/src/action.rs 🔗

@@ -123,6 +123,7 @@ pub fn register_action<A: Action>() {
 /// Construct an action based on its name and optional JSON parameters sourced from the keymap.
 pub fn build_action(name: &str, params: Option<serde_json::Value>) -> Result<Box<dyn Action>> {
     let lock = ACTION_REGISTRY.read();
+
     let build_action = lock
         .builders_by_name
         .get(name)

crates/gpui2/src/app.rs 🔗

@@ -18,8 +18,8 @@ use crate::{
     AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
     Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, ForegroundExecutor, KeyBinding, Keymap,
     LayoutId, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, SubscriberSet,
-    Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
-    WindowContext, WindowHandle, WindowId,
+    Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext,
+    Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, HashSet, VecDeque};
@@ -167,6 +167,7 @@ type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
 type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
 type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
 type ReleaseListener = Box<dyn FnOnce(&mut dyn Any, &mut AppContext) + 'static>;
+type NewViewListener = Box<dyn FnMut(AnyView, &mut WindowContext) + 'static>;
 
 // struct FrameConsumer {
 //     next_frame_callbacks: Vec<FrameCallback>,
@@ -193,6 +194,7 @@ pub struct AppContext {
     pub(crate) text_style_stack: Vec<TextStyleRefinement>,
     pub(crate) globals_by_type: HashMap<TypeId, AnyBox>,
     pub(crate) entities: EntityMap,
+    pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     pub(crate) keymap: Arc<Mutex<Keymap>>,
     pub(crate) global_action_listeners:
@@ -251,6 +253,7 @@ impl AppContext {
                 text_style_stack: Vec::new(),
                 globals_by_type: HashMap::default(),
                 entities,
+                new_view_observers: SubscriberSet::new(),
                 windows: SlotMap::with_key(),
                 keymap: Arc::new(Mutex::new(Keymap::default())),
                 global_action_listeners: HashMap::default(),
@@ -599,6 +602,7 @@ impl AppContext {
 
     fn apply_notify_effect(&mut self, emitter: EntityId) {
         self.pending_notifications.remove(&emitter);
+
         self.observers
             .clone()
             .retain(&emitter, |handler| handler(self));
@@ -838,6 +842,23 @@ impl AppContext {
         self.globals_by_type.insert(global_type, lease.global);
     }
 
+    pub fn observe_new_views<V: 'static>(
+        &mut self,
+        on_new: impl 'static + Fn(&mut V, &mut ViewContext<V>),
+    ) -> Subscription {
+        self.new_view_observers.insert(
+            TypeId::of::<V>(),
+            Box::new(move |any_view: AnyView, cx: &mut WindowContext| {
+                any_view
+                    .downcast::<V>()
+                    .unwrap()
+                    .update(cx, |view_state, cx| {
+                        on_new(view_state, cx);
+                    })
+            }),
+        )
+    }
+
     pub fn observe_release<E, T>(
         &mut self,
         handle: &E,

crates/gpui2/src/app/async_context.rs 🔗

@@ -258,7 +258,7 @@ impl VisualContext for AsyncWindowContext {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static,
+        V: 'static + Render,
     {
         self.window
             .update(self, |_, cx| cx.build_view(build_view_state))

crates/gpui2/src/gpui2.rs 🔗

@@ -112,7 +112,7 @@ pub trait VisualContext: Context {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static;
+        V: 'static + Render;
 
     fn update_view<V: 'static, R>(
         &mut self,

crates/gpui2/src/interactive.rs 🔗

@@ -414,10 +414,14 @@ pub trait ElementInteractivity<V: 'static>: 'static {
                     Box::new(move |_, key_down, context, phase, cx| {
                         if phase == DispatchPhase::Bubble {
                             let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
+                            dbg!(key_down);
                             if let KeyMatch::Some(action) =
                                 cx.match_keystroke(&global_id, &key_down.keystroke, context)
                             {
+                                dbg!(&action);
                                 return Some(action);
+                            } else {
+                                dbg!("none");
                             }
                         }
 

crates/gpui2/src/styled.rs 🔗

@@ -5,7 +5,7 @@ use crate::{
 };
 use crate::{BoxShadow, TextStyleRefinement};
 use refineable::Refineable;
-use smallvec::smallvec;
+use smallvec::{smallvec, SmallVec};
 
 pub trait Styled {
     fn style(&mut self) -> &mut StyleRefinement;
@@ -295,24 +295,11 @@ pub trait Styled {
 
     /// Sets the box shadow of the element.
     /// [Docs](https://tailwindcss.com/docs/box-shadow)
-    fn shadow(mut self) -> Self
+    fn shadow(mut self, shadows: SmallVec<[BoxShadow; 2]>) -> Self
     where
         Self: Sized,
     {
-        self.style().box_shadow = Some(smallvec![
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(1.)),
-                blur_radius: px(3.),
-                spread_radius: px(0.),
-            },
-            BoxShadow {
-                color: hsla(0., 0., 0., 0.1),
-                offset: point(px(0.), px(1.)),
-                blur_radius: px(2.),
-                spread_radius: px(-1.),
-            }
-        ]);
+        self.style().box_shadow = Some(shadows);
         self
     }
 

crates/gpui2/src/window.rs 🔗

@@ -1438,7 +1438,7 @@ impl VisualContext for WindowContext<'_> {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static,
+        V: 'static + Render,
     {
         let slot = self.app.entities.reserve();
         let view = View {
@@ -1446,7 +1446,16 @@ impl VisualContext for WindowContext<'_> {
         };
         let mut cx = ViewContext::new(&mut *self.app, &mut *self.window, &view);
         let entity = build_view_state(&mut cx);
-        self.entities.insert(slot, entity);
+        cx.entities.insert(slot, entity);
+
+        cx.new_view_observers
+            .clone()
+            .retain(&TypeId::of::<V>(), |observer| {
+                let any_view = AnyView::from(view.clone());
+                (observer)(any_view, self);
+                true
+            });
+
         view
     }
 
@@ -2233,7 +2242,7 @@ impl<V> Context for ViewContext<'_, V> {
 }
 
 impl<V: 'static> VisualContext for ViewContext<'_, V> {
-    fn build_view<W: 'static>(
+    fn build_view<W: Render + 'static>(
         &mut self,
         build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W,
     ) -> Self::Result<View<W>> {

crates/menu2/src/menu2.rs 🔗

@@ -1,5 +1,10 @@
 use gpui::actions;
 
+// todo!(remove this)
+// https://github.com/rust-lang/rust/issues/47384
+// https://github.com/mmastrac/rust-ctor/issues/280
+pub fn unused() {}
+
 actions!(
     Cancel,
     Confirm,

crates/theme2/src/theme2.rs 🔗

@@ -71,7 +71,7 @@ impl Theme {
         &self.styles.system
     }
 
-    /// Returns the [`ThemeColors`] for the theme.
+    /// Returns the [`PlayerColors`] for the theme.
     #[inline(always)]
     pub fn players(&self) -> &PlayerColors {
         &self.styles.player

crates/ui2/src/components.rs 🔗

@@ -3,6 +3,7 @@ mod button;
 mod checkbox;
 mod context_menu;
 mod details;
+mod elevated_surface;
 mod facepile;
 mod icon;
 mod icon_button;
@@ -30,6 +31,7 @@ pub use button::*;
 pub use checkbox::*;
 pub use context_menu::*;
 pub use details::*;
+pub use elevated_surface::*;
 pub use facepile::*;
 pub use icon::*;
 pub use icon_button::*;

crates/ui2/src/components/elevated_surface.rs 🔗

@@ -0,0 +1,28 @@
+use gpui::Div;
+
+use crate::{prelude::*, v_stack};
+
+/// Create an elevated surface.
+///
+/// Must be used inside of a relative parent element
+pub fn elevated_surface<V: 'static>(level: ElevationIndex, cx: &mut ViewContext<V>) -> Div<V> {
+    let colors = cx.theme().colors();
+
+    // let shadow = BoxShadow {
+    //     color: hsla(0., 0., 0., 0.1),
+    //     offset: point(px(0.), px(1.)),
+    //     blur_radius: px(3.),
+    //     spread_radius: px(0.),
+    // };
+
+    v_stack()
+        .rounded_lg()
+        .bg(colors.elevated_surface_background)
+        .border()
+        .border_color(colors.border)
+        .shadow(level.shadow())
+}
+
+pub fn modal<V>(cx: &mut ViewContext<V>) -> Div<V> {
+    elevated_surface(ElevationIndex::ModalSurfaces, cx)
+}

crates/ui2/src/elevation.rs 🔗

@@ -1,3 +1,6 @@
+use gpui::{hsla, point, px, BoxShadow};
+use smallvec::{smallvec, SmallVec};
+
 #[doc = include_str!("elevation.md")]
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Elevation {
@@ -17,8 +20,8 @@ pub enum ElevationIndex {
 }
 
 impl ElevationIndex {
-    pub fn usize(&self) -> usize {
-        match *self {
+    pub fn z_index(self) -> u32 {
+        match self {
             ElevationIndex::AppBackground => 0,
             ElevationIndex::UISurface => 100,
             ElevationIndex::ElevatedSurface => 200,
@@ -27,6 +30,26 @@ impl ElevationIndex {
             ElevationIndex::DraggedElement => 900,
         }
     }
+
+    pub fn shadow(self) -> SmallVec<[BoxShadow; 2]> {
+        match self {
+            ElevationIndex::AppBackground => smallvec![],
+
+            ElevationIndex::UISurface => smallvec![BoxShadow {
+                color: hsla(0., 0., 0., 0.12),
+                offset: point(px(0.), px(1.)),
+                blur_radius: px(3.),
+                spread_radius: px(0.),
+            }],
+
+            _ => smallvec![BoxShadow {
+                color: hsla(0., 0., 0., 0.32),
+                offset: point(px(1.), px(3.)),
+                blur_radius: px(12.),
+                spread_radius: px(0.),
+            }],
+        }
+    }
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]

crates/workspace2/src/dock.rs 🔗

@@ -407,6 +407,14 @@ impl Dock {
     //     }
 }
 
+impl Render for Dock {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        todo!()
+    }
+}
+
 // todo!()
 // impl View for Dock {
 //     fn ui_name() -> &'static str {

crates/workspace2/src/modal_layer.rs 🔗

@@ -1,35 +1,38 @@
 use crate::Workspace;
 use gpui::{
-    div, AnyView, AppContext, Div, ParentElement, Render, StatelessInteractive, View, ViewContext,
+    div, px, AnyView, Component, Div, EventEmitter, ParentElement, Render, StatelessInteractive,
+    Styled, Subscription, View, ViewContext,
 };
 use std::{any::TypeId, sync::Arc};
+use ui::v_stack;
 
-pub struct ModalRegistry {
-    registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
-}
-
-pub trait Modal {}
-
-#[derive(Clone)]
 pub struct ModalLayer {
     open_modal: Option<AnyView>,
+    subscription: Option<Subscription>,
+    registered_modals: Vec<(TypeId, Box<dyn Fn(Div<Workspace>) -> Div<Workspace>>)>,
 }
 
-pub fn init_modal_registry(cx: &mut AppContext) {
-    cx.set_global(ModalRegistry {
-        registered_modals: Vec::new(),
-    });
+pub enum ModalEvent {
+    Dismissed,
 }
 
-struct ToggleModal {
-    name: String,
+pub trait Modal: EventEmitter + Render {
+    fn to_modal_event(&self, _: &Self::Event) -> Option<ModalEvent>;
 }
 
-impl ModalRegistry {
+impl ModalLayer {
+    pub fn new() -> Self {
+        Self {
+            open_modal: None,
+            subscription: None,
+            registered_modals: Vec::new(),
+        }
+    }
+
     pub fn register_modal<A: 'static, V, B>(&mut self, action: A, build_view: B)
     where
-        V: Render,
-        B: Fn(&Workspace, &mut ViewContext<Workspace>) -> View<V> + 'static,
+        V: Modal,
+        B: Fn(&mut Workspace, &mut ViewContext<Workspace>) -> Option<View<V>> + 'static,
     {
         let build_view = Arc::new(build_view);
 
@@ -38,42 +41,56 @@ impl ModalRegistry {
             Box::new(move |mut div| {
                 let build_view = build_view.clone();
 
-                div.on_action(
-                    move |workspace: &mut Workspace, event: &A, cx: &mut ViewContext<Workspace>| {
-                        let new_modal = (build_view)(workspace, cx);
-                        workspace.modal_layer.update(cx, |modal_layer, _| {
-                            modal_layer.open_modal = Some(new_modal.into());
-                        });
-
-                        cx.notify();
-                    },
-                )
+                div.on_action(move |workspace, event: &A, cx| {
+                    let Some(new_modal) = (build_view)(workspace, cx) else {
+                        return;
+                    };
+                    workspace.modal_layer().show_modal(new_modal, cx);
+                })
             }),
         ));
     }
-}
 
-impl ModalLayer {
-    pub fn new() -> Self {
-        Self { open_modal: None }
+    pub fn show_modal<V: Modal>(&mut self, new_modal: View<V>, cx: &mut ViewContext<Workspace>) {
+        self.subscription = Some(cx.subscribe(&new_modal, |this, modal, e, cx| {
+            match modal.read(cx).to_modal_event(e) {
+                Some(ModalEvent::Dismissed) => this.modal_layer().hide_modal(cx),
+                None => {}
+            }
+        }));
+        self.open_modal = Some(new_modal.into());
+        cx.notify();
     }
 
-    pub fn render(&self, workspace: &Workspace, cx: &ViewContext<Workspace>) -> Div<Workspace> {
-        let mut div = div();
-
-        // div, c workspace.toggle_modal()div.on_action()) {
-        //
-        // }
+    pub fn hide_modal(&mut self, cx: &mut ViewContext<Workspace>) {
+        self.open_modal.take();
+        self.subscription.take();
+        cx.notify();
+    }
 
-        // for (type_id, action) in cx.global::<ModalRegistry>().registered_modals.iter() {
-        //     div = div.useful_on_action(*type_id, action)
-        // }
+    pub fn wrapper_element(&self, cx: &ViewContext<Workspace>) -> Div<Workspace> {
+        let mut parent = div().relative().size_full();
 
-        for (_, action) in cx.global::<ModalRegistry>().registered_modals.iter() {
-            div = (action)(div);
+        for (_, action) in self.registered_modals.iter() {
+            parent = (action)(parent);
         }
 
-        div.children(self.open_modal.clone())
+        parent.when_some(self.open_modal.as_ref(), |parent, open_modal| {
+            let container1 = div()
+                .absolute()
+                .flex()
+                .flex_col()
+                .items_center()
+                .size_full()
+                .top_0()
+                .left_0()
+                .z_index(400);
+
+            // transparent layer
+            let container2 = v_stack().h(px(0.0)).relative().top_20();
+
+            parent.child(container1.child(container2.child(open_modal.clone())))
+        })
     }
 }
 

crates/workspace2/src/toolbar.rs 🔗

@@ -1,6 +1,7 @@
 use crate::ItemHandle;
 use gpui::{
-    AnyView, AppContext, Entity, EntityId, EventEmitter, Render, View, ViewContext, WindowContext,
+    AnyView, AppContext, Div, Entity, EntityId, EventEmitter, Render, View, ViewContext,
+    WindowContext,
 };
 
 pub trait ToolbarItemView: Render + EventEmitter {
@@ -56,6 +57,14 @@ pub struct Toolbar {
     items: Vec<(Box<dyn ToolbarItemViewHandle>, ToolbarItemLocation)>,
 }
 
+impl Render for Toolbar {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        todo!()
+    }
+}
+
 // todo!()
 // impl View for Toolbar {
 //     fn ui_name() -> &'static str {

crates/workspace2/src/workspace2.rs 🔗

@@ -46,8 +46,7 @@ use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings,
 use itertools::Itertools;
 use language2::LanguageRegistry;
 use lazy_static::lazy_static;
-pub use modal_layer::ModalRegistry;
-use modal_layer::{init_modal_registry, ModalLayer};
+pub use modal_layer::*;
 use node_runtime::NodeRuntime;
 use notifications::{simple_message_notification::MessageNotification, NotificationHandle};
 pub use pane::*;
@@ -227,7 +226,6 @@ pub fn init_settings(cx: &mut AppContext) {
 
 pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     init_settings(cx);
-    init_modal_registry(cx);
     pane::init(cx);
     notifications::init(cx);
 
@@ -547,7 +545,7 @@ pub struct Workspace {
     last_active_center_pane: Option<WeakView<Pane>>,
     last_active_view_id: Option<proto::ViewId>,
     status_bar: View<StatusBar>,
-    modal_layer: View<ModalLayer>,
+    modal_layer: ModalLayer,
     //     titlebar_item: Option<AnyViewHandle>,
     notifications: Vec<(TypeId, usize, Box<dyn NotificationHandle>)>,
     project: Model<Project>,
@@ -698,7 +696,8 @@ impl Workspace {
             status_bar
         });
 
-        let modal_layer = cx.build_view(|cx| ModalLayer::new());
+        let workspace_handle = cx.view().downgrade();
+        let modal_layer = ModalLayer::new();
 
         // todo!()
         // cx.update_default_global::<DragAndDrop<Workspace>, _, _>(|drag_and_drop, _| {
@@ -782,6 +781,10 @@ impl Workspace {
         }
     }
 
+    pub fn modal_layer(&mut self) -> &mut ModalLayer {
+        &mut self.modal_layer
+    }
+
     fn new_local(
         abs_paths: Vec<PathBuf>,
         app_state: Arc<AppState>,
@@ -3712,13 +3715,13 @@ impl Render for Workspace {
             .bg(cx.theme().colors().background)
             .child(self.render_titlebar(cx))
             .child(
+                // todo! should this be a component a view?
                 self.modal_layer
-                    .read(cx)
-                    .render(self, cx)
+                    .wrapper_element(cx)
+                    .relative()
                     .flex_1()
                     .w_full()
                     .flex()
-                    .flex_row()
                     .overflow_hidden()
                     .border_t()
                     .border_b()

crates/zed2/Cargo.toml 🔗

@@ -48,6 +48,7 @@ journal = { package = "journal2", path = "../journal2" }
 language = { package = "language2", path = "../language2" }
 # language_selector = { path = "../language_selector" }
 lsp = { package = "lsp2", path = "../lsp2" }
+menu = { package = "menu2", path = "../menu2" }
 language_tools = { path = "../language_tools" }
 node_runtime = { path = "../node_runtime" }
 # assistant = { path = "../assistant" }

crates/zed2/src/main.rs 🔗

@@ -56,6 +56,10 @@ use zed2::{
 mod open_listener;
 
 fn main() {
+    //TODO!(figure out what the linker issues are here)
+    // https://github.com/rust-lang/rust/issues/47384
+    // https://github.com/mmastrac/rust-ctor/issues/280
+    menu::unused();
     let http = http::client();
     init_paths();
     init_logger();

styles/src/style_tree/assistant.ts 🔗

@@ -23,7 +23,7 @@ export default function assistant(): any {
     const theme = useTheme()
 
     const interactive_role = (
-        color: StyleSets
+        color: StyleSets,
     ): Interactive<RoleCycleButton> => {
         return interactive({
             base: {
@@ -94,7 +94,7 @@ export default function assistant(): any {
                     margin: { left: 8, right: 18 },
                     color: foreground(theme.highest, "positive"),
                     width: 12,
-                }
+                },
             },
             retrieve_context: toggleable({
                 base: interactive({
@@ -106,7 +106,8 @@ export default function assistant(): any {
                         background: background(theme.highest, "on"),
                         corner_radius: 2,
                         border: {
-                            width: 1., color: background(theme.highest, "on")
+                            width: 1,
+                            color: background(theme.highest, "on"),
                         },
                         margin: { left: 2 },
                         padding: {
@@ -118,17 +119,45 @@ export default function assistant(): any {
                     },
                     state: {
                         hovered: {
-                            ...text(theme.highest, "mono", "variant", "hovered"),
-                            background: background(theme.highest, "on", "hovered"),
+                            ...text(
+                                theme.highest,
+                                "mono",
+                                "variant",
+                                "hovered",
+                            ),
+                            background: background(
+                                theme.highest,
+                                "on",
+                                "hovered",
+                            ),
                             border: {
-                                width: 1., color: background(theme.highest, "on", "hovered")
+                                width: 1,
+                                color: background(
+                                    theme.highest,
+                                    "on",
+                                    "hovered",
+                                ),
                             },
                         },
                         clicked: {
-                            ...text(theme.highest, "mono", "variant", "pressed"),
-                            background: background(theme.highest, "on", "pressed"),
+                            ...text(
+                                theme.highest,
+                                "mono",
+                                "variant",
+                                "pressed",
+                            ),
+                            background: background(
+                                theme.highest,
+                                "on",
+                                "pressed",
+                            ),
                             border: {
-                                width: 1., color: background(theme.highest, "on", "pressed")
+                                width: 1,
+                                color: background(
+                                    theme.highest,
+                                    "on",
+                                    "pressed",
+                                ),
                             },
                         },
                     },
@@ -143,11 +172,19 @@ export default function assistant(): any {
                             border: border(theme.highest, "accent"),
                         },
                         hovered: {
-                            background: background(theme.highest, "accent", "hovered"),
+                            background: background(
+                                theme.highest,
+                                "accent",
+                                "hovered",
+                            ),
                             border: border(theme.highest, "accent", "hovered"),
                         },
                         clicked: {
-                            background: background(theme.highest, "accent", "pressed"),
+                            background: background(
+                                theme.highest,
+                                "accent",
+                                "pressed",
+                            ),
                             border: border(theme.highest, "accent", "pressed"),
                         },
                     },
@@ -163,7 +200,8 @@ export default function assistant(): any {
                         background: background(theme.highest, "on"),
                         corner_radius: 2,
                         border: {
-                            width: 1., color: background(theme.highest, "on")
+                            width: 1,
+                            color: background(theme.highest, "on"),
                         },
                         padding: {
                             left: 4,
@@ -174,17 +212,45 @@ export default function assistant(): any {
                     },
                     state: {
                         hovered: {
-                            ...text(theme.highest, "mono", "variant", "hovered"),
-                            background: background(theme.highest, "on", "hovered"),
+                            ...text(
+                                theme.highest,
+                                "mono",
+                                "variant",
+                                "hovered",
+                            ),
+                            background: background(
+                                theme.highest,
+                                "on",
+                                "hovered",
+                            ),
                             border: {
-                                width: 1., color: background(theme.highest, "on", "hovered")
+                                width: 1,
+                                color: background(
+                                    theme.highest,
+                                    "on",
+                                    "hovered",
+                                ),
                             },
                         },
                         clicked: {
-                            ...text(theme.highest, "mono", "variant", "pressed"),
-                            background: background(theme.highest, "on", "pressed"),
+                            ...text(
+                                theme.highest,
+                                "mono",
+                                "variant",
+                                "pressed",
+                            ),
+                            background: background(
+                                theme.highest,
+                                "on",
+                                "pressed",
+                            ),
                             border: {
-                                width: 1., color: background(theme.highest, "on", "pressed")
+                                width: 1,
+                                color: background(
+                                    theme.highest,
+                                    "on",
+                                    "pressed",
+                                ),
                             },
                         },
                     },
@@ -199,11 +265,19 @@ export default function assistant(): any {
                             border: border(theme.highest, "accent"),
                         },
                         hovered: {
-                            background: background(theme.highest, "accent", "hovered"),
+                            background: background(
+                                theme.highest,
+                                "accent",
+                                "hovered",
+                            ),
                             border: border(theme.highest, "accent", "hovered"),
                         },
                         clicked: {
-                            background: background(theme.highest, "accent", "pressed"),
+                            background: background(
+                                theme.highest,
+                                "accent",
+                                "pressed",
+                            ),
                             border: border(theme.highest, "accent", "pressed"),
                         },
                     },

styles/src/style_tree/status_bar.ts 🔗

@@ -78,33 +78,33 @@ export default function status_bar(): any {
                     padding: { top: 2, bottom: 2, left: 6, right: 6 },
                 },
                 container_warning: diagnostic_status_container,
-                container_error: diagnostic_status_container
+                container_error: diagnostic_status_container,
             },
             state: {
                 hovered: {
                     icon_color_ok: foreground(layer, "on"),
                     container_ok: {
-                        background: background(layer, "hovered")
+                        background: background(layer, "hovered"),
                     },
                     container_warning: {
-                        background: background(layer, "hovered")
+                        background: background(layer, "hovered"),
                     },
                     container_error: {
-                        background: background(layer, "hovered")
+                        background: background(layer, "hovered"),
                     },
                 },
                 clicked: {
                     icon_color_ok: foreground(layer, "on"),
                     container_ok: {
-                        background: background(layer, "pressed")
+                        background: background(layer, "pressed"),
                     },
                     container_warning: {
-                        background: background(layer, "pressed")
+                        background: background(layer, "pressed"),
                     },
                     container_error: {
-                        background: background(layer, "pressed")
-                    }
-                }
+                        background: background(layer, "pressed"),
+                    },
+                },
             },
         }),
         panel_buttons: {

styles/src/themes/rose-pine/rose-pine-dawn.ts 🔗

@@ -31,7 +31,7 @@ export const theme: ThemeConfig = {
                     color.muted,
                     color.subtle,
                     color.text,
-                ].reverse()
+                ].reverse(),
             )
             .domain([0, 0.35, 0.45, 0.65, 0.7, 0.8, 0.9, 1]),
         red: color_ramp(chroma(color.love)),