Show cursor position in status bar (#3464)

Conrad Irwin created

Passes the compiler so it must work!

(though it doesn't show up until we fix pane focus)

Release Notes:

- N/A

Change summary

crates/editor2/src/items.rs         | 154 +++++++++++++++---------------
crates/gpui2/src/view.rs            |   4 
crates/workspace2/src/status_bar.rs |  25 +---
crates/zed2/src/zed2.rs             |   4 
4 files changed, 89 insertions(+), 98 deletions(-)

Detailed changes

crates/editor2/src/items.rs 🔗

@@ -8,9 +8,9 @@ use anyhow::{anyhow, Context, Result};
 use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
-    div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
-    FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
-    ViewContext, VisualContext, WeakView, WindowContext,
+    div, point, AnyElement, AppContext, AsyncAppContext, Div, Entity, EntityId, EventEmitter,
+    FocusHandle, IntoElement, Model, ParentElement, Pixels, Render, SharedString, Styled,
+    Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use language::{
     proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@@ -20,6 +20,7 @@ use project::{search::SearchQuery, FormatTrigger, Item as _, Project, ProjectPat
 use rpc::proto::{self, update_view, PeerId};
 use settings::Settings;
 use smallvec::SmallVec;
+use std::fmt::Write;
 use std::{
     borrow::Cow,
     cmp::{self, Ordering},
@@ -31,8 +32,11 @@ use std::{
 use text::Selection;
 use theme::{ActiveTheme, Theme};
 use ui::{Color, Label};
-use util::{paths::PathExt, ResultExt, TryFutureExt};
-use workspace::item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle};
+use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
+use workspace::{
+    item::{BreadcrumbText, FollowEvent, FollowableEvents, FollowableItemHandle},
+    StatusItemView,
+};
 use workspace::{
     item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
     searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@@ -1119,86 +1123,78 @@ pub struct CursorPosition {
     _observe_active_editor: Option<Subscription>,
 }
 
-// impl Default for CursorPosition {
-//     fn default() -> Self {
-//         Self::new()
-//     }
-// }
+impl Default for CursorPosition {
+    fn default() -> Self {
+        Self::new()
+    }
+}
 
-// impl CursorPosition {
-//     pub fn new() -> Self {
-//         Self {
-//             position: None,
-//             selected_count: 0,
-//             _observe_active_editor: None,
-//         }
-//     }
-
-//     fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
-//         let editor = editor.read(cx);
-//         let buffer = editor.buffer().read(cx).snapshot(cx);
-
-//         self.selected_count = 0;
-//         let mut last_selection: Option<Selection<usize>> = None;
-//         for selection in editor.selections.all::<usize>(cx) {
-//             self.selected_count += selection.end - selection.start;
-//             if last_selection
-//                 .as_ref()
-//                 .map_or(true, |last_selection| selection.id > last_selection.id)
-//             {
-//                 last_selection = Some(selection);
-//             }
-//         }
-//         self.position = last_selection.map(|s| s.head().to_point(&buffer));
+impl CursorPosition {
+    pub fn new() -> Self {
+        Self {
+            position: None,
+            selected_count: 0,
+            _observe_active_editor: None,
+        }
+    }
 
-//         cx.notify();
-//     }
-// }
+    fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
+        let editor = editor.read(cx);
+        let buffer = editor.buffer().read(cx).snapshot(cx);
 
-// impl Entity for CursorPosition {
-//     type Event = ();
-// }
+        self.selected_count = 0;
+        let mut last_selection: Option<Selection<usize>> = None;
+        for selection in editor.selections.all::<usize>(cx) {
+            self.selected_count += selection.end - selection.start;
+            if last_selection
+                .as_ref()
+                .map_or(true, |last_selection| selection.id > last_selection.id)
+            {
+                last_selection = Some(selection);
+            }
+        }
+        self.position = last_selection.map(|s| s.head().to_point(&buffer));
 
-// impl View for CursorPosition {
-//     fn ui_name() -> &'static str {
-//         "CursorPosition"
-//     }
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-//         if let Some(position) = self.position {
-//             let theme = &theme::current(cx).workspace.status_bar;
-//             let mut text = format!(
-//                 "{}{FILE_ROW_COLUMN_DELIMITER}{}",
-//                 position.row + 1,
-//                 position.column + 1
-//             );
-//             if self.selected_count > 0 {
-//                 write!(text, " ({} selected)", self.selected_count).unwrap();
-//             }
-//             Label::new(text, theme.cursor_position.clone()).into_any()
-//         } else {
-//             Empty::new().into_any()
-//         }
-//     }
-// }
+        cx.notify();
+    }
+}
 
-// impl StatusItemView for CursorPosition {
-//     fn set_active_pane_item(
-//         &mut self,
-//         active_pane_item: Option<&dyn ItemHandle>,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
-//             self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
-//             self.update_position(editor, cx);
-//         } else {
-//             self.position = None;
-//             self._observe_active_editor = None;
-//         }
+impl Render for CursorPosition {
+    type Element = Div;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        div().when_some(self.position, |el, position| {
+            let mut text = format!(
+                "{}{FILE_ROW_COLUMN_DELIMITER}{}",
+                position.row + 1,
+                position.column + 1
+            );
+            if self.selected_count > 0 {
+                write!(text, " ({} selected)", self.selected_count).unwrap();
+            }
 
-//         cx.notify();
-//     }
-// }
+            el.child(Label::new(text))
+        })
+    }
+}
+
+impl StatusItemView for CursorPosition {
+    fn set_active_pane_item(
+        &mut self,
+        active_pane_item: Option<&dyn ItemHandle>,
+        cx: &mut ViewContext<Self>,
+    ) {
+        if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
+            self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
+            self.update_position(editor, cx);
+        } else {
+            self.position = None;
+            self._observe_active_editor = None;
+        }
+
+        cx.notify();
+    }
+}
 
 fn path_for_buffer<'a>(
     buffer: &Model<MultiBuffer>,

crates/gpui2/src/view.rs 🔗

@@ -240,6 +240,10 @@ impl Element for AnyView {
     }
 
     fn paint(self, _: Bounds<Pixels>, state: &mut Self::State, cx: &mut WindowContext) {
+        debug_assert!(
+            state.is_some(),
+            "state is None. Did you include an AnyView twice in the tree?"
+        );
         (self.paint)(&self, state.take().unwrap(), cx)
     }
 }

crates/workspace2/src/status_bar.rs 🔗

@@ -52,22 +52,13 @@ impl Render for StatusBar {
                 h_stack()
                     .gap_4()
                     .child(
-                        h_stack()
-                            .gap_1()
-                            .child(
-                                // TODO: Line / column numbers
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(Button::new("status_line_column_numbers", "15:22")),
-                            )
-                            .child(
-                                // TODO: Language picker
-                                div()
-                                    .border()
-                                    .border_color(gpui::red())
-                                    .child(Button::new("status_buffer_language", "Rust")),
-                            ),
+                        h_stack().gap_1().child(
+                            // TODO: Language picker
+                            div()
+                                .border()
+                                .border_color(gpui::red())
+                                .child(Button::new("status_buffer_language", "Rust")),
+                        ),
                     )
                     .child(
                         h_stack()
@@ -133,7 +124,7 @@ impl StatusBar {
         h_stack()
             .items_center()
             .gap_2()
-            .children(self.right_items.iter().map(|item| item.to_any()))
+            .children(self.right_items.iter().rev().map(|item| item.to_any()))
     }
 }
 

crates/zed2/src/zed2.rs 🔗

@@ -147,7 +147,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
         //     let feedback_button = cx.add_view(|_| {
         //         feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace)
         //     });
-        //     let cursor_position = cx.add_view(|_| editor::items::CursorPosition::new());
+        let cursor_position = cx.build_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);
@@ -156,7 +156,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             // 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);
+            status_bar.add_right_item(cursor_position, cx);
         });
 
         auto_update::notify_of_any_new_update(cx);