Skip to content

Commit

Permalink
Add tab switcher (#7987)
Browse files Browse the repository at this point in the history
The Tab Switcher implementation (#7653):
- `ctrl-tab` opens the Tab Switcher and moves selection to the
previously selcted tab. It also cycles selection forward.
- `ctrl-shift-tab` opens the Tab Switcher and moves selection to the
last tab in the list. It also cycles selection backward.
- Tab is selected and the Tab Switcher is closed on the shortcut
modifier key (`ctrl` by default) release.
- List items are in reverse activation history order.
- The list reacts to the item changes in background (new tab, tab
closed, tab title changed etc.)

Intentionally not in scope of this PR:
- File icons
- Close buttons

I will come back to these features. I think they need to be implemented
in separate PRs, and be synchronized with changes in how tabs are
rendered, to reuse the code as it's done in the current implementation.
The Tab Switcher looks usable even without them.

Known Issues:

Tab Switcher doesn't react to mouse click on a list item. It's not a tab
switcher specific problem, it looks like ctrl-clicks are not handled the
same way in Zed as cmd-clicks. For instance, menu items can be activated
with cmd-click, but don't react to ctrl-click. Since the Tab Switcher's
default keybinding is `ctrl-tab`, the user can only click an item with
`ctrl` pushed down, thus preventing `on_click()` from firing.

fixes #7653, #7321

Release Notes:

- Added Tab Switcher which is accessible via `ctrl-tab` and
`ctrl-shift-tab` (#7653) (#7321)

Related issues:

- Unblocks #7356, I hope 😄

How it looks and works (it's only `ctrl-tab`'s and `ctrl-shift-tab`'s,
no `enter`'s or mouse clicks):


https://github.com/zed-industries/zed/assets/2101250/4ad4ec6a-5314-481b-8b35-7ac85e43eb92

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
  • Loading branch information
3 people committed Mar 27, 2024
1 parent 9c22009 commit 894b39a
Show file tree
Hide file tree
Showing 13 changed files with 715 additions and 56 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ members = [
"crates/story",
"crates/storybook",
"crates/sum_tree",
"crates/tab_switcher",
"crates/terminal",
"crates/terminal_view",
"crates/text",
Expand Down Expand Up @@ -188,6 +189,7 @@ sqlez_macros = { path = "crates/sqlez_macros" }
story = { path = "crates/story" }
storybook = { path = "crates/storybook" }
sum_tree = { path = "crates/sum_tree" }
tab_switcher = { path = "crates/tab_switcher" }
terminal = { path = "crates/terminal" }
terminal_view = { path = "crates/terminal_view" }
text = { path = "crates/text" }
Expand Down
8 changes: 6 additions & 2 deletions assets/keymaps/default-linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,7 @@
{
"context": "Pane",
"bindings": {
"ctrl-shift-tab": "pane::ActivatePrevItem",
"ctrl-pageup": "pane::ActivatePrevItem",
"ctrl-tab": "pane::ActivateNextItem",
"ctrl-pagedown": "pane::ActivateNextItem",
"ctrl-w": "pane::CloseActiveItem",
"alt-ctrl-t": "pane::CloseInactiveItems",
Expand Down Expand Up @@ -420,6 +418,8 @@
"ctrl-k ctrl-t": "theme_selector::Toggle",
"ctrl-t": "project_symbols::Toggle",
"ctrl-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"ctrl-e": "file_finder::Toggle",
"ctrl-shift-p": "command_palette::Toggle",
"ctrl-shift-m": "diagnostics::Deploy",
Expand Down Expand Up @@ -589,6 +589,10 @@
"context": "FileFinder",
"bindings": { "ctrl-shift-p": "file_finder::SelectPrev" }
},
{
"context": "TabSwitcher",
"bindings": { "ctrl-shift-tab": "menu::SelectPrev" }
},
{
"context": "Terminal",
"bindings": {
Expand Down
7 changes: 7 additions & 0 deletions assets/keymaps/default-macos.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"cmd-escape": "menu::Cancel",
"ctrl-escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"shift-enter": "menu::UseSelectedQuery",
"cmd-shift-w": "workspace::CloseWindow",
Expand Down Expand Up @@ -441,6 +442,8 @@
"cmd-k cmd-t": "theme_selector::Toggle",
"cmd-t": "project_symbols::Toggle",
"cmd-p": "file_finder::Toggle",
"ctrl-tab": "tab_switcher::Toggle",
"ctrl-shift-tab": ["tab_switcher::Toggle", { "select_last": true }],
"cmd-shift-p": "command_palette::Toggle",
"cmd-shift-m": "diagnostics::Deploy",
"cmd-shift-e": "project_panel::ToggleFocus",
Expand Down Expand Up @@ -603,6 +606,10 @@
"context": "FileFinder",
"bindings": { "cmd-shift-p": "file_finder::SelectPrev" }
},
{
"context": "TabSwitcher",
"bindings": { "ctrl-shift-tab": "menu::SelectPrev" }
},
{
"context": "Terminal",
"bindings": {
Expand Down
9 changes: 7 additions & 2 deletions crates/picker/src/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,13 @@ impl Head {
Self::Editor(editor)
}

pub fn empty(cx: &mut WindowContext) -> Self {
Self::Empty(cx.new_view(|cx| EmptyHead::new(cx)))
pub fn empty<V: 'static>(
blur_handler: impl FnMut(&mut V, &mut ViewContext<'_, V>) + 'static,
cx: &mut ViewContext<V>,
) -> Self {
let head = cx.new_view(|cx| EmptyHead::new(cx));
cx.on_blur(&head.focus_handle(cx), blur_handler).detach();
Self::Empty(head)
}
}

Expand Down
23 changes: 20 additions & 3 deletions crates/picker/src/picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use anyhow::Result;
use editor::{scroll::Autoscroll, Editor};
use gpui::{
div, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent, DismissEvent,
EventEmitter, FocusHandle, FocusableView, Length, ListState, Render, Task,
UniformListScrollHandle, View, ViewContext, WindowContext,
EventEmitter, FocusHandle, FocusableView, Length, ListState, MouseButton, MouseUpEvent, Render,
Task, UniformListScrollHandle, View, ViewContext, WindowContext,
};
use head::Head;
use std::{sync::Arc, time::Duration};
Expand Down Expand Up @@ -116,7 +116,7 @@ impl<D: PickerDelegate> Picker<D> {
/// A picker, which displays its matches using `gpui::uniform_list`, all matches should have the same height.
/// If `PickerDelegate::render_match` can return items with different heights, use `Picker::list`.
pub fn nonsearchable_uniform_list(delegate: D, cx: &mut ViewContext<Self>) -> Self {
let head = Head::empty(cx);
let head = Head::empty(Self::on_empty_head_blur, cx);

Self::new(delegate, ContainerKind::UniformList, head, cx)
}
Expand Down Expand Up @@ -313,6 +313,13 @@ impl<D: PickerDelegate> Picker<D> {
}
}

fn on_empty_head_blur(&mut self, cx: &mut ViewContext<Self>) {
let Head::Empty(_) = &self.head else {
panic!("unexpected call");
};
self.cancel(&menu::Cancel, cx);
}

pub fn refresh(&mut self, cx: &mut ViewContext<Self>) {
let query = self.query(cx);
self.update_matches(query, cx);
Expand Down Expand Up @@ -394,6 +401,16 @@ impl<D: PickerDelegate> Picker<D> {
.on_click(cx.listener(move |this, event: &ClickEvent, cx| {
this.handle_click(ix, event.down.modifiers.command, cx)
}))
// As of this writing, GPUI intercepts `ctrl-[mouse-event]`s on macOS
// and produces right mouse button events. This matches platforms norms
// but means that UIs which depend on holding ctrl down (such as the tab
// switcher) can't be clicked on. Hence, this handler.
.on_mouse_up(
MouseButton::Right,
cx.listener(move |this, event: &MouseUpEvent, cx| {
this.handle_click(ix, event.modifiers.command, cx)
}),
)
.children(
self.delegate
.render_match(ix, ix == self.delegate.selected_index(), cx),
Expand Down
32 changes: 32 additions & 0 deletions crates/tab_switcher/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "tab_switcher"
version = "0.1.0"
edition = "2021"
publish = false
license = "GPL-3.0-or-later"

[lib]
path = "src/tab_switcher.rs"
doctest = false

[dependencies]
collections.workspace = true
gpui.workspace = true
menu.workspace = true
picker.workspace = true
serde.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

[dev-dependencies]
anyhow.workspace = true
ctor.workspace = true
editor.workspace = true
env_logger.workspace = true
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
project.workspace = true
serde_json.workspace = true
theme = { workspace = true, features = ["test-support"] }
workspace = { workspace = true, features = ["test-support"] }
1 change: 1 addition & 0 deletions crates/tab_switcher/LICENSE-GPL
Loading

0 comments on commit 894b39a

Please sign in to comment.