vim: Add support for ctrl-g (#23562)

Conrad Irwin and Jon Walstedt created

Co-Authored-By: Jon Walstedt <jon@walstedt.se>

Closes #22094

Release Notes:

- vim: Added support for ctrl-g

Co-authored-by: Jon Walstedt <jon@walstedt.se>

Change summary

assets/keymaps/vim.json          |  1 
crates/vim/src/mode_indicator.rs | 24 +++++++++++-------
crates/vim/src/normal.rs         | 43 +++++++++++++++++++++++++++++++++
crates/vim/src/vim.rs            |  5 +++
4 files changed, 62 insertions(+), 11 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -86,6 +86,7 @@
       "ctrl-[": ["vim::SwitchMode", "Normal"],
       "v": "vim::ToggleVisual",
       "shift-v": "vim::ToggleVisualLine",
+      "ctrl-g": "vim::ShowLocation",
       "ctrl-v": "vim::ToggleVisualBlock",
       "ctrl-q": "vim::ToggleVisualBlock",
       "shift-k": "editor::Hover",

crates/vim/src/mode_indicator.rs 🔗

@@ -97,18 +97,24 @@ impl Render for ModeIndicator {
         };
 
         let vim_readable = vim.read(cx);
-        let mode = if vim_readable.temp_mode {
-            format!("(insert) {}", vim_readable.mode)
+        let label = if let Some(label) = vim_readable.status_label.clone() {
+            label
         } else {
-            vim_readable.mode.to_string()
+            let mode = if vim_readable.temp_mode {
+                format!("(insert) {}", vim_readable.mode)
+            } else {
+                vim_readable.mode.to_string()
+            };
+
+            let current_operators_description = self.current_operators_description(vim.clone(), cx);
+            let pending = self
+                .pending_keys
+                .as_ref()
+                .unwrap_or(&current_operators_description);
+            format!("{} -- {} --", pending, mode).into()
         };
 
-        let current_operators_description = self.current_operators_description(vim.clone(), cx);
-        let pending = self
-            .pending_keys
-            .as_ref()
-            .unwrap_or(&current_operators_description);
-        Label::new(format!("{} -- {} --", pending, mode))
+        Label::new(label)
             .size(LabelSize::Small)
             .line_height_style(LineHeightStyle::UiLabel)
             .into_any_element()

crates/vim/src/normal.rs 🔗

@@ -30,7 +30,7 @@ use editor::Bias;
 use editor::Editor;
 use editor::{display_map::ToDisplayPoint, movement};
 use gpui::{actions, ViewContext};
-use language::{Point, SelectionGoal};
+use language::{Point, SelectionGoal, ToPoint};
 use log::error;
 use multi_buffer::MultiBufferRow;
 
@@ -56,6 +56,7 @@ actions!(
         ConvertToUpperCase,
         ConvertToLowerCase,
         ToggleComments,
+        ShowLocation,
         Undo,
         Redo,
     ]
@@ -75,6 +76,7 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
     Vim::action(editor, cx, Vim::yank_line);
     Vim::action(editor, cx, Vim::toggle_comments);
     Vim::action(editor, cx, Vim::paste);
+    Vim::action(editor, cx, Vim::show_location);
 
     Vim::action(editor, cx, |vim, _: &DeleteLeft, cx| {
         vim.record_current_action(cx);
@@ -419,6 +421,45 @@ impl Vim {
         self.yank_motion(motion::Motion::CurrentLine, count, cx)
     }
 
+    fn show_location(&mut self, _: &ShowLocation, cx: &mut ViewContext<Self>) {
+        let count = Vim::take_count(cx);
+        self.update_editor(cx, |vim, editor, cx| {
+            let selection = editor.selections.newest_anchor();
+            if let Some((_, buffer, _)) = editor.active_excerpt(cx) {
+                let filename = if let Some(file) = buffer.read(cx).file() {
+                    if count.is_some() {
+                        if let Some(local) = file.as_local() {
+                            local.abs_path(cx).to_string_lossy().to_string()
+                        } else {
+                            file.full_path(cx).to_string_lossy().to_string()
+                        }
+                    } else {
+                        file.path().to_string_lossy().to_string()
+                    }
+                } else {
+                    "[No Name]".into()
+                };
+                let buffer = buffer.read(cx);
+                let snapshot = buffer.snapshot();
+                let lines = buffer.max_point().row + 1;
+                let current_line = selection.head().text_anchor.to_point(&snapshot).row;
+                let percentage = current_line as f32 / lines as f32;
+                let modified = if buffer.is_dirty() { " [modified]" } else { "" };
+                vim.status_label = Some(
+                    format!(
+                        "{}{} {} lines --{:.0}%--",
+                        filename,
+                        modified,
+                        lines,
+                        percentage * 100.0,
+                    )
+                    .into(),
+                );
+                cx.notify();
+            }
+        });
+    }
+
     fn toggle_comments(&mut self, _: &ToggleComments, cx: &mut ViewContext<Self>) {
         self.record_current_action(cx);
         self.store_visual_marks(cx);

crates/vim/src/vim.rs 🔗

@@ -42,7 +42,7 @@ use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
 use std::{mem, ops::Range, sync::Arc};
 use surrounds::SurroundsType;
 use theme::ThemeSettings;
-use ui::{px, IntoElement, VisualContext};
+use ui::{px, IntoElement, SharedString, VisualContext};
 use vim_mode_setting::VimModeSetting;
 use workspace::{self, Pane, ResizeIntent, Workspace};
 
@@ -201,6 +201,7 @@ pub(crate) struct Vim {
     pub(crate) mode: Mode,
     pub last_mode: Mode,
     pub temp_mode: bool,
+    pub status_label: Option<SharedString>,
     pub exit_temporary_mode: bool,
 
     operator_stack: Vec<Operator>,
@@ -262,6 +263,7 @@ impl Vim {
             current_anchor: None,
             undo_modes: HashMap::default(),
 
+            status_label: None,
             selected_register: None,
             search: SearchState::default(),
 
@@ -519,6 +521,7 @@ impl Vim {
         let last_mode = self.mode;
         let prior_mode = self.last_mode;
         let prior_tx = self.current_tx;
+        self.status_label.take();
         self.last_mode = last_mode;
         self.mode = mode;
         self.operator_stack.clear();