Add :tabonly and :only vim commands (#8337)

riccardofano and Conrad Irwin created

Release Notes:

- Added
[`:tabo[nly][!]`](https://neovim.io/doc/user/tabpage.html#%3Atabonly),
closes all the tabs except the active one but in the current pane only,
every other split pane remains unaffected.
The version with the `!` force closes the tabs while the one without
asks you to save or discard the changes.
- Added [`:on[ly][!]`](https://neovim.io/doc/user/windows.html#%3Aonly),
closes all the tabs *and* panes except the active one.
The version with the `!` force closes the tabs while the one without
asks you to save or discard the changes.
Since Zed does not have different splits per tab like in Neovim `:only`
works the same as it does in VscodeVim.

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/vim/src/command.rs         | 28 ++++++++++++++++++++++++++++
crates/workspace/src/pane.rs      | 22 +++++++++++++++-------
crates/workspace/src/workspace.rs | 14 ++++++++++----
3 files changed, 53 insertions(+), 11 deletions(-)

Detailed changes

crates/vim/src/command.rs 🔗

@@ -201,6 +201,34 @@ pub fn command_interceptor(mut query: &str, cx: &AppContext) -> Option<CommandIn
             }
             .boxed_clone(),
         ),
+        "tabo" | "tabon" | "tabonl" | "tabonly" => (
+            "tabonly",
+            workspace::CloseInactiveItems {
+                save_intent: Some(SaveIntent::Close),
+            }
+            .boxed_clone(),
+        ),
+        "tabo!" | "tabon!" | "tabonl!" | "tabonly!" => (
+            "tabonly!",
+            workspace::CloseInactiveItems {
+                save_intent: Some(SaveIntent::Skip),
+            }
+            .boxed_clone(),
+        ),
+        "on" | "onl" | "only" => (
+            "only",
+            workspace::CloseInactiveTabsAndPanes {
+                save_intent: Some(SaveIntent::Close),
+            }
+            .boxed_clone(),
+        ),
+        "on!" | "onl!" | "only!" => (
+            "only!",
+            workspace::CloseInactiveTabsAndPanes {
+                save_intent: Some(SaveIntent::Skip),
+            }
+            .boxed_clone(),
+        ),
 
         // quickfix / loclist (merged together for now)
         "cl" | "cli" | "clis" | "clist" => (

crates/workspace/src/pane.rs 🔗

@@ -66,6 +66,12 @@ pub struct CloseActiveItem {
     pub save_intent: Option<SaveIntent>,
 }
 
+#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct CloseInactiveItems {
+    pub save_intent: Option<SaveIntent>,
+}
+
 #[derive(Clone, PartialEq, Debug, Deserialize, Default)]
 #[serde(rename_all = "camelCase")]
 pub struct CloseAllItems {
@@ -83,6 +89,7 @@ impl_actions!(
     [
         CloseAllItems,
         CloseActiveItem,
+        CloseInactiveItems,
         ActivateItem,
         RevealInProjectPanel
     ]
@@ -94,7 +101,6 @@ actions!(
         ActivatePrevItem,
         ActivateNextItem,
         ActivateLastItem,
-        CloseInactiveItems,
         CloseCleanItems,
         CloseItemsToTheLeft,
         CloseItemsToTheRight,
@@ -767,7 +773,7 @@ impl Pane {
 
     pub fn close_inactive_items(
         &mut self,
-        _: &CloseInactiveItems,
+        action: &CloseInactiveItems,
         cx: &mut ViewContext<Self>,
     ) -> Option<Task<Result<()>>> {
         if self.items.is_empty() {
@@ -775,9 +781,11 @@ impl Pane {
         }
 
         let active_item_id = self.items[self.active_item_index].item_id();
-        Some(self.close_items(cx, SaveIntent::Close, move |item_id| {
-            item_id != active_item_id
-        }))
+        Some(self.close_items(
+            cx,
+            action.save_intent.unwrap_or(SaveIntent::Close),
+            move |item_id| item_id != active_item_id,
+        ))
     }
 
     pub fn close_clean_items(
@@ -1397,7 +1405,7 @@ impl Pane {
                         )
                         .entry(
                             "Close Others",
-                            Some(Box::new(CloseInactiveItems)),
+                            Some(Box::new(CloseInactiveItems { save_intent: None })),
                             cx.handler_for(&pane, move |pane, cx| {
                                 pane.close_items(cx, SaveIntent::Close, |id| id != item_id)
                                     .detach_and_log_err(cx);
@@ -2432,7 +2440,7 @@ mod tests {
         set_labeled_items(&pane, ["A", "B", "C*", "D", "E"], cx);
 
         pane.update(cx, |pane, cx| {
-            pane.close_inactive_items(&CloseInactiveItems, cx)
+            pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
         })
         .unwrap()
         .await

crates/workspace/src/workspace.rs 🔗

@@ -103,7 +103,6 @@ actions!(
         NewFile,
         NewWindow,
         CloseWindow,
-        CloseInactiveTabsAndPanes,
         AddFolderToProject,
         Unfollow,
         SaveAs,
@@ -161,6 +160,12 @@ pub struct CloseAllItemsAndPanes {
     pub save_intent: Option<SaveIntent>,
 }
 
+#[derive(Clone, PartialEq, Debug, Deserialize, Default)]
+#[serde(rename_all = "camelCase")]
+pub struct CloseInactiveTabsAndPanes {
+    pub save_intent: Option<SaveIntent>,
+}
+
 #[derive(Clone, Deserialize, PartialEq)]
 pub struct SendKeystrokes(pub String);
 
@@ -170,6 +175,7 @@ impl_actions!(
         ActivatePane,
         ActivatePaneInDirection,
         CloseAllItemsAndPanes,
+        CloseInactiveTabsAndPanes,
         NewFileInDirection,
         OpenTerminal,
         Save,
@@ -1620,10 +1626,10 @@ impl Workspace {
 
     pub fn close_inactive_items_and_panes(
         &mut self,
-        _: &CloseInactiveTabsAndPanes,
+        action: &CloseInactiveTabsAndPanes,
         cx: &mut ViewContext<Self>,
     ) {
-        self.close_all_internal(true, SaveIntent::Close, cx)
+        self.close_all_internal(true, action.save_intent.unwrap_or(SaveIntent::Close), cx)
             .map(|task| task.detach_and_log_err(cx));
     }
 
@@ -1648,7 +1654,7 @@ impl Workspace {
 
         if retain_active_pane {
             if let Some(current_pane_close) = current_pane.update(cx, |pane, cx| {
-                pane.close_inactive_items(&CloseInactiveItems, cx)
+                pane.close_inactive_items(&CloseInactiveItems { save_intent: None }, cx)
             }) {
                 tasks.push(current_pane_close);
             };