Skip to content

Commit

Permalink
Put close icons at the start of the tab label by default on macOS
Browse files Browse the repository at this point in the history
Add a new window.tabCloseIconPlacement preference for whether to present
the Close (X) icon in tab titles on the left or the right of the tab.
Default to the left on macOS platform in conformity with the OS's native
tab controls.
Show the new preference in the Settings UI on macOS platform only.

Render the tab title with the icon on the left or right accordingly.

Fixes eclipse-theia/theia-ide#460

Signed-off-by: Christian W. Damus <[email protected]>
Signed-off-by: Christian W. Damus <[email protected]>
  • Loading branch information
cdamus committed Mar 3, 2025
1 parent 14908a2 commit 69af7eb
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 17 deletions.
9 changes: 8 additions & 1 deletion packages/core/i18n/nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,14 @@
"tabDefaultSize": "Specifies the default size for tabs.",
"tabMaximize": "Controls whether to maximize tabs on double click.",
"tabMinimumSize": "Specifies the minimum size for tabs.",
"tabShrinkToFit": "Shrink tabs to fit available space."
"tabShrinkToFit": "Shrink tabs to fit available space.",
"window": {
"tabCloseIconPlacement": {
"description": "Place the close icons on tab titles at the start or end of the tab. The default is the host OS convention.",
"end": "Place the close icon at the end of the label. In left-to-right languages, this is the right side of the tab.",
"start": "Place the close icon at the start of the label. In left-to-right languages, this is the left side of the tab."
}
}
},
"debug": {
"addConfigurationPlaceholder": "Select workspace root to add configuration to",
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/browser/core-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,18 @@ export const corePreferenceSchema: PreferenceSchema = {
scope: 'application',
markdownDescription: nls.localizeByDefault('Separator used by {0}.', '`#window.title#`')
},
'window.tabCloseIconPlacement': {
type: 'string',
enum: ['end', 'start'],
enumDescriptions: [
nls.localize('theia/core/window/tabCloseIconPlacement/end', 'Place the close icon at the end of the label. In left-to-right languages, this is the right side of the tab.'),
nls.localize('theia/core/window/tabCloseIconPlacement/start', 'Place the close icon at the start of the label. In left-to-right languages, this is the left side of the tab.'),
],
default: isOSX ? 'start' : 'end',
scope: 'application',
description: nls.localize('theia/core/window/tabCloseIconPlacement/description', 'Place the close icons on tab titles at the start or end of the tab. The default is the host OS convention.'),
included: isOSX
},
'window.secondaryWindowPlacement': {
type: 'string',
enum: ['originalSize', 'halfWidth', 'fullSize'],
Expand Down Expand Up @@ -305,6 +317,7 @@ export interface CoreConfiguration {
'window.menuBarVisibility': 'classic' | 'visible' | 'hidden' | 'compact';
'window.title': string;
'window.titleSeparator': string;
'window.tabCloseIconPlacement': 'end' | 'start';
'workbench.list.openMode': 'singleClick' | 'doubleClick';
'workbench.commandPalette.history': number;
'workbench.editor.highlightModifiedTabs': boolean;
Expand Down
41 changes: 27 additions & 14 deletions packages/core/src/browser/shell/tab-bars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ export class TabBarRenderer extends TabBar.Renderer {
}
}));
}
if (this.corePreferences) {
this.toDispose.push(this.corePreferences.onPreferenceChanged(event => {
if (event.preferenceName === 'window.tabCloseIconPlacement' && this._tabBar) {
this._tabBar.update();
}
}));
}
}

dispose(): void {
Expand Down Expand Up @@ -159,11 +166,13 @@ export class TabBarRenderer extends TabBar.Renderer {
* @returns {VirtualElement} The virtual element of the rendered tab.
*/
override renderTab(data: SideBarRenderData, isInSidePanel?: boolean, isPartOfHiddenTabBar?: boolean): VirtualElement {
const tabCloseIconStart = this.corePreferences?.['window.tabCloseIconPlacement'] === 'start';

const title = data.title;
const id = this.createTabId(title, isPartOfHiddenTabBar);
const key = this.createTabKey(data);
const style = this.createTabStyle(data);
const className = this.createTabClass(data);
const className = this.createTabClass(data) + (tabCloseIconStart ? ' closeIcon-start' : '');
const dataset = this.createTabDataset(data);
const closeIconTitle = data.title.className.includes(PINNED_CLASS)
? nls.localizeByDefault('Unpin')
Expand All @@ -175,6 +184,22 @@ export class TabBarRenderer extends TabBar.Renderer {
onmouseenter: this.handleMouseEnterEvent
};

const tabLabel = h.div(
{ className: 'theia-tab-icon-label' },
this.renderIcon(data, isInSidePanel),
this.renderLabel(data, isInSidePanel),
this.renderTailDecorations(data, isInSidePanel),
this.renderBadge(data, isInSidePanel),
this.renderLock(data, isInSidePanel)
);
const tabCloseIcon = h.div({
className: 'p-TabBar-tabCloseIcon action-label',
title: closeIconTitle,
onclick: this.handleCloseClickEvent
});

const tabContents = tabCloseIconStart ? [tabCloseIcon, tabLabel] : [tabLabel, tabCloseIcon];

return h.li(
{
...hover,
Expand All @@ -186,19 +211,7 @@ export class TabBarRenderer extends TabBar.Renderer {
e.preventDefault();
}
},
h.div(
{ className: 'theia-tab-icon-label' },
this.renderIcon(data, isInSidePanel),
this.renderLabel(data, isInSidePanel),
this.renderTailDecorations(data, isInSidePanel),
this.renderBadge(data, isInSidePanel),
this.renderLock(data, isInSidePanel)
),
h.div({
className: 'p-TabBar-tabCloseIcon action-label',
title: closeIconTitle,
onclick: this.handleCloseClickEvent
})
...tabContents
);
}

Expand Down
15 changes: 13 additions & 2 deletions packages/core/src/browser/style/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@
-ms-user-select: none;
}

.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.closeIcon-start > .p-TabBar-tabCloseIcon,
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned.closeIcon-start > .p-TabBar-tabCloseIcon {
margin-left: inherit;
margin-right: 4px;
}

.p-TabBar.theia-app-centers.dynamic-tabs .p-TabBar-tab.p-mod-closable>.p-TabBar-tabCloseIcon,
.p-TabBar.theia-app-centers.dynamic-tabs .p-TabBar-tab.theia-mod-pinned>.p-TabBar-tabCloseIcon {
/* hide close icon for dynamic tabs strategy*/
Expand All @@ -254,11 +260,16 @@
background-color: rgba(50%, 50%, 50%, 0.2);
}

.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable,
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned {
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable:not(.closeIcon-start),
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned:not(.closeIcon-start) {
padding-right: 4px;
}

.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.closeIcon-start,
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned.closeIcon-start {
padding-left: 4px;
}

.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable:not(.theia-mod-dirty):hover>.p-TabBar-tabCloseIcon:before,
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable:not(.theia-mod-dirty).p-TabBar-tab.p-mod-current>.p-TabBar-tabCloseIcon:before,
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty>.p-TabBar-tabCloseIcon:hover:before {
Expand Down

0 comments on commit 69af7eb

Please sign in to comment.