vim: Add setting to control whether edit predictions are shown in normal mode (#55956)

Ben Kunkle created

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Closes #ISSUE

Release Notes:

- Added a setting
[vim.show_edit_predictions_in_normal_mode](zed://settings/vim.show_edit_predictions_in_normal_mode)
to control whether edit predictions are shown in normal mode.

Change summary

assets/settings/default.json                    |  3 ++
crates/settings_content/src/settings_content.rs |  3 ++
crates/settings_ui/src/page_data.rs             | 24 ++++++++++++++++++
crates/vim/src/state.rs                         |  4 +++
crates/vim/src/vim.rs                           |  6 +++
5 files changed, 38 insertions(+), 2 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -2515,6 +2515,9 @@
     "gdefault": false,
     "highlight_on_yank_duration": 200,
     "custom_digraphs": {},
+    // When enabled, edit predictions are shown in Vim normal mode.
+    // By default, edit predictions are only shown in insert and replace modes.
+    "show_edit_predictions_in_normal_mode": false,
     // Cursor shape for each mode.
     // The shape can be one of the following: "block", "bar", "underline", "hollow".
     "cursor_shape": {

crates/settings_content/src/settings_content.rs 🔗

@@ -864,6 +864,9 @@ pub struct VimSettingsContent {
     pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
     pub highlight_on_yank_duration: Option<u64>,
     pub cursor_shape: Option<CursorShapeSettings>,
+    /// When enabled, edit predictions are shown in Vim normal mode.
+    /// By default, edit predictions are only shown in insert and replace modes.
+    pub show_edit_predictions_in_normal_mode: Option<bool>,
 }
 
 #[derive(

crates/settings_ui/src/page_data.rs 🔗

@@ -2573,7 +2573,7 @@ fn editor_page() -> SettingsPage {
         ]
     }
 
-    fn vim_settings_section() -> [SettingsPageItem; 13] {
+    fn vim_settings_section() -> [SettingsPageItem; 14] {
         [
             SettingsPageItem::SectionHeader("Vim"),
             SettingsPageItem::SettingItem(SettingItem {
@@ -2700,6 +2700,28 @@ fn editor_page() -> SettingsPage {
                 metadata: None,
                 files: USER,
             }),
+            SettingsPageItem::SettingItem(SettingItem {
+                title: "Show Edit Predictions in Normal Mode",
+                description: "Whether edit predictions are shown in normal mode. By default, edit predictions are only shown in insert and replace modes.",
+                field: Box::new(SettingField {
+                    json_path: Some("vim.show_edit_predictions_in_normal_mode"),
+                    pick: |settings_content| {
+                        settings_content
+                            .vim
+                            .as_ref()?
+                            .show_edit_predictions_in_normal_mode
+                            .as_ref()
+                    },
+                    write: |settings_content, value, _| {
+                        settings_content
+                            .vim
+                            .get_or_insert_default()
+                            .show_edit_predictions_in_normal_mode = value;
+                    },
+                }),
+                metadata: None,
+                files: USER,
+            }),
             SettingsPageItem::SettingItem(SettingItem {
                 title: "Cursor Shape - Normal Mode",
                 description: "Cursor shape for normal mode.",

crates/vim/src/state.rs 🔗

@@ -79,6 +79,10 @@ impl Mode {
     pub fn is_helix(&self) -> bool {
         matches!(self, Self::HelixNormal | Self::HelixSelect)
     }
+
+    pub fn is_normal(&self) -> bool {
+        matches!(self, Self::Normal | Self::HelixNormal)
+    }
 }
 
 #[derive(Clone, Debug, PartialEq)]

crates/vim/src/vim.rs 🔗

@@ -2209,7 +2209,9 @@ impl Vim {
             autoindent: self.should_autoindent(),
             cursor_offset_on_selection: self.mode.is_visual() || self.mode.is_helix(),
             line_mode: matches!(self.mode, Mode::VisualLine),
-            hide_edit_predictions: !matches!(self.mode, Mode::Insert | Mode::Replace),
+            hide_edit_predictions: !matches!(self.mode, Mode::Insert | Mode::Replace)
+                && !(self.mode.is_normal()
+                    && VimSettings::get_global(cx).show_edit_predictions_in_normal_mode),
         }
     }
 
@@ -2259,6 +2261,7 @@ struct VimSettings {
     pub custom_digraphs: HashMap<String, Arc<str>>,
     pub highlight_on_yank_duration: u64,
     pub cursor_shape: CursorShapeSettings,
+    pub show_edit_predictions_in_normal_mode: bool,
 }
 
 /// Cursor shape configuration for insert mode.
@@ -2346,6 +2349,7 @@ impl Settings for VimSettings {
             custom_digraphs: vim.custom_digraphs.unwrap(),
             highlight_on_yank_duration: vim.highlight_on_yank_duration.unwrap(),
             cursor_shape: vim.cursor_shape.unwrap().into(),
+            show_edit_predictions_in_normal_mode: vim.show_edit_predictions_in_normal_mode.unwrap(),
         }
     }
 }