Skip to content

Commit 69af7eb

Browse files
committed
Put close icons at the start of the tab label by default on macOS
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]>
1 parent 14908a2 commit 69af7eb

File tree

4 files changed

+61
-17
lines changed

4 files changed

+61
-17
lines changed

packages/core/i18n/nls.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,14 @@
453453
"tabDefaultSize": "Specifies the default size for tabs.",
454454
"tabMaximize": "Controls whether to maximize tabs on double click.",
455455
"tabMinimumSize": "Specifies the minimum size for tabs.",
456-
"tabShrinkToFit": "Shrink tabs to fit available space."
456+
"tabShrinkToFit": "Shrink tabs to fit available space.",
457+
"window": {
458+
"tabCloseIconPlacement": {
459+
"description": "Place the close icons on tab titles at the start or end of the tab. The default is the host OS convention.",
460+
"end": "Place the close icon at the end of the label. In left-to-right languages, this is the right side of the tab.",
461+
"start": "Place the close icon at the start of the label. In left-to-right languages, this is the left side of the tab."
462+
}
463+
}
457464
},
458465
"debug": {
459466
"addConfigurationPlaceholder": "Select workspace root to add configuration to",

packages/core/src/browser/core-preferences.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ export const corePreferenceSchema: PreferenceSchema = {
121121
scope: 'application',
122122
markdownDescription: nls.localizeByDefault('Separator used by {0}.', '`#window.title#`')
123123
},
124+
'window.tabCloseIconPlacement': {
125+
type: 'string',
126+
enum: ['end', 'start'],
127+
enumDescriptions: [
128+
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.'),
129+
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.'),
130+
],
131+
default: isOSX ? 'start' : 'end',
132+
scope: 'application',
133+
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.'),
134+
included: isOSX
135+
},
124136
'window.secondaryWindowPlacement': {
125137
type: 'string',
126138
enum: ['originalSize', 'halfWidth', 'fullSize'],
@@ -305,6 +317,7 @@ export interface CoreConfiguration {
305317
'window.menuBarVisibility': 'classic' | 'visible' | 'hidden' | 'compact';
306318
'window.title': string;
307319
'window.titleSeparator': string;
320+
'window.tabCloseIconPlacement': 'end' | 'start';
308321
'workbench.list.openMode': 'singleClick' | 'doubleClick';
309322
'workbench.commandPalette.history': number;
310323
'workbench.editor.highlightModifiedTabs': boolean;

packages/core/src/browser/shell/tab-bars.ts

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,13 @@ export class TabBarRenderer extends TabBar.Renderer {
118118
}
119119
}));
120120
}
121+
if (this.corePreferences) {
122+
this.toDispose.push(this.corePreferences.onPreferenceChanged(event => {
123+
if (event.preferenceName === 'window.tabCloseIconPlacement' && this._tabBar) {
124+
this._tabBar.update();
125+
}
126+
}));
127+
}
121128
}
122129

123130
dispose(): void {
@@ -159,11 +166,13 @@ export class TabBarRenderer extends TabBar.Renderer {
159166
* @returns {VirtualElement} The virtual element of the rendered tab.
160167
*/
161168
override renderTab(data: SideBarRenderData, isInSidePanel?: boolean, isPartOfHiddenTabBar?: boolean): VirtualElement {
169+
const tabCloseIconStart = this.corePreferences?.['window.tabCloseIconPlacement'] === 'start';
170+
162171
const title = data.title;
163172
const id = this.createTabId(title, isPartOfHiddenTabBar);
164173
const key = this.createTabKey(data);
165174
const style = this.createTabStyle(data);
166-
const className = this.createTabClass(data);
175+
const className = this.createTabClass(data) + (tabCloseIconStart ? ' closeIcon-start' : '');
167176
const dataset = this.createTabDataset(data);
168177
const closeIconTitle = data.title.className.includes(PINNED_CLASS)
169178
? nls.localizeByDefault('Unpin')
@@ -175,6 +184,22 @@ export class TabBarRenderer extends TabBar.Renderer {
175184
onmouseenter: this.handleMouseEnterEvent
176185
};
177186

187+
const tabLabel = h.div(
188+
{ className: 'theia-tab-icon-label' },
189+
this.renderIcon(data, isInSidePanel),
190+
this.renderLabel(data, isInSidePanel),
191+
this.renderTailDecorations(data, isInSidePanel),
192+
this.renderBadge(data, isInSidePanel),
193+
this.renderLock(data, isInSidePanel)
194+
);
195+
const tabCloseIcon = h.div({
196+
className: 'p-TabBar-tabCloseIcon action-label',
197+
title: closeIconTitle,
198+
onclick: this.handleCloseClickEvent
199+
});
200+
201+
const tabContents = tabCloseIconStart ? [tabCloseIcon, tabLabel] : [tabLabel, tabCloseIcon];
202+
178203
return h.li(
179204
{
180205
...hover,
@@ -186,19 +211,7 @@ export class TabBarRenderer extends TabBar.Renderer {
186211
e.preventDefault();
187212
}
188213
},
189-
h.div(
190-
{ className: 'theia-tab-icon-label' },
191-
this.renderIcon(data, isInSidePanel),
192-
this.renderLabel(data, isInSidePanel),
193-
this.renderTailDecorations(data, isInSidePanel),
194-
this.renderBadge(data, isInSidePanel),
195-
this.renderLock(data, isInSidePanel)
196-
),
197-
h.div({
198-
className: 'p-TabBar-tabCloseIcon action-label',
199-
title: closeIconTitle,
200-
onclick: this.handleCloseClickEvent
201-
})
214+
...tabContents
202215
);
203216
}
204217

packages/core/src/browser/style/tabs.css

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@
237237
-ms-user-select: none;
238238
}
239239

240+
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.closeIcon-start > .p-TabBar-tabCloseIcon,
241+
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned.closeIcon-start > .p-TabBar-tabCloseIcon {
242+
margin-left: inherit;
243+
margin-right: 4px;
244+
}
245+
240246
.p-TabBar.theia-app-centers.dynamic-tabs .p-TabBar-tab.p-mod-closable>.p-TabBar-tabCloseIcon,
241247
.p-TabBar.theia-app-centers.dynamic-tabs .p-TabBar-tab.theia-mod-pinned>.p-TabBar-tabCloseIcon {
242248
/* hide close icon for dynamic tabs strategy*/
@@ -254,11 +260,16 @@
254260
background-color: rgba(50%, 50%, 50%, 0.2);
255261
}
256262

257-
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable,
258-
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned {
263+
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable:not(.closeIcon-start),
264+
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned:not(.closeIcon-start) {
259265
padding-right: 4px;
260266
}
261267

268+
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.closeIcon-start,
269+
.p-TabBar.theia-app-centers .p-TabBar-tab.theia-mod-pinned.closeIcon-start {
270+
padding-left: 4px;
271+
}
272+
262273
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable:not(.theia-mod-dirty):hover>.p-TabBar-tabCloseIcon:before,
263274
.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,
264275
.p-TabBar.theia-app-centers .p-TabBar-tab.p-mod-closable.theia-mod-dirty>.p-TabBar-tabCloseIcon:hover:before {

0 commit comments

Comments
 (0)