Merge pull request #1356 from zed-industries/differentiate-same-tab-titles

Differentiate among tabs with the same name
This commit is contained in:
Antonio Scandurra 2022-07-15 08:30:22 +02:00 committed by GitHub
commit d89465c451
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 248 additions and 28 deletions

View file

@ -256,7 +256,11 @@ pub trait Item: View {
fn navigate(&mut self, _: Box<dyn Any>, _: &mut ViewContext<Self>) -> bool {
false
}
fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn tab_description<'a>(&'a self, _: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
None
}
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
-> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
fn is_singleton(&self, cx: &AppContext) -> bool;
@ -409,7 +413,9 @@ impl<T: FollowableItem> FollowableItemHandle for ViewHandle<T> {
}
pub trait ItemHandle: 'static + fmt::Debug {
fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>>;
fn tab_content(&self, detail: Option<usize>, style: &theme::Tab, cx: &AppContext)
-> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
fn project_entry_ids(&self, cx: &AppContext) -> SmallVec<[ProjectEntryId; 3]>;
fn is_singleton(&self, cx: &AppContext) -> bool;
@ -463,8 +469,17 @@ impl dyn ItemHandle {
}
impl<T: Item> ItemHandle for ViewHandle<T> {
fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox {
self.read(cx).tab_content(style, cx)
fn tab_description<'a>(&self, detail: usize, cx: &'a AppContext) -> Option<Cow<'a, str>> {
self.read(cx).tab_description(detail, cx)
}
fn tab_content(
&self,
detail: Option<usize>,
style: &theme::Tab,
cx: &AppContext,
) -> ElementBox {
self.read(cx).tab_content(detail, style, cx)
}
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath> {
@ -2686,11 +2701,62 @@ fn open_new(app_state: &Arc<AppState>, cx: &mut MutableAppContext) {
#[cfg(test)]
mod tests {
use std::cell::Cell;
use super::*;
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
use project::{FakeFs, Project, ProjectEntryId};
use serde_json::json;
#[gpui::test]
async fn test_tab_disambiguation(cx: &mut TestAppContext) {
cx.foreground().forbid_parking();
Settings::test_async(cx);
let fs = FakeFs::new(cx.background());
let project = Project::test(fs, [], cx).await;
let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
// Adding an item with no ambiguity renders the tab without detail.
let item1 = cx.add_view(window_id, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b1/c", "a/b1/c"]);
item
});
workspace.update(cx, |workspace, cx| {
workspace.add_item(Box::new(item1.clone()), cx);
});
item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), None));
// Adding an item that creates ambiguity increases the level of detail on
// both tabs.
let item2 = cx.add_view(window_id, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
});
workspace.update(cx, |workspace, cx| {
workspace.add_item(Box::new(item2.clone()), cx);
});
item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
// Adding an item that creates ambiguity increases the level of detail only
// on the ambiguous tabs. In this case, the ambiguity can't be resolved so
// we stop at the highest detail available.
let item3 = cx.add_view(window_id, |_| {
let mut item = TestItem::new();
item.tab_descriptions = Some(vec!["c", "b2/c", "a/b2/c"]);
item
});
workspace.update(cx, |workspace, cx| {
workspace.add_item(Box::new(item3.clone()), cx);
});
item1.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(1)));
item2.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
item3.read_with(cx, |item, _| assert_eq!(item.tab_detail.get(), Some(3)));
}
#[gpui::test]
async fn test_tracking_active_path(cx: &mut TestAppContext) {
cx.foreground().forbid_parking();
@ -3211,6 +3277,8 @@ mod tests {
project_entry_ids: Vec<ProjectEntryId>,
project_path: Option<ProjectPath>,
nav_history: Option<ItemNavHistory>,
tab_descriptions: Option<Vec<&'static str>>,
tab_detail: Cell<Option<usize>>,
}
enum TestItemEvent {
@ -3230,6 +3298,8 @@ mod tests {
project_entry_ids: self.project_entry_ids.clone(),
project_path: self.project_path.clone(),
nav_history: None,
tab_descriptions: None,
tab_detail: Default::default(),
}
}
}
@ -3247,6 +3317,8 @@ mod tests {
project_path: None,
is_singleton: true,
nav_history: None,
tab_descriptions: None,
tab_detail: Default::default(),
}
}
@ -3277,7 +3349,15 @@ mod tests {
}
impl Item for TestItem {
fn tab_content(&self, _: &theme::Tab, _: &AppContext) -> ElementBox {
fn tab_description<'a>(&'a self, detail: usize, _: &'a AppContext) -> Option<Cow<'a, str>> {
self.tab_descriptions.as_ref().and_then(|descriptions| {
let description = *descriptions.get(detail).or(descriptions.last())?;
Some(description.into())
})
}
fn tab_content(&self, detail: Option<usize>, _: &theme::Tab, _: &AppContext) -> ElementBox {
self.tab_detail.set(detail);
Empty::new().boxed()
}