Add searchable global tab switcher (#28047)
resolves #24655 resolves #23945 I haven't yet added a default binding for the new command. #27797 added `:ls` and `:buffers` which in my opinion should use the global searchable version given that that matches the vim semantics of those commands better than just showing the tabs in the local pane. There's also a question of what to do when you select a tab from another pane, should the focus jump to that pane or should that tab move to the currently focused pane? For now I've implemented the former. Release Notes: - Added `tab_switcher::ToggleAll` to search open tabs from all panes and focus the selected one. --------- Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
52eef3c35d
commit
4dff47ae20
40 changed files with 360 additions and 181 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -14312,6 +14312,7 @@ dependencies = [
|
||||||
"ctor",
|
"ctor",
|
||||||
"editor",
|
"editor",
|
||||||
"env_logger 0.11.8",
|
"env_logger 0.11.8",
|
||||||
|
"fuzzy",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"menu",
|
"menu",
|
||||||
|
@ -14321,6 +14322,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
|
"smol",
|
||||||
"theme",
|
"theme",
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
|
|
|
@ -597,6 +597,10 @@ impl Item for AgentDiff {
|
||||||
editor.added_to_workspace(workspace, window, cx)
|
editor.added_to_workspace(workspace, window, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"Agent Diff".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for AgentDiff {
|
impl Render for AgentDiff {
|
||||||
|
|
|
@ -1045,6 +1045,10 @@ mod tests {
|
||||||
fn include_in_nav_history() -> bool {
|
fn include_in_nav_history() -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"Test".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<()> for AtMentionEditor {}
|
impl EventEmitter<()> for AtMentionEditor {}
|
||||||
|
|
|
@ -193,7 +193,7 @@ impl Focusable for ConfigurationView {
|
||||||
impl Item for ConfigurationView {
|
impl Item for ConfigurationView {
|
||||||
type Event = ConfigurationViewEvent;
|
type Event = ConfigurationViewEvent;
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Configuration".into())
|
"Configuration".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3160,8 +3160,8 @@ impl Focusable for ContextEditor {
|
||||||
impl Item for ContextEditor {
|
impl Item for ContextEditor {
|
||||||
type Event = editor::EditorEvent;
|
type Event = editor::EditorEvent;
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
|
||||||
Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
|
util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
|
||||||
|
|
|
@ -108,8 +108,8 @@ impl EventEmitter<()> for ContextHistory {}
|
||||||
impl Item for ContextHistory {
|
impl Item for ContextHistory {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("History".into())
|
"History".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ fn view_release_notes_locally(
|
||||||
|
|
||||||
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
|
|
||||||
let tab_description = SharedString::from(body.title.to_string());
|
let tab_content = SharedString::from(body.title.to_string());
|
||||||
let editor = cx.new(|cx| {
|
let editor = cx.new(|cx| {
|
||||||
Editor::for_multibuffer(buffer, Some(project), window, cx)
|
Editor::for_multibuffer(buffer, Some(project), window, cx)
|
||||||
});
|
});
|
||||||
|
@ -102,7 +102,7 @@ fn view_release_notes_locally(
|
||||||
editor,
|
editor,
|
||||||
workspace_handle,
|
workspace_handle,
|
||||||
language_registry,
|
language_registry,
|
||||||
Some(tab_description),
|
tab_content,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1517,10 +1517,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
workspace.leader_for_pane(workspace.active_pane())
|
workspace.leader_for_pane(workspace.active_pane())
|
||||||
);
|
);
|
||||||
let item = workspace.active_item(cx).unwrap();
|
let item = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(item.tab_content_text(0, cx), SharedString::from("w.rs"));
|
||||||
item.tab_description(0, cx).unwrap(),
|
|
||||||
SharedString::from("w.rs")
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: in app code, this would be done by the collab_ui.
|
// TODO: in app code, this would be done by the collab_ui.
|
||||||
|
@ -1546,10 +1543,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
executor.run_until_parked();
|
executor.run_until_parked();
|
||||||
workspace_b_project_a.update(&mut cx_b2, |workspace, cx| {
|
workspace_b_project_a.update(&mut cx_b2, |workspace, cx| {
|
||||||
let item = workspace.active_item(cx).unwrap();
|
let item = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(item.tab_content_text(0, cx), SharedString::from("x.rs"));
|
||||||
item.tab_description(0, cx).unwrap(),
|
|
||||||
SharedString::from("x.rs")
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
workspace_a.update_in(cx_a, |workspace, window, cx| {
|
workspace_a.update_in(cx_a, |workspace, window, cx| {
|
||||||
|
@ -1564,7 +1558,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
workspace.leader_for_pane(workspace.active_pane())
|
workspace.leader_for_pane(workspace.active_pane())
|
||||||
);
|
);
|
||||||
let item = workspace.active_pane().read(cx).active_item().unwrap();
|
let item = workspace.active_pane().read(cx).active_item().unwrap();
|
||||||
assert_eq!(item.tab_description(0, cx).unwrap(), "x.rs");
|
assert_eq!(item.tab_content_text(0, cx), "x.rs");
|
||||||
});
|
});
|
||||||
|
|
||||||
// b moves to y.rs in b's project, a is still following but can't yet see
|
// b moves to y.rs in b's project, a is still following but can't yet see
|
||||||
|
@ -1625,10 +1619,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
workspace.leader_for_pane(workspace.active_pane())
|
workspace.leader_for_pane(workspace.active_pane())
|
||||||
);
|
);
|
||||||
let item = workspace.active_item(cx).unwrap();
|
let item = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(item.tab_content_text(0, cx), SharedString::from("y.rs"));
|
||||||
item.tab_description(0, cx).unwrap(),
|
|
||||||
SharedString::from("y.rs")
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1885,13 +1876,7 @@ fn pane_summaries(workspace: &Entity<Workspace>, cx: &mut VisualTestContext) ->
|
||||||
items: pane
|
items: pane
|
||||||
.items()
|
.items()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(ix, item)| {
|
.map(|(ix, item)| (ix == active_ix, item.tab_content_text(0, cx).into()))
|
||||||
(
|
|
||||||
ix == active_ix,
|
|
||||||
item.tab_description(0, cx)
|
|
||||||
.map_or(String::new(), |s| s.to_string()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2179,7 +2164,7 @@ async fn test_following_to_channel_notes_other_workspace(
|
||||||
cx_a.run_until_parked();
|
cx_a.run_until_parked();
|
||||||
workspace_a.update(cx_a, |workspace, cx| {
|
workspace_a.update(cx_a, |workspace, cx| {
|
||||||
let editor = workspace.active_item(cx).unwrap();
|
let editor = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
assert_eq!(editor.tab_content_text(0, cx), "1.txt");
|
||||||
});
|
});
|
||||||
|
|
||||||
// b joins channel and is following a
|
// b joins channel and is following a
|
||||||
|
@ -2188,7 +2173,7 @@ async fn test_following_to_channel_notes_other_workspace(
|
||||||
let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
|
let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
|
||||||
workspace_b.update(cx_b, |workspace, cx| {
|
workspace_b.update(cx_b, |workspace, cx| {
|
||||||
let editor = workspace.active_item(cx).unwrap();
|
let editor = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
assert_eq!(editor.tab_content_text(0, cx), "1.txt");
|
||||||
});
|
});
|
||||||
|
|
||||||
// a opens a second workspace and the channel notes
|
// a opens a second workspace and the channel notes
|
||||||
|
@ -2212,13 +2197,13 @@ async fn test_following_to_channel_notes_other_workspace(
|
||||||
|
|
||||||
workspace_a.update(cx_a, |workspace, cx| {
|
workspace_a.update(cx_a, |workspace, cx| {
|
||||||
let editor = workspace.active_item(cx).unwrap();
|
let editor = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
assert_eq!(editor.tab_content_text(0, cx), "1.txt");
|
||||||
});
|
});
|
||||||
|
|
||||||
// b should follow a back
|
// b should follow a back
|
||||||
workspace_b.update(cx_b, |workspace, cx| {
|
workspace_b.update(cx_b, |workspace, cx| {
|
||||||
let editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
let editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
assert_eq!(editor.tab_content_text(0, cx), "1.txt");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2238,7 +2223,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
cx_a.run_until_parked();
|
cx_a.run_until_parked();
|
||||||
workspace_a.update(cx_a, |workspace, cx| {
|
workspace_a.update(cx_a, |workspace, cx| {
|
||||||
let editor = workspace.active_item(cx).unwrap();
|
let editor = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
assert_eq!(editor.tab_content_text(0, cx), "1.txt");
|
||||||
});
|
});
|
||||||
|
|
||||||
// b joins channel and is following a
|
// b joins channel and is following a
|
||||||
|
@ -2247,7 +2232,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
|
let (workspace_b, cx_b) = client_b.active_workspace(cx_b);
|
||||||
workspace_b.update(cx_b, |workspace, cx| {
|
workspace_b.update(cx_b, |workspace, cx| {
|
||||||
let editor = workspace.active_item(cx).unwrap();
|
let editor = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
assert_eq!(editor.tab_content_text(0, cx), "1.txt");
|
||||||
});
|
});
|
||||||
|
|
||||||
// stop following
|
// stop following
|
||||||
|
@ -2260,7 +2245,7 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
|
|
||||||
workspace_b.update(cx_b, |workspace, cx| {
|
workspace_b.update(cx_b, |workspace, cx| {
|
||||||
let editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
let editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "1.txt");
|
assert_eq!(editor.tab_content_text(0, cx), "1.txt");
|
||||||
});
|
});
|
||||||
|
|
||||||
// a opens a file in a new window
|
// a opens a file in a new window
|
||||||
|
@ -2281,12 +2266,12 @@ async fn test_following_while_deactivated(cx_a: &mut TestAppContext, cx_b: &mut
|
||||||
|
|
||||||
workspace_a.update(cx_a, |workspace, cx| {
|
workspace_a.update(cx_a, |workspace, cx| {
|
||||||
let editor = workspace.active_item(cx).unwrap();
|
let editor = workspace.active_item(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "2.js");
|
assert_eq!(editor.tab_content_text(0, cx), "2.js");
|
||||||
});
|
});
|
||||||
|
|
||||||
// b should follow a back
|
// b should follow a back
|
||||||
workspace_b.update(cx_b, |workspace, cx| {
|
workspace_b.update(cx_b, |workspace, cx| {
|
||||||
let editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
let editor = workspace.active_item_as::<Editor>(cx).unwrap();
|
||||||
assert_eq!(editor.tab_description(0, cx).unwrap(), "2.js");
|
assert_eq!(editor.tab_content_text(0, cx), "2.js");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -540,6 +540,10 @@ impl Item for ChannelView {
|
||||||
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
|
fn to_item_events(event: &EditorEvent, f: impl FnMut(ItemEvent)) {
|
||||||
Editor::to_item_events(event, f)
|
Editor::to_item_events(event, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"Channels".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FollowableItem for ChannelView {
|
impl FollowableItem for ChannelView {
|
||||||
|
|
|
@ -735,8 +735,8 @@ impl From<ComponentId> for ActivePageId {
|
||||||
impl Item for ComponentPreview {
|
impl Item for ComponentPreview {
|
||||||
type Event = ItemEvent;
|
type Event = ItemEvent;
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Component Preview".into())
|
"Component Preview".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -727,8 +727,8 @@ impl Item for DapLogView {
|
||||||
Editor::to_item_events(event, f)
|
Editor::to_item_events(event, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("DAP Logs".into())
|
"DAP Logs".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -170,6 +170,9 @@ impl Focusable for DebugSession {
|
||||||
|
|
||||||
impl Item for DebugSession {
|
impl Item for DebugSession {
|
||||||
type Event = DebugPanelItemEvent;
|
type Event = DebugPanelItemEvent;
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"Debugger".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FollowableItem for DebugSession {
|
impl FollowableItem for DebugSession {
|
||||||
|
|
|
@ -139,8 +139,8 @@ impl Item for SubView {
|
||||||
|
|
||||||
/// This is used to serialize debugger pane layouts
|
/// This is used to serialize debugger pane layouts
|
||||||
/// A SharedString gets converted to a enum and back during serialization/deserialization.
|
/// A SharedString gets converted to a enum and back during serialization/deserialization.
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some(self.kind.to_shared_string())
|
self.kind.to_shared_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content(
|
fn tab_content(
|
||||||
|
|
|
@ -568,6 +568,10 @@ impl Item for ProjectDiagnosticsEditor {
|
||||||
Some("Project Diagnostics".into())
|
Some("Project Diagnostics".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _: &App) -> SharedString {
|
||||||
|
"Diagnostics".into()
|
||||||
|
}
|
||||||
|
|
||||||
fn tab_content(&self, params: TabContentParams, _window: &Window, _: &App) -> AnyElement {
|
fn tab_content(&self, params: TabContentParams, _window: &Window, _: &App) -> AnyElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
|
|
@ -619,9 +619,12 @@ impl Item for Editor {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString {
|
||||||
let path = path_for_buffer(&self.buffer, detail, true, cx)?;
|
if let Some(path) = path_for_buffer(&self.buffer, detail, true, cx) {
|
||||||
Some(path.to_string_lossy().to_string().into())
|
path.to_string_lossy().to_string().into()
|
||||||
|
} else {
|
||||||
|
"untitled".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {
|
fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {
|
||||||
|
|
|
@ -285,8 +285,8 @@ impl Item for ProposedChangesEditor {
|
||||||
Some(Icon::new(IconName::Diff))
|
Some(Icon::new(IconName::Diff))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some(self.title.clone())
|
self.title.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
|
|
|
@ -1398,8 +1398,8 @@ impl Focusable for ExtensionsPage {
|
||||||
impl Item for ExtensionsPage {
|
impl Item for ExtensionsPage {
|
||||||
type Event = ItemEvent;
|
type Event = ItemEvent;
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Extensions".into())
|
"Extensions".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -19,7 +19,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use ui::{Color, Icon, IconName, Label, LabelCommon as _};
|
use ui::{Color, Icon, IconName, Label, LabelCommon as _, SharedString};
|
||||||
use util::{ResultExt, truncate_and_trailoff};
|
use util::{ResultExt, truncate_and_trailoff};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace,
|
Item, ItemHandle as _, ItemNavHistory, ToolbarItemLocation, Workspace,
|
||||||
|
@ -409,10 +409,8 @@ impl Item for CommitView {
|
||||||
Some(Icon::new(IconName::GitBranch).color(Color::Muted))
|
Some(Icon::new(IconName::GitBranch).color(Color::Muted))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content(&self, params: TabContentParams, _window: &Window, _: &App) -> AnyElement {
|
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
|
||||||
let short_sha = self.commit.sha.get(0..7).unwrap_or(&*self.commit.sha);
|
Label::new(self.tab_content_text(params.detail.unwrap_or_default(), cx))
|
||||||
let subject = truncate_and_trailoff(self.commit.message.split('\n').next().unwrap(), 20);
|
|
||||||
Label::new(format!("{short_sha} - {subject}",))
|
|
||||||
.color(if params.selected {
|
.color(if params.selected {
|
||||||
Color::Default
|
Color::Default
|
||||||
} else {
|
} else {
|
||||||
|
@ -421,6 +419,12 @@ impl Item for CommitView {
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
let short_sha = self.commit.sha.get(0..7).unwrap_or(&*self.commit.sha);
|
||||||
|
let subject = truncate_and_trailoff(self.commit.message.split('\n').next().unwrap(), 20);
|
||||||
|
format!("{short_sha} - {subject}").into()
|
||||||
|
}
|
||||||
|
|
||||||
fn tab_tooltip_text(&self, _: &App) -> Option<ui::SharedString> {
|
fn tab_tooltip_text(&self, _: &App) -> Option<ui::SharedString> {
|
||||||
let short_sha = self.commit.sha.get(0..16).unwrap_or(&*self.commit.sha);
|
let short_sha = self.commit.sha.get(0..16).unwrap_or(&*self.commit.sha);
|
||||||
let subject = self.commit.message.split('\n').next().unwrap();
|
let subject = self.commit.message.split('\n').next().unwrap();
|
||||||
|
|
|
@ -547,6 +547,10 @@ impl Item for ProjectDiff {
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _: &App) -> SharedString {
|
||||||
|
"Uncommitted Changes".into()
|
||||||
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
Some("Project Diff Opened")
|
Some("Project Diff Opened")
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ impl Item for ImageView {
|
||||||
Some(file_path.into())
|
Some(file_path.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content(&self, params: TabContentParams, _: &Window, cx: &App) -> AnyElement {
|
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
|
||||||
let project_path = self.image_item.read(cx).project_path(cx);
|
let project_path = self.image_item.read(cx).project_path(cx);
|
||||||
|
|
||||||
let label_color = if ItemSettings::get_global(cx).git_status {
|
let label_color = if ItemSettings::get_global(cx).git_status {
|
||||||
|
@ -121,20 +121,23 @@ impl Item for ImageView {
|
||||||
params.text_color()
|
params.text_color()
|
||||||
};
|
};
|
||||||
|
|
||||||
let title = self
|
Label::new(self.tab_content_text(params.detail.unwrap_or_default(), cx))
|
||||||
.image_item
|
|
||||||
.read(cx)
|
|
||||||
.file
|
|
||||||
.file_name(cx)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
Label::new(title)
|
|
||||||
.single_line()
|
.single_line()
|
||||||
.color(label_color)
|
.color(label_color)
|
||||||
.when(params.preview, |this| this.italic())
|
.when(params.preview, |this| this.italic())
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _: usize, cx: &App) -> SharedString {
|
||||||
|
self.image_item
|
||||||
|
.read(cx)
|
||||||
|
.file
|
||||||
|
.file_name(cx)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {
|
fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {
|
||||||
let path = self.image_item.read(cx).path();
|
let path = self.image_item.read(cx).path();
|
||||||
ItemSettings::get_global(cx)
|
ItemSettings::get_global(cx)
|
||||||
|
|
|
@ -150,8 +150,8 @@ impl Item for KeyContextView {
|
||||||
|
|
||||||
fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
|
fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Keyboard Context".into())
|
"Keyboard Context".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -1058,8 +1058,8 @@ impl Item for LspLogView {
|
||||||
Editor::to_item_events(event, f)
|
Editor::to_item_events(event, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("LSP Logs".into())
|
"LSP Logs".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -401,8 +401,8 @@ impl Item for SyntaxTreeView {
|
||||||
|
|
||||||
fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
|
fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Syntax Tree".into())
|
"Syntax Tree".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -35,8 +35,7 @@ pub struct MarkdownPreviewView {
|
||||||
contents: Option<ParsedMarkdown>,
|
contents: Option<ParsedMarkdown>,
|
||||||
selected_block: usize,
|
selected_block: usize,
|
||||||
list_state: ListState,
|
list_state: ListState,
|
||||||
tab_description: Option<String>,
|
tab_content_text: SharedString,
|
||||||
fallback_tab_description: SharedString,
|
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
parsing_markdown_task: Option<Task<Result<()>>>,
|
parsing_markdown_task: Option<Task<Result<()>>>,
|
||||||
}
|
}
|
||||||
|
@ -130,7 +129,7 @@ impl MarkdownPreviewView {
|
||||||
editor,
|
editor,
|
||||||
workspace_handle,
|
workspace_handle,
|
||||||
language_registry,
|
language_registry,
|
||||||
None,
|
"Markdown Preview".into(),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -141,7 +140,7 @@ impl MarkdownPreviewView {
|
||||||
active_editor: Entity<Editor>,
|
active_editor: Entity<Editor>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
language_registry: Arc<LanguageRegistry>,
|
language_registry: Arc<LanguageRegistry>,
|
||||||
fallback_description: Option<SharedString>,
|
tab_content_text: SharedString,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
|
@ -262,10 +261,8 @@ impl MarkdownPreviewView {
|
||||||
workspace: workspace.clone(),
|
workspace: workspace.clone(),
|
||||||
contents: None,
|
contents: None,
|
||||||
list_state,
|
list_state,
|
||||||
tab_description: None,
|
tab_content_text,
|
||||||
language_registry,
|
language_registry,
|
||||||
fallback_tab_description: fallback_description
|
|
||||||
.unwrap_or_else(|| "Markdown Preview".into()),
|
|
||||||
parsing_markdown_task: None,
|
parsing_markdown_task: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -343,10 +340,8 @@ impl MarkdownPreviewView {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
self.tab_description = editor
|
let tab_content = editor.read(cx).tab_content_text(0, cx);
|
||||||
.read(cx)
|
self.tab_content_text = format!("Preview {}", tab_content).into();
|
||||||
.tab_description(0, cx)
|
|
||||||
.map(|tab_description| format!("Preview {}", tab_description));
|
|
||||||
|
|
||||||
self.active_editor = Some(EditorState {
|
self.active_editor = Some(EditorState {
|
||||||
editor,
|
editor,
|
||||||
|
@ -496,12 +491,8 @@ impl Item for MarkdownPreviewView {
|
||||||
Some(Icon::new(IconName::FileDoc))
|
Some(Icon::new(IconName::FileDoc))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some(if let Some(description) = &self.tab_description {
|
self.tab_content_text.clone()
|
||||||
description.clone().into()
|
|
||||||
} else {
|
|
||||||
self.fallback_tab_description.clone()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -5308,6 +5308,10 @@ impl ProjectItem for TestProjectItemView {
|
||||||
|
|
||||||
impl Item for TestProjectItemView {
|
impl Item for TestProjectItemView {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"Test".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<()> for TestProjectItemView {}
|
impl EventEmitter<()> for TestProjectItemView {}
|
||||||
|
|
|
@ -731,17 +731,21 @@ impl Item for NotebookEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
|
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
|
||||||
|
Label::new(self.tab_content_text(params.detail.unwrap_or(0), cx))
|
||||||
|
.single_line()
|
||||||
|
.color(params.text_color())
|
||||||
|
.when(params.preview, |this| this.italic())
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
|
||||||
let path = &self.notebook_item.read(cx).path;
|
let path = &self.notebook_item.read(cx).path;
|
||||||
let title = path
|
let title = path
|
||||||
.file_name()
|
.file_name()
|
||||||
.unwrap_or_else(|| path.as_os_str())
|
.unwrap_or_else(|| path.as_os_str())
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.to_string();
|
.to_string();
|
||||||
Label::new(title)
|
title.into()
|
||||||
.single_line()
|
|
||||||
.color(params.text_color())
|
|
||||||
.when(params.preview, |this| this.italic())
|
|
||||||
.into_any_element()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
|
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
|
||||||
|
|
|
@ -178,8 +178,8 @@ impl Focusable for ReplSessionsPage {
|
||||||
impl Item for ReplSessionsPage {
|
impl Item for ReplSessionsPage {
|
||||||
type Event = ItemEvent;
|
type Event = ItemEvent;
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("REPL Sessions".into())
|
"REPL Sessions".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -446,7 +446,7 @@ impl Item for ProjectSearchView {
|
||||||
Some(Icon::new(IconName::MagnifyingGlass))
|
Some(Icon::new(IconName::MagnifyingGlass))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content_text(&self, _: &Window, cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
|
||||||
let last_query: Option<SharedString> = self
|
let last_query: Option<SharedString> = self
|
||||||
.entity
|
.entity
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -457,11 +457,10 @@ impl Item for ProjectSearchView {
|
||||||
let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN);
|
let query_text = util::truncate_and_trailoff(&query, MAX_TAB_TITLE_LEN);
|
||||||
query_text.into()
|
query_text.into()
|
||||||
});
|
});
|
||||||
Some(
|
|
||||||
last_query
|
last_query
|
||||||
.filter(|query| !query.is_empty())
|
.filter(|query| !query.is_empty())
|
||||||
.unwrap_or_else(|| "Project Search".into()),
|
.unwrap_or_else(|| "Project Search".into())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -289,8 +289,8 @@ impl EventEmitter<()> for ProjectIndexDebugView {}
|
||||||
impl Item for ProjectIndexDebugView {
|
impl Item for ProjectIndexDebugView {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Project Index (Debug)".into())
|
"Project Index (Debug)".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clone_on_split(
|
fn clone_on_split(
|
||||||
|
|
|
@ -160,8 +160,8 @@ impl Item for SettingsPage {
|
||||||
Some(Icon::new(IconName::Settings))
|
Some(Icon::new(IconName::Settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Settings".into())
|
"Settings".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_toolbar(&self) -> bool {
|
fn show_toolbar(&self) -> bool {
|
||||||
|
|
|
@ -15,6 +15,7 @@ doctest = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
editor.workspace = true
|
editor.workspace = true
|
||||||
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
|
@ -22,6 +23,7 @@ project.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
smol.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
util.workspace = true
|
util.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
|
@ -3,6 +3,7 @@ mod tab_switcher_tests;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::items::entry_git_aware_label_color;
|
use editor::items::entry_git_aware_label_color;
|
||||||
|
use fuzzy::StringMatchCandidate;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, App, Context, DismissEvent, Entity, EntityId, EventEmitter, FocusHandle,
|
Action, AnyElement, App, Context, DismissEvent, Entity, EntityId, EventEmitter, FocusHandle,
|
||||||
Focusable, Modifiers, ModifiersChangedEvent, MouseButton, MouseUpEvent, ParentElement, Render,
|
Focusable, Modifiers, ModifiersChangedEvent, MouseButton, MouseUpEvent, ParentElement, Render,
|
||||||
|
@ -13,7 +14,7 @@ use project::Project;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::{cmp::Reverse, sync::Arc};
|
||||||
use ui::{ListItem, ListItemSpacing, Tooltip, prelude::*};
|
use ui::{ListItem, ListItemSpacing, Tooltip, prelude::*};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -32,7 +33,7 @@ pub struct Toggle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_actions!(tab_switcher, [Toggle]);
|
impl_actions!(tab_switcher, [Toggle]);
|
||||||
actions!(tab_switcher, [CloseSelectedItem]);
|
actions!(tab_switcher, [CloseSelectedItem, ToggleAll]);
|
||||||
|
|
||||||
pub struct TabSwitcher {
|
pub struct TabSwitcher {
|
||||||
picker: Entity<Picker<TabSwitcherDelegate>>,
|
picker: Entity<Picker<TabSwitcherDelegate>>,
|
||||||
|
@ -53,7 +54,19 @@ impl TabSwitcher {
|
||||||
) {
|
) {
|
||||||
workspace.register_action(|workspace, action: &Toggle, window, cx| {
|
workspace.register_action(|workspace, action: &Toggle, window, cx| {
|
||||||
let Some(tab_switcher) = workspace.active_modal::<Self>(cx) else {
|
let Some(tab_switcher) = workspace.active_modal::<Self>(cx) else {
|
||||||
Self::open(action, workspace, window, cx);
|
Self::open(workspace, action.select_last, false, window, cx);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
tab_switcher.update(cx, |tab_switcher, cx| {
|
||||||
|
tab_switcher
|
||||||
|
.picker
|
||||||
|
.update(cx, |picker, cx| picker.cycle_selection(window, cx))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
workspace.register_action(|workspace, _action: &ToggleAll, window, cx| {
|
||||||
|
let Some(tab_switcher) = workspace.active_modal::<Self>(cx) else {
|
||||||
|
Self::open(workspace, false, true, window, cx);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,8 +79,9 @@ impl TabSwitcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(
|
fn open(
|
||||||
action: &Toggle,
|
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
|
select_last: bool,
|
||||||
|
is_global: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) {
|
) {
|
||||||
|
@ -90,24 +104,43 @@ impl TabSwitcher {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let weak_workspace = workspace.weak_handle();
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
workspace.toggle_modal(window, cx, |window, cx| {
|
workspace.toggle_modal(window, cx, |window, cx| {
|
||||||
let delegate = TabSwitcherDelegate::new(
|
let delegate = TabSwitcherDelegate::new(
|
||||||
project,
|
project,
|
||||||
action,
|
select_last,
|
||||||
cx.entity().downgrade(),
|
cx.entity().downgrade(),
|
||||||
weak_pane,
|
weak_pane,
|
||||||
|
weak_workspace,
|
||||||
|
is_global,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
TabSwitcher::new(delegate, window, cx)
|
TabSwitcher::new(delegate, window, is_global, cx)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new(delegate: TabSwitcherDelegate, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
fn new(
|
||||||
|
delegate: TabSwitcherDelegate,
|
||||||
|
window: &mut Window,
|
||||||
|
is_global: bool,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let init_modifiers = if is_global {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
window.modifiers().modified().then_some(window.modifiers())
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
picker: cx.new(|cx| Picker::nonsearchable_uniform_list(delegate, window, cx)),
|
picker: cx.new(|cx| {
|
||||||
init_modifiers: window.modifiers().modified().then_some(window.modifiers()),
|
if is_global {
|
||||||
|
Picker::uniform_list(delegate, window, cx)
|
||||||
|
} else {
|
||||||
|
Picker::nonsearchable_uniform_list(delegate, window, cx)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
init_modifiers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +196,9 @@ impl Render for TabSwitcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
struct TabMatch {
|
struct TabMatch {
|
||||||
|
pane: WeakEntity<Pane>,
|
||||||
item_index: usize,
|
item_index: usize,
|
||||||
item: Box<dyn ItemHandle>,
|
item: Box<dyn ItemHandle>,
|
||||||
detail: usize,
|
detail: usize,
|
||||||
|
@ -175,27 +210,34 @@ pub struct TabSwitcherDelegate {
|
||||||
tab_switcher: WeakEntity<TabSwitcher>,
|
tab_switcher: WeakEntity<TabSwitcher>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
pane: WeakEntity<Pane>,
|
pane: WeakEntity<Pane>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
matches: Vec<TabMatch>,
|
matches: Vec<TabMatch>,
|
||||||
|
is_all_panes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TabSwitcherDelegate {
|
impl TabSwitcherDelegate {
|
||||||
|
#[allow(clippy::complexity)]
|
||||||
fn new(
|
fn new(
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
action: &Toggle,
|
select_last: bool,
|
||||||
tab_switcher: WeakEntity<TabSwitcher>,
|
tab_switcher: WeakEntity<TabSwitcher>,
|
||||||
pane: WeakEntity<Pane>,
|
pane: WeakEntity<Pane>,
|
||||||
|
workspace: WeakEntity<Workspace>,
|
||||||
|
is_all_panes: bool,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<TabSwitcher>,
|
cx: &mut Context<TabSwitcher>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::subscribe_to_updates(&pane, window, cx);
|
Self::subscribe_to_updates(&pane, window, cx);
|
||||||
Self {
|
Self {
|
||||||
select_last: action.select_last,
|
select_last,
|
||||||
tab_switcher,
|
tab_switcher,
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
pane,
|
pane,
|
||||||
|
workspace,
|
||||||
project,
|
project,
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
|
is_all_panes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +254,8 @@ impl TabSwitcherDelegate {
|
||||||
PaneEvent::AddItem { .. }
|
PaneEvent::AddItem { .. }
|
||||||
| PaneEvent::RemovedItem { .. }
|
| PaneEvent::RemovedItem { .. }
|
||||||
| PaneEvent::Remove { .. } => tab_switcher.picker.update(cx, |picker, cx| {
|
| PaneEvent::Remove { .. } => tab_switcher.picker.update(cx, |picker, cx| {
|
||||||
picker.delegate.update_matches(window, cx);
|
let query = picker.query(cx);
|
||||||
|
picker.delegate.update_matches(query, window, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}),
|
}),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -221,7 +264,91 @@ impl TabSwitcherDelegate {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(&mut self, _window: &mut Window, cx: &mut App) {
|
fn update_all_pane_matches(&mut self, query: String, window: &mut Window, cx: &mut App) {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let mut all_items = Vec::new();
|
||||||
|
let mut item_index = 0;
|
||||||
|
for pane_handle in workspace.read(cx).panes() {
|
||||||
|
let pane = pane_handle.read(cx);
|
||||||
|
let items: Vec<Box<dyn ItemHandle>> =
|
||||||
|
pane.items().map(|item| item.boxed_clone()).collect();
|
||||||
|
for ((_detail, item), detail) in items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.zip(tab_details(&items, window, cx))
|
||||||
|
{
|
||||||
|
all_items.push(TabMatch {
|
||||||
|
pane: pane_handle.downgrade(),
|
||||||
|
item_index,
|
||||||
|
item: item.clone(),
|
||||||
|
detail,
|
||||||
|
preview: pane.is_active_preview_item(item.item_id()),
|
||||||
|
});
|
||||||
|
item_index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
eprintln!("");
|
||||||
|
all_items
|
||||||
|
.sort_by_key(|tab| (Reverse(history.get(&tab.item.item_id())), tab.item_index));
|
||||||
|
all_items
|
||||||
|
} else {
|
||||||
|
let candidates = all_items
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.flat_map(|(ix, tab_match)| {
|
||||||
|
Some(StringMatchCandidate::new(
|
||||||
|
ix,
|
||||||
|
&tab_match.item.tab_content_text(0, cx),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
smol::block_on(fuzzy::match_strings(
|
||||||
|
&candidates,
|
||||||
|
&query,
|
||||||
|
true,
|
||||||
|
10000,
|
||||||
|
&Default::default(),
|
||||||
|
cx.background_executor().clone(),
|
||||||
|
))
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| all_items[m.candidate_id].clone())
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
let selected_item_id = self.selected_item_id();
|
||||||
|
self.matches = matches;
|
||||||
|
self.selected_index = self.compute_selected_index(selected_item_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_matches(
|
||||||
|
&mut self,
|
||||||
|
query: String,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Picker<Self>>,
|
||||||
|
) {
|
||||||
|
if self.is_all_panes {
|
||||||
|
// needed because we need to borrow the workspace, but that may be borrowed when the picker
|
||||||
|
// calls update_matches.
|
||||||
|
let this = cx.entity();
|
||||||
|
window.defer(cx, move |window, cx| {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.delegate.update_all_pane_matches(query, window, cx);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
let selected_item_id = self.selected_item_id();
|
let selected_item_id = self.selected_item_id();
|
||||||
self.matches.clear();
|
self.matches.clear();
|
||||||
let Some(pane) = self.pane.upgrade() else {
|
let Some(pane) = self.pane.upgrade() else {
|
||||||
|
@ -240,8 +367,9 @@ impl TabSwitcherDelegate {
|
||||||
items
|
items
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.zip(tab_details(&items, cx))
|
.zip(tab_details(&items, window, cx))
|
||||||
.map(|((item_index, item), detail)| TabMatch {
|
.map(|((item_index, item), detail)| TabMatch {
|
||||||
|
pane: self.pane.clone(),
|
||||||
item_index,
|
item_index,
|
||||||
item: item.boxed_clone(),
|
item: item.boxed_clone(),
|
||||||
detail,
|
detail,
|
||||||
|
@ -348,11 +476,11 @@ impl PickerDelegate for TabSwitcherDelegate {
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(
|
||||||
&mut self,
|
&mut self,
|
||||||
_raw_query: String,
|
raw_query: String,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Task<()> {
|
) -> Task<()> {
|
||||||
self.update_matches(window, cx);
|
self.update_matches(raw_query, window, cx);
|
||||||
Task::ready(())
|
Task::ready(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,15 +490,17 @@ impl PickerDelegate for TabSwitcherDelegate {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<TabSwitcherDelegate>>,
|
cx: &mut Context<Picker<TabSwitcherDelegate>>,
|
||||||
) {
|
) {
|
||||||
let Some(pane) = self.pane.upgrade() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(selected_match) = self.matches.get(self.selected_index()) else {
|
let Some(selected_match) = self.matches.get(self.selected_index()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
pane.update(cx, |pane, cx| {
|
selected_match
|
||||||
pane.activate_item(selected_match.item_index, true, true, window, cx);
|
.pane
|
||||||
});
|
.update(cx, |pane, cx| {
|
||||||
|
if let Some(index) = pane.index_for_item(selected_match.item.as_ref()) {
|
||||||
|
pane.activate_item(index, true, true, window, cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<TabSwitcherDelegate>>) {
|
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<TabSwitcherDelegate>>) {
|
||||||
|
|
|
@ -1462,6 +1462,11 @@ impl Item for TerminalView {
|
||||||
.into_any()
|
.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString {
|
||||||
|
let terminal = self.terminal().read(cx);
|
||||||
|
terminal.title(detail == 0).into()
|
||||||
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
@ -796,8 +796,8 @@ fn generate_commands(_: &App) -> Vec<VimCommand> {
|
||||||
VimCommand::new(("bf", "irst"), workspace::ActivateItem(0)),
|
VimCommand::new(("bf", "irst"), workspace::ActivateItem(0)),
|
||||||
VimCommand::new(("br", "ewind"), workspace::ActivateItem(0)),
|
VimCommand::new(("br", "ewind"), workspace::ActivateItem(0)),
|
||||||
VimCommand::new(("bl", "ast"), workspace::ActivateLastItem),
|
VimCommand::new(("bl", "ast"), workspace::ActivateLastItem),
|
||||||
VimCommand::str(("buffers", ""), "tab_switcher::Toggle"),
|
VimCommand::str(("buffers", ""), "tab_switcher::ToggleAll"),
|
||||||
VimCommand::str(("ls", ""), "tab_switcher::Toggle"),
|
VimCommand::str(("ls", ""), "tab_switcher::ToggleAll"),
|
||||||
VimCommand::new(("new", ""), workspace::NewFileSplitHorizontal),
|
VimCommand::new(("new", ""), workspace::NewFileSplitHorizontal),
|
||||||
VimCommand::new(("vne", "w"), workspace::NewFileSplitVertical),
|
VimCommand::new(("vne", "w"), workspace::NewFileSplitVertical),
|
||||||
VimCommand::new(("tabe", "dit"), workspace::NewFile),
|
VimCommand::new(("tabe", "dit"), workspace::NewFile),
|
||||||
|
|
|
@ -420,8 +420,8 @@ impl Focusable for WelcomePage {
|
||||||
impl Item for WelcomePage {
|
impl Item for WelcomePage {
|
||||||
type Event = ItemEvent;
|
type Event = ItemEvent;
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some("Welcome".into())
|
"Welcome".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -30,7 +30,7 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use theme::Theme;
|
use theme::Theme;
|
||||||
use ui::{Color, Element as _, Icon, IntoElement, Label, LabelCommon};
|
use ui::{Color, Icon, IntoElement, Label, LabelCommon};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
||||||
pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
|
pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
|
||||||
|
@ -247,10 +247,8 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
|
||||||
///
|
///
|
||||||
/// By default this returns a [`Label`] that displays that text from
|
/// By default this returns a [`Label`] that displays that text from
|
||||||
/// `tab_content_text`.
|
/// `tab_content_text`.
|
||||||
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
|
fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
|
||||||
let Some(text) = self.tab_content_text(window, cx) else {
|
let text = self.tab_content_text(params.detail.unwrap_or_default(), cx);
|
||||||
return gpui::Empty.into_any();
|
|
||||||
};
|
|
||||||
|
|
||||||
Label::new(text)
|
Label::new(text)
|
||||||
.color(params.text_color())
|
.color(params.text_color())
|
||||||
|
@ -258,11 +256,7 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the textual contents of the tab.
|
/// Returns the textual contents of the tab.
|
||||||
///
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString;
|
||||||
/// Use this if you don't need to customize the tab contents.
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
|
fn tab_icon(&self, _window: &Window, _cx: &App) -> Option<Icon> {
|
||||||
None
|
None
|
||||||
|
@ -283,10 +277,6 @@ pub trait Item: Focusable + EventEmitter<Self::Event> + Render + Sized {
|
||||||
self.tab_tooltip_text(cx).map(TabTooltipContent::Text)
|
self.tab_tooltip_text(cx).map(TabTooltipContent::Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_description(&self, _: usize, _: &App) -> Option<SharedString> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {}
|
fn to_item_events(_event: &Self::Event, _f: impl FnMut(ItemEvent)) {}
|
||||||
|
|
||||||
fn deactivated(&mut self, _window: &mut Window, _: &mut Context<Self>) {}
|
fn deactivated(&mut self, _window: &mut Window, _: &mut Context<Self>) {}
|
||||||
|
@ -492,8 +482,8 @@ pub trait ItemHandle: 'static + Send {
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
handler: Box<dyn Fn(ItemEvent, &mut Window, &mut App)>,
|
handler: Box<dyn Fn(ItemEvent, &mut Window, &mut App)>,
|
||||||
) -> gpui::Subscription;
|
) -> gpui::Subscription;
|
||||||
fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString>;
|
|
||||||
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement;
|
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement;
|
||||||
|
fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString;
|
||||||
fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon>;
|
fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon>;
|
||||||
fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString>;
|
fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString>;
|
||||||
fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent>;
|
fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent>;
|
||||||
|
@ -616,13 +606,12 @@ impl<T: Item> ItemHandle for Entity<T> {
|
||||||
self.read(cx).telemetry_event_text()
|
self.read(cx).telemetry_event_text()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_description(&self, detail: usize, cx: &App) -> Option<SharedString> {
|
|
||||||
self.read(cx).tab_description(detail, cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
|
fn tab_content(&self, params: TabContentParams, window: &Window, cx: &App) -> AnyElement {
|
||||||
self.read(cx).tab_content(params, window, cx)
|
self.read(cx).tab_content(params, window, cx)
|
||||||
}
|
}
|
||||||
|
fn tab_content_text(&self, detail: usize, cx: &App) -> SharedString {
|
||||||
|
self.read(cx).tab_content_text(detail, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon> {
|
fn tab_icon(&self, window: &Window, cx: &App) -> Option<Icon> {
|
||||||
self.read(cx).tab_icon(window, cx)
|
self.read(cx).tab_icon(window, cx)
|
||||||
|
@ -1450,11 +1439,15 @@ pub mod test {
|
||||||
f(*event)
|
f(*event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_description(&self, detail: usize, _: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, detail: usize, _cx: &App) -> SharedString {
|
||||||
self.tab_descriptions.as_ref().and_then(|descriptions| {
|
self.tab_descriptions
|
||||||
let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
|
.as_ref()
|
||||||
Some(description.into())
|
.and_then(|descriptions| {
|
||||||
})
|
let description = *descriptions.get(detail).or_else(|| descriptions.last())?;
|
||||||
|
description.into()
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -502,6 +502,7 @@ impl Pane {
|
||||||
fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn focus_in(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if !self.was_focused {
|
if !self.was_focused {
|
||||||
self.was_focused = true;
|
self.was_focused = true;
|
||||||
|
self.update_history(self.active_item_index);
|
||||||
cx.emit(Event::Focus);
|
cx.emit(Event::Focus);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -1095,17 +1096,7 @@ impl Pane {
|
||||||
prev_item.deactivated(window, cx);
|
prev_item.deactivated(window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(newly_active_item) = self.items.get(index) {
|
self.update_history(index);
|
||||||
self.activation_history
|
|
||||||
.retain(|entry| entry.entity_id != newly_active_item.item_id());
|
|
||||||
self.activation_history.push(ActivationHistoryEntry {
|
|
||||||
entity_id: newly_active_item.item_id(),
|
|
||||||
timestamp: self
|
|
||||||
.next_activation_timestamp
|
|
||||||
.fetch_add(1, Ordering::SeqCst),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
self.update_toolbar(window, cx);
|
self.update_toolbar(window, cx);
|
||||||
self.update_status_bar(window, cx);
|
self.update_status_bar(window, cx);
|
||||||
|
|
||||||
|
@ -1127,6 +1118,19 @@ impl Pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_history(&mut self, index: usize) {
|
||||||
|
if let Some(newly_active_item) = self.items.get(index) {
|
||||||
|
self.activation_history
|
||||||
|
.retain(|entry| entry.entity_id != newly_active_item.item_id());
|
||||||
|
self.activation_history.push(ActivationHistoryEntry {
|
||||||
|
entity_id: newly_active_item.item_id(),
|
||||||
|
timestamp: self
|
||||||
|
.next_activation_timestamp
|
||||||
|
.fetch_add(1, Ordering::SeqCst),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn activate_prev_item(
|
pub fn activate_prev_item(
|
||||||
&mut self,
|
&mut self,
|
||||||
activate_pane: bool,
|
activate_pane: bool,
|
||||||
|
@ -2634,7 +2638,7 @@ impl Pane {
|
||||||
.items
|
.items
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.zip(tab_details(&self.items, cx))
|
.zip(tab_details(&self.items, window, cx))
|
||||||
.map(|((ix, item), detail)| {
|
.map(|((ix, item), detail)| {
|
||||||
self.render_tab(ix, &**item, detail, &focus_handle, window, cx)
|
self.render_tab(ix, &**item, detail, &focus_handle, window, cx)
|
||||||
})
|
})
|
||||||
|
@ -3632,7 +3636,7 @@ fn dirty_message_for(buffer_path: Option<ProjectPath>) -> String {
|
||||||
format!("{path} contains unsaved edits. Do you want to save it?")
|
format!("{path} contains unsaved edits. Do you want to save it?")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab_details(items: &[Box<dyn ItemHandle>], cx: &App) -> Vec<usize> {
|
pub fn tab_details(items: &[Box<dyn ItemHandle>], _window: &Window, cx: &App) -> Vec<usize> {
|
||||||
let mut tab_details = items.iter().map(|_| 0).collect::<Vec<_>>();
|
let mut tab_details = items.iter().map(|_| 0).collect::<Vec<_>>();
|
||||||
let mut tab_descriptions = HashMap::default();
|
let mut tab_descriptions = HashMap::default();
|
||||||
let mut done = false;
|
let mut done = false;
|
||||||
|
@ -3641,15 +3645,12 @@ pub fn tab_details(items: &[Box<dyn ItemHandle>], cx: &App) -> Vec<usize> {
|
||||||
|
|
||||||
// Store item indices by their tab description.
|
// Store item indices by their tab description.
|
||||||
for (ix, (item, detail)) in items.iter().zip(&tab_details).enumerate() {
|
for (ix, (item, detail)) in items.iter().zip(&tab_details).enumerate() {
|
||||||
if let Some(description) = item.tab_description(*detail, cx) {
|
let description = item.tab_content_text(*detail, cx);
|
||||||
if *detail == 0
|
if *detail == 0 || description != item.tab_content_text(detail - 1, cx) {
|
||||||
|| Some(&description) != item.tab_description(detail - 1, cx).as_ref()
|
tab_descriptions
|
||||||
{
|
.entry(description)
|
||||||
tab_descriptions
|
.or_insert(Vec::new())
|
||||||
.entry(description)
|
.push(ix);
|
||||||
.or_insert(Vec::new())
|
|
||||||
.push(ix);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,8 +93,8 @@ impl Item for SharedScreen {
|
||||||
Some(Icon::new(IconName::Screen))
|
Some(Icon::new(IconName::Screen))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
Some(format!("{}'s screen", self.user.github_login).into())
|
format!("{}'s screen", self.user.github_login).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -80,9 +80,9 @@ impl Item for ThemePreview {
|
||||||
|
|
||||||
fn to_item_events(_: &Self::Event, _: impl FnMut(crate::item::ItemEvent)) {}
|
fn to_item_events(_: &Self::Event, _: impl FnMut(crate::item::ItemEvent)) {}
|
||||||
|
|
||||||
fn tab_content_text(&self, window: &Window, cx: &App) -> Option<SharedString> {
|
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
|
||||||
let name = cx.theme().name.clone();
|
let name = cx.theme().name.clone();
|
||||||
Some(format!("{} Preview", name).into())
|
format!("{} Preview", name).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn telemetry_event_text(&self) -> Option<&'static str> {
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
|
|
@ -1460,6 +1460,27 @@ impl Workspace {
|
||||||
&self.project
|
&self.project
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn recently_activated_items(&self, cx: &App) -> HashMap<EntityId, usize> {
|
||||||
|
let mut history: HashMap<EntityId, usize> = HashMap::default();
|
||||||
|
|
||||||
|
for pane_handle in &self.panes {
|
||||||
|
let pane = pane_handle.read(cx);
|
||||||
|
|
||||||
|
for entry in pane.activation_history() {
|
||||||
|
history.insert(
|
||||||
|
entry.entity_id,
|
||||||
|
history
|
||||||
|
.get(&entry.entity_id)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.max(entry.timestamp),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
history
|
||||||
|
}
|
||||||
|
|
||||||
pub fn recent_navigation_history_iter(
|
pub fn recent_navigation_history_iter(
|
||||||
&self,
|
&self,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
|
@ -2105,7 +2126,7 @@ impl Workspace {
|
||||||
.flat_map(|pane| {
|
.flat_map(|pane| {
|
||||||
pane.read(cx).items().filter_map(|item| {
|
pane.read(cx).items().filter_map(|item| {
|
||||||
if item.is_dirty(cx) {
|
if item.is_dirty(cx) {
|
||||||
item.tab_description(0, cx);
|
item.tab_content_text(0, cx);
|
||||||
Some((pane.downgrade(), item.boxed_clone()))
|
Some((pane.downgrade(), item.boxed_clone()))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -9022,6 +9043,9 @@ mod tests {
|
||||||
|
|
||||||
impl Item for TestPngItemView {
|
impl Item for TestPngItemView {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl EventEmitter<()> for TestPngItemView {}
|
impl EventEmitter<()> for TestPngItemView {}
|
||||||
impl Focusable for TestPngItemView {
|
impl Focusable for TestPngItemView {
|
||||||
|
@ -9094,6 +9118,9 @@ mod tests {
|
||||||
|
|
||||||
impl Item for TestIpynbItemView {
|
impl Item for TestIpynbItemView {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl EventEmitter<()> for TestIpynbItemView {}
|
impl EventEmitter<()> for TestIpynbItemView {}
|
||||||
impl Focusable for TestIpynbItemView {
|
impl Focusable for TestIpynbItemView {
|
||||||
|
@ -9137,6 +9164,9 @@ mod tests {
|
||||||
|
|
||||||
impl Item for TestAlternatePngItemView {
|
impl Item for TestAlternatePngItemView {
|
||||||
type Event = ();
|
type Event = ();
|
||||||
|
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||||
|
"".into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<()> for TestAlternatePngItemView {}
|
impl EventEmitter<()> for TestAlternatePngItemView {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue