Skip to content

Commit

Permalink
Fixed #4897: supported tab details to disambiguate identical names
Browse files Browse the repository at this point in the history
- Supported additional tab details for tabs with identical titles.
- If there existed multiple tabs with the same name, each tab would have its partial relative path displayed after the title.

Signed-off-by: fangnx <[email protected]>
  • Loading branch information
fangnx committed Jul 22, 2019
1 parent 87a35c1 commit 7f08365
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 5 deletions.
101 changes: 96 additions & 5 deletions packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ export class TabBarRenderer extends TabBar.Renderer {
/**
* Render tabs with the default DOM structure, but additionally register a context
* menu listener.
* @param {boolean} [isInSidePanel] - Whether the label is in the side panel.
*/
renderTab(data: SideBarRenderData): VirtualElement {
renderTab(data: SideBarRenderData, isInSidePanel?: boolean): VirtualElement {
const title = data.title;
const id = this.createTabId(data.title);
const key = this.createTabKey(data);
Expand All @@ -96,7 +97,7 @@ export class TabBarRenderer extends TabBar.Renderer {
ondblclick: this.handleDblClickEvent
},
this.renderIcon(data),
this.renderLabel(data),
this.renderLabel(data, isInSidePanel),
this.renderCloseIcon(data)
);
}
Expand Down Expand Up @@ -131,8 +132,9 @@ export class TabBarRenderer extends TabBar.Renderer {
/**
* If size information is available for the label, set it as inline style. Tab padding
* and icon size are also considered in the `top` position.
* @param {boolean} [isInSidePanel] - Whether the label is in the side panel.
*/
renderLabel(data: SideBarRenderData): VirtualElement {
renderLabel(data: SideBarRenderData, isInSidePanel?: boolean): VirtualElement {
const labelSize = data.labelSize;
const iconSize = data.iconSize;
let width: string | undefined;
Expand All @@ -152,7 +154,96 @@ export class TabBarRenderer extends TabBar.Renderer {
top = `${paddingTop + iconHeight}px`;
}
const style: ElementInlineStyle = { width, height, top };
return h.div({ className: 'p-TabBar-tabLabel', style }, data.title.label);
if (isInSidePanel || (this.tabBar && this.tabBar.titles.length < 2)) {
return h.div({ className: 'p-TabBar-tabLabel', style }, data.title.label);
}
const originalToDisplayedMap = this.findDuplicateLabels();
const labelDetails: string | undefined = originalToDisplayedMap.get(data.title.caption);
return (labelDetails)
? h.div({ className: 'p-TabBar-tabLabelWrapper' },
h.div({ className: 'p-TabBar-tabLabel', style }, data.title.label),
h.div({ className: 'p-TabBar-tabLabelDetails', style }, labelDetails))
: h.div({ className: 'p-TabBar-tabLabel', style }, data.title.label);
}

/**
* Find duplicate labels from the currently opened tabs in the tab bar.
* Return the approriate partial paths that can distinguish the identical labels.
* E.g., a/p/index.ts => a/..., b/p/index.ts => b/...
* @returns {Map<string, string>} A map from each tab's original path to its displayed partial path.
*/
findDuplicateLabels(): Map<string, string> {
const titles = this.tabBar!.titles;
// Filter from all tabs to group them by the distinct label (file name).
const labelGroups = new Map<string, Map<number, string>>();
titles.forEach((title, index) => {
if (!labelGroups.has(title.label)) {
labelGroups.set(title.label, new Map<number, string>());
}
labelGroups.get(title.label)!.set(index, title.caption);
});

const originalToDisplayedMap = new Map<string, string>();
// Parse each group of editors with the same label.
labelGroups.forEach(labelGroup => {
// Filter to get groups that have duplicates.
if (labelGroup.size > 1) {
const paths: string[][] = [];
let maxPathLength = 0;
labelGroup.forEach((pathStr, index) => {
const steps = pathStr.split('/');
maxPathLength = Math.max(maxPathLength, steps.length);
paths[index] = (steps.slice(0, steps.length - 1));
// By default, show at maximum three levels from the end.
let defaultDisplayedPath = steps.slice(-4, -1).join('/');
if (steps.length > 4) {
defaultDisplayedPath = '.../' + defaultDisplayedPath;
}
originalToDisplayedMap.set(pathStr, defaultDisplayedPath);
});

let i = 0;
while (i < maxPathLength - 1) {
// Store indexes of all paths that have the identical element in each step.
const stepOccurrences = new Map<string, number[]>();
// Compare the current step of all paths
paths.forEach((path, index) => {
const step = path[i];
if (path.length > 0) {
if (i > path.length - 1) {
paths[index] = [];
} else if (!stepOccurrences.has(step)) {
stepOccurrences.set(step, new Array());
stepOccurrences.get(step)!.push(index);
} else {
stepOccurrences.get(step)!.push(index);
}
}
});

// Set the displayed path for each tab.
stepOccurrences.forEach((indexArr, displayedPath) => {
if (indexArr.length === 1) {
const originalPath = labelGroup.get(indexArr[0]);
if (originalPath) {
const originalElements = originalPath.split('/');
const displayedElements = displayedPath.split('/');
if (originalElements.slice(-2)[0] !== displayedElements.slice(-1)[0]) {
displayedPath += '/...';
}
if (originalElements[0] !== displayedElements[0]) {
displayedPath = '.../' + displayedPath;
}
originalToDisplayedMap.set(originalPath, displayedPath);
paths[indexArr[0]] = [];
}
}
});
i++;
}
}
});
return originalToDisplayedMap;
}

/**
Expand Down Expand Up @@ -550,7 +641,7 @@ export class SideTabBar extends ScrollableTabBar {
} else {
rd = { title, current, zIndex };
}
content[i] = renderer.renderTab(rd);
content[i] = renderer.renderTab(rd, true);
}
VirtualDOM.render(content, host);
}
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/browser/style/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,23 @@

.p-TabBar.theia-app-centers .p-TabBar-tabIcon,
.p-TabBar.theia-app-centers .p-TabBar-tabLabel,
.p-TabBar.theia-app-centers .p-TabBar-tabLabelDetails,
.p-TabBar.theia-app-centers .p-TabBar-tabCloseIcon {
display: inline-block;
}

.p-TabBar.theia-app-centers .p-TabBar-tabLabelDetails {
margin-left: 5px;
color: var(--theia-ui-font-color2);
flex: 1 1 auto;
overflow: hidden;
white-space: nowrap;
}

.p-TabBar.theia-app-centers .p-TabBar-tabLabelWrapper {
display: flex;
}

.p-TabBar-tab-secondary-label {
color: var(--theia-brand-color2);
cursor: pointer;
Expand Down

0 comments on commit 7f08365

Please sign in to comment.