Make tab switcher show preview of selected tab (#36718)

David Kleingeld and Julia Ryan created

Similar to nvim's telescope this makes it easier to find the right tab
in the list.

The preview takes place in the pane where the tab resides.
- on dismiss: We restore all panes.
- on confirm: We restore all panes except the one where the selected tab
resides. For this reason we collect the active item for each pane before
the tabswither starts.

Release Notes:

- Improved tab switcher, it now shows a preview of the selected tab

Co-authored-by: Julia Ryan <juliaryan3.14@gmail.com>

Change summary

crates/tab_switcher/src/tab_switcher.rs | 55 ++++++++++++++++++++++----
1 file changed, 46 insertions(+), 9 deletions(-)

Detailed changes

crates/tab_switcher/src/tab_switcher.rs 🔗

@@ -113,7 +113,13 @@ impl TabSwitcher {
         }
 
         let weak_workspace = workspace.weak_handle();
+
         let project = workspace.project().clone();
+        let original_items: Vec<_> = workspace
+            .panes()
+            .iter()
+            .map(|p| (p.clone(), p.read(cx).active_item_index()))
+            .collect();
         workspace.toggle_modal(window, cx, |window, cx| {
             let delegate = TabSwitcherDelegate::new(
                 project,
@@ -124,6 +130,7 @@ impl TabSwitcher {
                 is_global,
                 window,
                 cx,
+                original_items,
             );
             TabSwitcher::new(delegate, window, is_global, cx)
         });
@@ -221,7 +228,9 @@ pub struct TabSwitcherDelegate {
     workspace: WeakEntity<Workspace>,
     project: Entity<Project>,
     matches: Vec<TabMatch>,
+    original_items: Vec<(Entity<Pane>, usize)>,
     is_all_panes: bool,
+    restored_items: bool,
 }
 
 impl TabSwitcherDelegate {
@@ -235,6 +244,7 @@ impl TabSwitcherDelegate {
         is_all_panes: bool,
         window: &mut Window,
         cx: &mut Context<TabSwitcher>,
+        original_items: Vec<(Entity<Pane>, usize)>,
     ) -> Self {
         Self::subscribe_to_updates(&pane, window, cx);
         Self {
@@ -246,6 +256,8 @@ impl TabSwitcherDelegate {
             project,
             matches: Vec::new(),
             is_all_panes,
+            original_items,
+            restored_items: false,
         }
     }
 
@@ -300,13 +312,6 @@ impl TabSwitcherDelegate {
 
         let matches = if query.is_empty() {
             let history = workspace.read(cx).recently_activated_items(cx);
-            for item in &all_items {
-                eprintln!(
-                    "{:?} {:?}",
-                    item.item.tab_content_text(0, cx),
-                    (Reverse(history.get(&item.item.item_id())), item.item_index)
-                )
-            }
             all_items
                 .sort_by_key(|tab| (Reverse(history.get(&tab.item.item_id())), tab.item_index));
             all_items
@@ -473,8 +478,25 @@ impl PickerDelegate for TabSwitcherDelegate {
         self.selected_index
     }
 
-    fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) {
+    fn set_selected_index(
+        &mut self,
+        ix: usize,
+        window: &mut Window,
+        cx: &mut Context<Picker<Self>>,
+    ) {
         self.selected_index = ix;
+
+        let Some(selected_match) = self.matches.get(self.selected_index()) else {
+            return;
+        };
+        selected_match
+            .pane
+            .update(cx, |pane, cx| {
+                if let Some(index) = pane.index_for_item(selected_match.item.as_ref()) {
+                    pane.activate_item(index, false, false, window, cx);
+                }
+            })
+            .ok();
         cx.notify();
     }
 
@@ -501,6 +523,13 @@ impl PickerDelegate for TabSwitcherDelegate {
         let Some(selected_match) = self.matches.get(self.selected_index()) else {
             return;
         };
+
+        self.restored_items = true;
+        for (pane, index) in self.original_items.iter() {
+            pane.update(cx, |this, cx| {
+                this.activate_item(*index, false, false, window, cx);
+            })
+        }
         selected_match
             .pane
             .update(cx, |pane, cx| {
@@ -511,7 +540,15 @@ impl PickerDelegate for TabSwitcherDelegate {
             .ok();
     }
 
-    fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<TabSwitcherDelegate>>) {
+    fn dismissed(&mut self, window: &mut Window, cx: &mut Context<Picker<TabSwitcherDelegate>>) {
+        if !self.restored_items {
+            for (pane, index) in self.original_items.iter() {
+                pane.update(cx, |this, cx| {
+                    this.activate_item(*index, false, false, window, cx);
+                })
+            }
+        }
+
         self.tab_switcher
             .update(cx, |_, cx| cx.emit(DismissEvent))
             .log_err();