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

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>
This commit is contained in:
David Kleingeld 2025-08-22 00:21:36 +02:00 committed by GitHub
parent 0beb919bbb
commit 06c0e59379
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -113,7 +113,13 @@ impl TabSwitcher {
} }
let weak_workspace = workspace.weak_handle(); let weak_workspace = workspace.weak_handle();
let project = workspace.project().clone(); 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| { workspace.toggle_modal(window, cx, |window, cx| {
let delegate = TabSwitcherDelegate::new( let delegate = TabSwitcherDelegate::new(
project, project,
@ -124,6 +130,7 @@ impl TabSwitcher {
is_global, is_global,
window, window,
cx, cx,
original_items,
); );
TabSwitcher::new(delegate, window, is_global, cx) TabSwitcher::new(delegate, window, is_global, cx)
}); });
@ -221,7 +228,9 @@ pub struct TabSwitcherDelegate {
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
project: Entity<Project>, project: Entity<Project>,
matches: Vec<TabMatch>, matches: Vec<TabMatch>,
original_items: Vec<(Entity<Pane>, usize)>,
is_all_panes: bool, is_all_panes: bool,
restored_items: bool,
} }
impl TabSwitcherDelegate { impl TabSwitcherDelegate {
@ -235,6 +244,7 @@ impl TabSwitcherDelegate {
is_all_panes: bool, is_all_panes: bool,
window: &mut Window, window: &mut Window,
cx: &mut Context<TabSwitcher>, cx: &mut Context<TabSwitcher>,
original_items: Vec<(Entity<Pane>, usize)>,
) -> Self { ) -> Self {
Self::subscribe_to_updates(&pane, window, cx); Self::subscribe_to_updates(&pane, window, cx);
Self { Self {
@ -246,6 +256,8 @@ impl TabSwitcherDelegate {
project, project,
matches: Vec::new(), matches: Vec::new(),
is_all_panes, is_all_panes,
original_items,
restored_items: false,
} }
} }
@ -300,13 +312,6 @@ impl TabSwitcherDelegate {
let matches = if query.is_empty() { let matches = if query.is_empty() {
let history = workspace.read(cx).recently_activated_items(cx); 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 all_items
.sort_by_key(|tab| (Reverse(history.get(&tab.item.item_id())), tab.item_index)); .sort_by_key(|tab| (Reverse(history.get(&tab.item.item_id())), tab.item_index));
all_items all_items
@ -473,8 +478,25 @@ impl PickerDelegate for TabSwitcherDelegate {
self.selected_index 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; 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(); cx.notify();
} }
@ -501,6 +523,13 @@ impl PickerDelegate for TabSwitcherDelegate {
let Some(selected_match) = self.matches.get(self.selected_index()) else { let Some(selected_match) = self.matches.get(self.selected_index()) else {
return; 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 selected_match
.pane .pane
.update(cx, |pane, cx| { .update(cx, |pane, cx| {
@ -511,7 +540,15 @@ impl PickerDelegate for TabSwitcherDelegate {
.ok(); .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 self.tab_switcher
.update(cx, |_, cx| cx.emit(DismissEvent)) .update(cx, |_, cx| cx.emit(DismissEvent))
.log_err(); .log_err();