tabsTitleControl.ts 41.6 KB
Newer Older
B
wip  
Benjamin Pasero 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import 'vs/css!./media/tabstitle';
B
Benjamin Pasero 已提交
9
import nls = require('vs/nls');
B
Benjamin Pasero 已提交
10
import { TPromise } from 'vs/base/common/winjs.base';
B
Benjamin Pasero 已提交
11 12
import errors = require('vs/base/common/errors');
import DOM = require('vs/base/browser/dom');
J
Johannes Rieken 已提交
13
import { isMacintosh } from 'vs/base/common/platform';
14
import { shorten } from 'vs/base/common/labels';
B
Benjamin Pasero 已提交
15
import { ActionRunner, IAction } from 'vs/base/common/actions';
16
import { Position, IEditorInput, Verbosity, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
17
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
J
Johannes Rieken 已提交
18
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
19
import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch';
J
Johannes Rieken 已提交
20
import { KeyCode } from 'vs/base/common/keyCodes';
B
Benjamin Pasero 已提交
21
import { ResourceLabel } from 'vs/workbench/browser/labels';
J
Johannes Rieken 已提交
22
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
23
import { IWorkbenchEditorService, DelegatingWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Johannes Rieken 已提交
24 25 26 27 28 29 30
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService } from 'vs/platform/actions/common/actions';
31
import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl';
J
Johannes Rieken 已提交
32
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
33
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
J
Johannes Rieken 已提交
34 35
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
36
import { getOrSet } from 'vs/base/common/map';
37
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
B
Benjamin Pasero 已提交
38
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
39 40
import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_BACKGROUND, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
41
import { Dimension } from 'vs/base/browser/builder';
42
import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
43
import { ResourcesDropHandler, fillResourceDataTransfers, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
44
import { Color } from 'vs/base/common/color';
45
import { INotificationService } from 'vs/platform/notification/common/notification';
B
Benjamin Pasero 已提交
46 47 48 49

interface IEditorInputLabel {
	name: string;
	description?: string;
50
	title?: string;
B
Benjamin Pasero 已提交
51
}
B
wip  
Benjamin Pasero 已提交
52

53 54
type AugmentedLabel = IEditorInputLabel & { editor: IEditorInput };

B
wip  
Benjamin Pasero 已提交
55
export class TabsTitleControl extends TitleControl {
B
Benjamin Pasero 已提交
56 57
	private titleContainer: HTMLElement;
	private tabsContainer: HTMLElement;
58
	private editorToolbarContainer: HTMLElement;
B
Benjamin Pasero 已提交
59
	private activeTab: HTMLElement;
B
Benjamin Pasero 已提交
60
	private editorLabels: ResourceLabel[];
61
	private scrollbar: ScrollableElement;
62
	private tabDisposeables: IDisposable[];
63
	private blockRevealActiveTab: boolean;
64
	private dimension: Dimension;
65
	private layoutScheduled: IDisposable;
66
	private transfer = LocalSelectionTransfer.getInstance<DraggedEditorIdentifier>();
B
wip  
Benjamin Pasero 已提交
67 68 69 70 71 72

	constructor(
		@IContextMenuService contextMenuService: IContextMenuService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IWorkbenchEditorService editorService: IWorkbenchEditorService,
		@IEditorGroupService editorGroupService: IEditorGroupService,
73
		@IContextKeyService contextKeyService: IContextKeyService,
74
		@IKeybindingService keybindingService: IKeybindingService,
B
wip  
Benjamin Pasero 已提交
75
		@ITelemetryService telemetryService: ITelemetryService,
76
		@INotificationService notificationService: INotificationService,
77
		@IMenuService menuService: IMenuService,
J
Joao Moreno 已提交
78
		@IQuickOpenService quickOpenService: IQuickOpenService,
79
		@IThemeService themeService: IThemeService
B
wip  
Benjamin Pasero 已提交
80
	) {
81
		super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService);
B
wip  
Benjamin Pasero 已提交
82

B
Benjamin Pasero 已提交
83
		this.tabDisposeables = [];
84
		this.editorLabels = [];
B
wip  
Benjamin Pasero 已提交
85 86
	}

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
	protected initActions(services: IInstantiationService): void {
		super.initActions(this.createScopedInstantiationService());
	}

	private createScopedInstantiationService(): IInstantiationService {
		const stacks = this.editorGroupService.getStacksModel();
		const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService);

		// We create a scoped instantiation service to override the behaviour when closing an inactive editor
		// Specifically we want to move focus back to the editor when an inactive editor is closed from anywhere
		// in the tabs title control (e.g. mouse middle click, context menu on tab). This is only needed for
		// the inactive editors because closing the active one will always cause a tab switch that sets focus.
		// We also want to block the tabs container to reveal the currently active tab because that makes it very
		// hard to close multiple inactive tabs next to each other.
		delegatingEditorService.setEditorCloseHandler((position, editor) => {
			const group = stacks.groupAt(position);
			if (group && stacks.isActive(group) && !group.isActive(editor)) {
				this.editorGroupService.focusGroup(group);
			}

			this.blockRevealActiveTab = true;

			return TPromise.as(void 0);
		});

		return this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService]));
	}

115
	public create(parent: HTMLElement): void {
116
		super.create(parent);
117

118
		this.titleContainer = parent;
B
wip  
Benjamin Pasero 已提交
119

120
		// Tabs Container
B
Benjamin Pasero 已提交
121
		this.tabsContainer = document.createElement('div');
122
		this.tabsContainer.setAttribute('role', 'tablist');
B
Benjamin Pasero 已提交
123
		DOM.addClass(this.tabsContainer, 'tabs-container');
124

125
		// Forward scrolling inside the container to our custom scrollbar
126
		this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.SCROLL, e => {
B
Benjamin Pasero 已提交
127
			if (DOM.hasClass(this.tabsContainer, 'scroll')) {
128
				this.scrollbar.setScrollPosition({
B
Benjamin Pasero 已提交
129 130 131 132 133
					scrollLeft: this.tabsContainer.scrollLeft // during DND the  container gets scrolled so we need to update the custom scrollbar
				});
			}
		}));

134
		// New file when double clicking on tabs container (but not tabs)
135
		this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DBLCLICK, e => {
136 137 138 139
			const target = e.target;
			if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
				DOM.EventHelper.stop(e);

140
				const group = this.context;
B
Benjamin Pasero 已提交
141
				if (group) {
142
					this.editorService.openEditor({ options: { pinned: true, index: group.count /* always at the end */ } } as IUntitledResourceInput).done(null, errors.onUnexpectedError); // untitled are always pinned
B
Benjamin Pasero 已提交
143
				}
144 145 146
			}
		}));

B
Benjamin Pasero 已提交
147 148 149 150 151 152
		this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
			if (e.button === 1) {
				e.preventDefault(); // required to prevent auto-scrolling (https://github.com/Microsoft/vscode/issues/16690)
			}
		}));

153 154 155 156 157 158 159 160 161 162 163 164 165 166
		// Custom Scrollbar
		this.scrollbar = new ScrollableElement(this.tabsContainer, {
			horizontal: ScrollbarVisibility.Auto,
			vertical: ScrollbarVisibility.Hidden,
			scrollYToX: true,
			useShadows: false,
			horizontalScrollbarSize: 3
		});

		this.scrollbar.onScroll(e => {
			this.tabsContainer.scrollLeft = e.scrollLeft;
		});

		this.titleContainer.appendChild(this.scrollbar.getDomNode());
B
Benjamin Pasero 已提交
167

B
Benjamin Pasero 已提交
168
		// Drag over
169
		this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_OVER, (e: DragEvent) => {
170
			const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
B
Benjamin Pasero 已提交
171 172 173

			// update the dropEffect, otherwise it would look like a "move" operation. but only if we are
			// not dragging a tab actually because there we support both moving as well as copying
174
			if (!draggedEditor) {
B
Benjamin Pasero 已提交
175 176
				e.dataTransfer.dropEffect = 'copy';
			}
177

B
Benjamin Pasero 已提交
178 179
			DOM.addClass(this.tabsContainer, 'scroll'); // enable support to scroll while dragging

B
Benjamin Pasero 已提交
180 181
			const target = e.target;
			if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
182 183 184 185 186 187 188 189 190 191 192

				// Find out if the currently dragged editor is the last tab of this group and in that
				// case we do not want to show any drop feedback because the drop would be a no-op
				let draggedEditorIsLastTab = false;
				if (draggedEditor && this.context === draggedEditor.group && this.context.indexOf(draggedEditor.editor) === this.context.count - 1) {
					draggedEditorIsLastTab = true;
				}

				if (!draggedEditorIsLastTab) {
					this.updateDropFeedback(this.tabsContainer, true);
				}
B
Benjamin Pasero 已提交
193 194 195 196
			}
		}));

		// Drag leave
197
		this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
B
Benjamin Pasero 已提交
198
			this.updateDropFeedback(this.tabsContainer, false);
B
Benjamin Pasero 已提交
199
			DOM.removeClass(this.tabsContainer, 'scroll');
B
Benjamin Pasero 已提交
200 201 202
		}));

		// Drag end
203
		this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DRAG_END, (e: DragEvent) => {
B
Benjamin Pasero 已提交
204
			this.updateDropFeedback(this.tabsContainer, false);
B
Benjamin Pasero 已提交
205
			DOM.removeClass(this.tabsContainer, 'scroll');
B
Benjamin Pasero 已提交
206 207
		}));

B
Benjamin Pasero 已提交
208
		// Drop onto tabs container
209
		this.toUnbind.push(DOM.addDisposableListener(this.tabsContainer, DOM.EventType.DROP, (e: DragEvent) => {
B
Benjamin Pasero 已提交
210
			this.updateDropFeedback(this.tabsContainer, false);
B
Benjamin Pasero 已提交
211
			DOM.removeClass(this.tabsContainer, 'scroll');
B
Benjamin Pasero 已提交
212

B
Benjamin Pasero 已提交
213
			const target = e.target;
B
Benjamin Pasero 已提交
214
			if (target instanceof HTMLElement && target.className.indexOf('tabs-container') === 0) {
B
Benjamin Pasero 已提交
215 216
				const group = this.context;
				if (group) {
217 218 219
					const targetPosition = this.stacks.positionOfGroup(group);
					const targetIndex = group.count;

B
Benjamin Pasero 已提交
220
					this.onDrop(e, group, targetPosition, targetIndex);
B
Benjamin Pasero 已提交
221 222 223 224
				}
			}
		}));

225 226 227 228
		// Editor Toolbar Container
		this.editorToolbarContainer = document.createElement('div');
		DOM.addClass(this.editorToolbarContainer, 'editor-actions');
		this.titleContainer.appendChild(this.editorToolbarContainer);
229 230

		// Editor Actions Toolbar
231
		this.createEditorActionsToolBar(this.editorToolbarContainer);
B
wip  
Benjamin Pasero 已提交
232 233
	}

B
Benjamin Pasero 已提交
234 235
	private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void {
		const isTab = (typeof index === 'number');
236
		const isActiveTab = isTab && this.context && this.context.isActive(this.context.getEditor(index));
B
Benjamin Pasero 已提交
237 238

		// Background
B
Benjamin Pasero 已提交
239
		const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : null;
B
Benjamin Pasero 已提交
240 241 242
		element.style.backgroundColor = isDND ? this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) : noDNDBackgroundColor;

		// Outline
243 244
		const activeContrastBorderColor = this.getColor(activeContrastBorder);
		if (activeContrastBorderColor && isDND) {
B
Benjamin Pasero 已提交
245 246
			element.style.outlineWidth = '2px';
			element.style.outlineStyle = 'dashed';
247
			element.style.outlineColor = activeContrastBorderColor;
B
Benjamin Pasero 已提交
248
			element.style.outlineOffset = isTab ? '-5px' : '-3px';
B
Benjamin Pasero 已提交
249 250 251
		} else {
			element.style.outlineWidth = null;
			element.style.outlineStyle = null;
252
			element.style.outlineColor = activeContrastBorderColor;
B
Benjamin Pasero 已提交
253
			element.style.outlineOffset = null;
B
Benjamin Pasero 已提交
254 255 256
		}
	}

B
Benjamin Pasero 已提交
257 258 259 260
	public allowDragging(element: HTMLElement): boolean {
		return (element.className === 'tabs-container');
	}

261 262 263 264 265 266 267 268
	protected doUpdate(): void {
		if (!this.context) {
			return;
		}

		const group = this.context;

		// Tabs container activity state
269 270
		const isGroupActive = this.stacks.isActive(group);
		if (isGroupActive) {
271
			DOM.addClass(this.titleContainer, 'active');
272
			DOM.removeClass(this.titleContainer, 'inactive');
273
		} else {
274
			DOM.addClass(this.titleContainer, 'inactive');
275 276 277
			DOM.removeClass(this.titleContainer, 'active');
		}

278 279
		// Compute labels and protect against duplicates
		const editorsOfGroup = this.context.getEditors();
280
		const labels = this.getTabLabels(editorsOfGroup);
281

282
		// Tab label and styles
283
		editorsOfGroup.forEach((editor, index) => {
284
			const tabContainer = this.tabsContainer.children[index] as HTMLElement;
B
Benjamin Pasero 已提交
285 286 287 288
			if (!tabContainer) {
				return; // could be a race condition between updating tabs and creating tabs
			}

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
			const isPinned = group.isPinned(index);
			const isTabActive = group.isActive(editor);
			const isDirty = editor.isDirty();

			const label = labels[index];
			const name = label.name;
			const description = label.description || '';
			const title = label.title || '';

			// Container
			tabContainer.setAttribute('aria-label', `${name}, tab`);
			tabContainer.title = title;
			tabContainer.style.borderLeftColor = (index !== 0) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
			tabContainer.style.borderRightColor = (index === editorsOfGroup.length - 1) ? (this.getColor(TAB_BORDER) || this.getColor(contrastBorder)) : null;
			tabContainer.style.outlineColor = this.getColor(activeContrastBorder);

			const tabOptions = this.editorGroupService.getTabOptions();
306

307
			['off', 'left', 'right'].forEach(option => {
308 309 310
				const domAction = tabOptions.tabCloseButton === option ? DOM.addClass : DOM.removeClass;
				domAction(tabContainer, `close-button-${option}`);
			});
311

312 313 314 315 316
			['fit', 'shrink'].forEach(option => {
				const domAction = tabOptions.tabSizing === option ? DOM.addClass : DOM.removeClass;
				domAction(tabContainer, `sizing-${option}`);
			});

B
Benjamin Pasero 已提交
317 318 319 320 321 322
			if (tabOptions.showIcons && !!tabOptions.iconTheme) {
				DOM.addClass(tabContainer, 'has-icon-theme');
			} else {
				DOM.removeClass(tabContainer, 'has-icon-theme');
			}

323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
			// Label
			const tabLabel = this.editorLabels[index];
			tabLabel.setLabel({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { extraClasses: ['tab-label'], italic: !isPinned });

			// Active state
			if (isTabActive) {
				DOM.addClass(tabContainer, 'active');
				tabContainer.setAttribute('aria-selected', 'true');
				tabContainer.style.backgroundColor = this.getColor(TAB_ACTIVE_BACKGROUND);
				tabLabel.element.style.color = this.getColor(isGroupActive ? TAB_ACTIVE_FOREGROUND : TAB_UNFOCUSED_ACTIVE_FOREGROUND);

				// Use boxShadow for the active tab border because if we also have a editor group header
				// color, the two colors would collide and the tab border never shows up.
				// see https://github.com/Microsoft/vscode/issues/33111
				const activeTabBorderColor = this.getColor(isGroupActive ? TAB_ACTIVE_BORDER : TAB_UNFOCUSED_ACTIVE_BORDER);
				if (activeTabBorderColor) {
					tabContainer.style.boxShadow = `${activeTabBorderColor} 0 -1px inset`;
340
				} else {
B
Benjamin Pasero 已提交
341
					tabContainer.style.boxShadow = null;
342 343
				}

344 345 346 347 348 349 350 351 352 353 354 355 356 357
				this.activeTab = tabContainer;
			} else {
				DOM.removeClass(tabContainer, 'active');
				tabContainer.setAttribute('aria-selected', 'false');
				tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND);
				tabLabel.element.style.color = this.getColor(isGroupActive ? TAB_INACTIVE_FOREGROUND : TAB_UNFOCUSED_INACTIVE_FOREGROUND);
				tabContainer.style.boxShadow = null;
			}

			// Dirty State
			if (isDirty) {
				DOM.addClass(tabContainer, 'dirty');
			} else {
				DOM.removeClass(tabContainer, 'dirty');
358 359
			}
		});
360

361
		// Update Editor Actions Toolbar
362
		this.updateEditorActionsToolbar();
363

B
Benjamin Pasero 已提交
364
		// Ensure the active tab is always revealed
365
		this.layout(this.dimension);
366 367
	}

368
	private getTabLabels(editors: IEditorInput[]): IEditorInputLabel[] {
369 370
		const labelFormat = this.editorGroupService.getTabOptions().labelFormat;
		const { verbosity, shortenDuplicates } = this.getLabelConfigFlags(labelFormat);
B
Benjamin Pasero 已提交
371 372

		// Build labels and descriptions for each editor
373 374 375 376 377 378
		const labels = editors.map(editor => ({
			editor,
			name: editor.getName(),
			description: editor.getDescription(verbosity),
			title: editor.getTitle(Verbosity.LONG)
		}));
379

380
		// Shorten labels as needed
381
		if (shortenDuplicates) {
382 383 384 385 386 387 388 389 390 391 392
			this.shortenTabLabels(labels);
		}

		return labels;
	}

	private shortenTabLabels(labels: AugmentedLabel[]): void {

		// Gather duplicate titles, while filtering out invalid descriptions
		const mapTitleToDuplicates = new Map<string, AugmentedLabel[]>();
		for (const label of labels) {
393
			if (typeof label.description === 'string') {
394 395 396
				getOrSet(mapTitleToDuplicates, label.name, []).push(label);
			} else {
				label.description = '';
397
			}
398
		}
B
Benjamin Pasero 已提交
399

400 401
		// Identify duplicate titles and shorten descriptions
		mapTitleToDuplicates.forEach(duplicateTitles => {
B
Benjamin Pasero 已提交
402

403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
			// Remove description if the title isn't duplicated
			if (duplicateTitles.length === 1) {
				duplicateTitles[0].description = '';

				return;
			}

			// Identify duplicate descriptions
			const mapDescriptionToDuplicates = new Map<string, AugmentedLabel[]>();
			for (const label of duplicateTitles) {
				getOrSet(mapDescriptionToDuplicates, label.description, []).push(label);
			}

			// For editors with duplicate descriptions, check whether any long descriptions differ
			let useLongDescriptions = false;
			mapDescriptionToDuplicates.forEach((duplicateDescriptions, name) => {
				if (!useLongDescriptions && duplicateDescriptions.length > 1) {
					const [first, ...rest] = duplicateDescriptions.map(({ editor }) => editor.getDescription(Verbosity.LONG));
					useLongDescriptions = rest.some(description => description !== first);
422
				}
423
			});
B
Benjamin Pasero 已提交
424

425 426 427 428 429 430
			// If so, replace all descriptions with long descriptions
			if (useLongDescriptions) {
				mapDescriptionToDuplicates.clear();
				duplicateTitles.forEach(label => {
					label.description = label.editor.getDescription(Verbosity.LONG);
					getOrSet(mapDescriptionToDuplicates, label.description, []).push(label);
B
Benjamin Pasero 已提交
431
				});
432 433 434 435 436
			}

			// Obtain final set of descriptions
			const descriptions: string[] = [];
			mapDescriptionToDuplicates.forEach((_, description) => descriptions.push(description));
B
Benjamin Pasero 已提交
437

438 439 440 441
			// Remove description if all descriptions are identical
			if (descriptions.length === 1) {
				for (const label of mapDescriptionToDuplicates.get(descriptions[0])) {
					label.description = '';
B
Benjamin Pasero 已提交
442
				}
443

444 445
				return;
			}
446

447 448 449 450 451
			// Shorten descriptions
			const shortenedDescriptions = shorten(descriptions);
			descriptions.forEach((description, i) => {
				for (const label of mapDescriptionToDuplicates.get(description)) {
					label.description = shortenedDescriptions[i];
452 453
				}
			});
454
		});
B
Benjamin Pasero 已提交
455 456
	}

457
	private getLabelConfigFlags(value: string) {
458 459 460 461 462 463 464 465 466 467 468 469
		switch (value) {
			case 'short':
				return { verbosity: Verbosity.SHORT, shortenDuplicates: false };
			case 'medium':
				return { verbosity: Verbosity.MEDIUM, shortenDuplicates: false };
			case 'long':
				return { verbosity: Verbosity.LONG, shortenDuplicates: false };
			default:
				return { verbosity: Verbosity.MEDIUM, shortenDuplicates: true };
		}
	}

470
	protected doRefresh(): void {
B
wip  
Benjamin Pasero 已提交
471
		const group = this.context;
472
		const editor = group && group.activeEditor;
B
wip  
Benjamin Pasero 已提交
473
		if (!editor) {
474 475
			this.clearTabs();

476
			this.clearEditorActionsToolbar();
B
wip  
Benjamin Pasero 已提交
477 478 479 480

			return; // return early if we are being closed
		}

B
Benjamin Pasero 已提交
481 482
		// Handle Tabs
		this.handleTabs(group.count);
483
		DOM.removeClass(this.titleContainer, 'empty');
484

485
		// Update Tabs
486
		this.doUpdate();
B
wip  
Benjamin Pasero 已提交
487 488
	}

489
	private clearTabs(): void {
B
Benjamin Pasero 已提交
490
		DOM.clearNode(this.tabsContainer);
491 492

		this.tabDisposeables = dispose(this.tabDisposeables);
493
		this.editorLabels = [];
B
Benjamin Pasero 已提交
494

495
		DOM.addClass(this.titleContainer, 'empty');
496 497
	}

B
Benjamin Pasero 已提交
498 499 500
	private handleTabs(tabsNeeded: number): void {
		const tabs = this.tabsContainer.children;
		const tabsCount = tabs.length;
B
Benjamin Pasero 已提交
501

B
Benjamin Pasero 已提交
502 503 504 505
		// Nothing to do if count did not change
		if (tabsCount === tabsNeeded) {
			return;
		}
B
Benjamin Pasero 已提交
506

B
Benjamin Pasero 已提交
507 508 509 510 511 512
		// We need more tabs: create new ones
		if (tabsCount < tabsNeeded) {
			for (let i = tabsCount; i < tabsNeeded; i++) {
				this.tabsContainer.appendChild(this.createTab(i));
			}
		}
513

B
Benjamin Pasero 已提交
514 515 516 517
		// We need less tabs: delete the ones we do not need
		else {
			for (let i = 0; i < tabsCount - tabsNeeded; i++) {
				(this.tabsContainer.lastChild as HTMLElement).remove();
518
				this.editorLabels.pop();
B
Benjamin Pasero 已提交
519
				this.tabDisposeables.pop().dispose();
520
			}
B
Benjamin Pasero 已提交
521 522
		}
	}
523

B
Benjamin Pasero 已提交
524
	private createTab(index: number): HTMLElement {
525

B
Benjamin Pasero 已提交
526 527 528 529 530
		// Tab Container
		const tabContainer = document.createElement('div');
		tabContainer.draggable = true;
		tabContainer.tabIndex = 0;
		tabContainer.setAttribute('role', 'presentation'); // cannot use role "tab" here due to https://github.com/Microsoft/vscode/issues/8659
531
		DOM.addClass(tabContainer, 'tab');
B
Benjamin Pasero 已提交
532

533
		// Gesture Support
534
		Gesture.addTarget(tabContainer);
535

B
Benjamin Pasero 已提交
536
		// Tab Editor Label
B
Benjamin Pasero 已提交
537
		const editorLabel = this.instantiationService.createInstance(ResourceLabel, tabContainer, void 0);
B
Benjamin Pasero 已提交
538
		this.editorLabels.push(editorLabel);
B
Benjamin Pasero 已提交
539

B
Benjamin Pasero 已提交
540 541 542 543
		// Tab Close
		const tabCloseContainer = document.createElement('div');
		DOM.addClass(tabCloseContainer, 'tab-close');
		tabContainer.appendChild(tabCloseContainer);
B
Benjamin Pasero 已提交
544

B
Benjamin Pasero 已提交
545 546 547 548
		const actionRunner = new TabActionRunner(() => this.context, index);
		this.tabDisposeables.push(actionRunner);

		const bar = new ActionBar(tabCloseContainer, { ariaLabel: nls.localize('araLabelTabActions', "Tab actions"), actionRunner });
549
		bar.push(this.closeOneEditorAction, { icon: true, label: false, keybinding: this.getKeybindingLabel(this.closeOneEditorAction) });
550

B
Benjamin Pasero 已提交
551
		// Eventing
552 553
		const disposable = this.hookTabListeners(tabContainer, index);

554
		this.tabDisposeables.push(combinedDisposable([disposable, bar, editorLabel]));
B
Benjamin Pasero 已提交
555 556

		return tabContainer;
557 558
	}

559 560
	public layout(dimension: Dimension): void {
		if (!this.activeTab || !dimension) {
561 562 563
			return;
		}

564 565
		this.dimension = dimension;

566 567 568 569 570 571 572 573 574 575 576 577
		// The layout of tabs can be an expensive operation because we access DOM properties
		// that can result in the browser doing a full page layout to validate them. To buffer
		// this a little bit we try at least to schedule this work on the next animation frame.
		if (!this.layoutScheduled) {
			this.layoutScheduled = scheduleAtNextAnimationFrame(() => {
				this.doLayout(this.dimension);
				this.layoutScheduled = void 0;
			});
		}
	}

	private doLayout(dimension: Dimension): void {
B
Benjamin Pasero 已提交
578
		const visibleContainerWidth = this.tabsContainer.offsetWidth;
579 580
		const totalContainerWidth = this.tabsContainer.scrollWidth;

581 582 583 584 585 586 587 588
		let activeTabPosX: number;
		let activeTabWidth: number;

		if (!this.blockRevealActiveTab) {
			activeTabPosX = this.activeTab.offsetLeft;
			activeTabWidth = this.activeTab.offsetWidth;
		}

589
		// Update scrollbar
590
		this.scrollbar.setScrollDimensions({
591 592 593 594
			width: visibleContainerWidth,
			scrollWidth: totalContainerWidth
		});

595 596 597 598 599 600 601
		// Return now if we are blocked to reveal the active tab and clear flag
		if (this.blockRevealActiveTab) {
			this.blockRevealActiveTab = false;
			return;
		}

		// Reveal the active one
602
		const containerScrollPosX = this.scrollbar.getScrollPosition().scrollLeft;
603
		const activeTabFits = activeTabWidth <= visibleContainerWidth;
B
Benjamin Pasero 已提交
604 605

		// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
606 607
		// Note: only try to do this if we actually have enough width to give to show the tab fully!
		if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) {
608
			this.scrollbar.setScrollPosition({
609 610
				scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */)
			});
B
Benjamin Pasero 已提交
611 612
		}

613 614
		// Tab is overlflowng to the left or does not fit: Scroll it into view to the left
		else if (containerScrollPosX > activeTabPosX || !activeTabFits) {
615
			this.scrollbar.setScrollPosition({
616
				scrollLeft: activeTabPosX
617
			});
B
Benjamin Pasero 已提交
618
		}
B
Benjamin Pasero 已提交
619 620
	}

621
	private hookTabListeners(tab: HTMLElement, index: number): IDisposable {
B
Benjamin Pasero 已提交
622
		const disposables: IDisposable[] = [];
B
Benjamin Pasero 已提交
623

624
		const handleClickOrTouch = (e: MouseEvent | GestureEvent): void => {
625 626
			tab.blur();

627
			if (e instanceof MouseEvent && e.button !== 0) {
628
				if (e.button === 1) {
629
					e.preventDefault(); // required to prevent auto-scrolling (https://github.com/Microsoft/vscode/issues/16690)
630 631 632
				}

				return void 0; // only for left mouse click
633 634
			}

635
			const { editor, position } = this.getGroupPositionAndEditor(index);
636
			if (!this.isTabActionBar(((e as GestureEvent).initialTarget || e.target || e.srcElement) as HTMLElement)) {
637 638
				setTimeout(() => this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError)); // timeout to keep focus in editor after mouse up
			}
639 640

			return void 0;
B
Benjamin Pasero 已提交
641
		};
642

B
Benjamin Pasero 已提交
643
		const showContextMenu = (e: Event) => {
644 645
			DOM.EventHelper.stop(e);

646
			const { group, editor } = this.getGroupPositionAndEditor(index);
647 648

			this.onContextMenu({ group, editor }, e, tab);
B
Benjamin Pasero 已提交
649
		};
650 651 652 653 654 655

		// Open on Click
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => handleClickOrTouch(e)));

		// Open on Touch
		disposables.push(DOM.addDisposableListener(tab, TouchEventType.Tap, (e: GestureEvent) => handleClickOrTouch(e)));
656

657 658
		// Touch Scroll Support
		disposables.push(DOM.addDisposableListener(tab, TouchEventType.Change, (e: GestureEvent) => {
659
			this.scrollbar.setScrollPosition({ scrollLeft: this.scrollbar.getScrollPosition().scrollLeft - e.translationX });
660 661
		}));

662
		// Close on mouse middle click
663
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.MOUSE_UP, (e: MouseEvent) => {
664
			DOM.EventHelper.stop(e);
665
			tab.blur();
666

667
			if (e.button === 1 /* Middle Button*/ && !this.isTabActionBar((e.target || e.srcElement) as HTMLElement)) {
668
				this.closeOneEditorAction.run({ groupId: this.context.id, editorIndex: index }).done(null, errors.onUnexpectedError);
B
Benjamin Pasero 已提交
669
			}
B
Benjamin Pasero 已提交
670
		}));
B
Benjamin Pasero 已提交
671

672
		// Context menu on Shift+F10
673
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
674 675
			const event = new StandardKeyboardEvent(e);
			if (event.shiftKey && event.keyCode === KeyCode.F10) {
B
Benjamin Pasero 已提交
676
				showContextMenu(e);
677 678 679
			}
		}));

680 681 682 683 684
		// Context menu on touch context menu gesture
		disposables.push(DOM.addDisposableListener(tab, TouchEventType.Contextmenu, (e: GestureEvent) => {
			showContextMenu(e);
		}));

B
Benjamin Pasero 已提交
685
		// Keyboard accessibility
686
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.KEY_UP, (e: KeyboardEvent) => {
B
Benjamin Pasero 已提交
687
			const event = new StandardKeyboardEvent(e);
B
Benjamin Pasero 已提交
688
			let handled = false;
B
Benjamin Pasero 已提交
689

690
			const { group, position, editor } = this.getGroupPositionAndEditor(index);
B
Benjamin Pasero 已提交
691

B
Benjamin Pasero 已提交
692
			// Run action on Enter/Space
A
Alexandru Dima 已提交
693
			if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
B
Benjamin Pasero 已提交
694
				handled = true;
B
Benjamin Pasero 已提交
695
				this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError);
B
Benjamin Pasero 已提交
696 697
			}

B
Benjamin Pasero 已提交
698
			// Navigate in editors
A
Alexandru Dima 已提交
699
			else if ([KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.Home, KeyCode.End].some(kb => event.equals(kb))) {
B
Benjamin Pasero 已提交
700
				let targetIndex: number;
A
Alexandru Dima 已提交
701
				if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.UpArrow)) {
B
Benjamin Pasero 已提交
702
					targetIndex = index - 1;
A
Alexandru Dima 已提交
703
				} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.DownArrow)) {
B
Benjamin Pasero 已提交
704
					targetIndex = index + 1;
A
Alexandru Dima 已提交
705
				} else if (event.equals(KeyCode.Home)) {
B
Benjamin Pasero 已提交
706 707 708 709 710
					targetIndex = 0;
				} else {
					targetIndex = group.count - 1;
				}

B
Benjamin Pasero 已提交
711 712 713
				const target = group.getEditor(targetIndex);
				if (target) {
					handled = true;
714
					this.editorService.openEditor(target, { preserveFocus: true }, position).done(null, errors.onUnexpectedError);
B
Benjamin Pasero 已提交
715 716 717
					(<HTMLElement>this.tabsContainer.childNodes[targetIndex]).focus();
				}
			}
B
Benjamin Pasero 已提交
718

B
Benjamin Pasero 已提交
719
			if (handled) {
720
				DOM.EventHelper.stop(e, true);
B
Benjamin Pasero 已提交
721
			}
722 723

			// moving in the tabs container can have an impact on scrolling position, so we need to update the custom scrollbar
724
			this.scrollbar.setScrollPosition({
725 726
				scrollLeft: this.tabsContainer.scrollLeft
			});
B
Benjamin Pasero 已提交
727 728
		}));

B
Benjamin Pasero 已提交
729
		// Pin on double click
730
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DBLCLICK, (e: MouseEvent) => {
B
Benjamin Pasero 已提交
731 732
			DOM.EventHelper.stop(e);

733
			const { group, editor } = this.getGroupPositionAndEditor(index);
B
Benjamin Pasero 已提交
734

735
			this.editorGroupService.pinEditor(group, editor);
B
Benjamin Pasero 已提交
736
		}));
B
Benjamin Pasero 已提交
737

B
Benjamin Pasero 已提交
738
		// Context menu
739
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.CONTEXT_MENU, (e: Event) => {
740
			DOM.EventHelper.stop(e, true);
741
			const { group, editor } = this.getGroupPositionAndEditor(index);
B
Benjamin Pasero 已提交
742 743

			this.onContextMenu({ group, editor }, e, tab);
744
		}, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */));
B
Benjamin Pasero 已提交
745 746

		// Drag start
747
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_START, (e: DragEvent) => {
748
			const { group, editor } = this.getGroupPositionAndEditor(index);
B
Benjamin Pasero 已提交
749

750 751
			this.transfer.setData([new DraggedEditorIdentifier({ editor, group })], DraggedEditorIdentifier.prototype);

752
			e.dataTransfer.effectAllowed = 'copyMove';
B
Benjamin Pasero 已提交
753

754
			// Apply some datatransfer types to allow for dragging the element outside of the application
755 756
			const resource = toResource(editor, { supportSideBySide: true });
			if (resource) {
757
				this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e);
B
Benjamin Pasero 已提交
758
			}
759 760 761 762

			// Fixes https://github.com/Microsoft/vscode/issues/18733
			DOM.addClass(tab, 'dragged');
			scheduleAtNextAnimationFrame(() => DOM.removeClass(tab, 'dragged'));
B
Benjamin Pasero 已提交
763 764
		}));

765 766 767 768 769 770
		// We need to keep track of DRAG_ENTER and DRAG_LEAVE events because a tab is not just a div without children,
		// it contains a label and a close button. HTML gives us DRAG_ENTER and DRAG_LEAVE events when hovering over
		// these children and this can cause flicker of the drop feedback. The workaround is to count the events and only
		// remove the drop feedback when the counter is 0 (see https://github.com/Microsoft/vscode/issues/14470)
		let counter = 0;

B
Benjamin Pasero 已提交
771
		// Drag over
772
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_ENTER, (e: DragEvent) => {
773
			counter++;
B
Benjamin Pasero 已提交
774 775 776 777

			// Find out if the currently dragged editor is this tab and in that
			// case we do not want to show any drop feedback
			let draggedEditorIsTab = false;
778
			const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
B
Benjamin Pasero 已提交
779
			if (draggedEditor) {
780
				const { group, editor } = this.getGroupPositionAndEditor(index);
B
Benjamin Pasero 已提交
781 782 783 784 785
				if (draggedEditor.editor === editor && draggedEditor.group === group) {
					draggedEditorIsTab = true;
				}
			}

786 787
			DOM.addClass(tab, 'dragged-over');

B
Benjamin Pasero 已提交
788 789 790
			if (!draggedEditorIsTab) {
				this.updateDropFeedback(tab, true, index);
			}
B
Benjamin Pasero 已提交
791 792 793
		}));

		// Drag leave
794
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_LEAVE, (e: DragEvent) => {
795 796
			counter--;
			if (counter === 0) {
797
				DOM.removeClass(tab, 'dragged-over');
B
Benjamin Pasero 已提交
798
				this.updateDropFeedback(tab, false, index);
799
			}
B
Benjamin Pasero 已提交
800 801 802
		}));

		// Drag end
803
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DRAG_END, (e: DragEvent) => {
804
			counter = 0;
805
			DOM.removeClass(tab, 'dragged-over');
B
Benjamin Pasero 已提交
806
			this.updateDropFeedback(tab, false, index);
B
Benjamin Pasero 已提交
807

808
			this.transfer.clearData();
B
Benjamin Pasero 已提交
809 810 811
		}));

		// Drop
812
		disposables.push(DOM.addDisposableListener(tab, DOM.EventType.DROP, (e: DragEvent) => {
813
			counter = 0;
814
			DOM.removeClass(tab, 'dragged-over');
B
Benjamin Pasero 已提交
815
			this.updateDropFeedback(tab, false, index);
B
Benjamin Pasero 已提交
816

817
			const { group, position } = this.getGroupPositionAndEditor(index);
818

B
Benjamin Pasero 已提交
819
			this.onDrop(e, group, position, index);
B
Benjamin Pasero 已提交
820
		}));
821 822

		return combinedDisposable(disposables);
B
Benjamin Pasero 已提交
823
	}
B
Benjamin Pasero 已提交
824

825 826 827 828
	private isTabActionBar(element: HTMLElement): boolean {
		return !!DOM.findParentWithClass(element, 'monaco-action-bar', 'tab');
	}

829
	private getGroupPositionAndEditor(index: number): { group: IEditorGroup, position: Position, editor: IEditorInput } {
B
Benjamin Pasero 已提交
830 831 832 833 834 835 836
		const group = this.context;
		const position = this.stacks.positionOfGroup(group);
		const editor = group.getEditor(index);

		return { group, position, editor };
	}

B
Benjamin Pasero 已提交
837
	private onDrop(e: DragEvent, group: IEditorGroup, targetPosition: Position, targetIndex: number): void {
B
Benjamin Pasero 已提交
838 839
		DOM.EventHelper.stop(e, true);

B
Benjamin Pasero 已提交
840
		this.updateDropFeedback(this.tabsContainer, false);
841
		DOM.removeClass(this.tabsContainer, 'scroll');
842

B
Benjamin Pasero 已提交
843
		// Local DND
844
		const draggedEditor = this.transfer.hasData(DraggedEditorIdentifier.prototype) ? this.transfer.getData(DraggedEditorIdentifier.prototype)[0].identifier : void 0;
B
Benjamin Pasero 已提交
845
		if (draggedEditor) {
846

B
Benjamin Pasero 已提交
847 848
			// Move editor to target position and index
			if (this.isMoveOperation(e, draggedEditor.group, group)) {
849
				this.editorGroupService.moveEditor(draggedEditor.editor, draggedEditor.group, group, { index: targetIndex });
B
Benjamin Pasero 已提交
850
			}
851

B
Benjamin Pasero 已提交
852
			// Copy: just open editor at target index
853
			else {
B
Benjamin Pasero 已提交
854
				this.editorService.openEditor(draggedEditor.editor, { pinned: true, index: targetIndex }, targetPosition).done(null, errors.onUnexpectedError);
855
			}
B
Benjamin Pasero 已提交
856

857
			this.transfer.clearData();
B
Benjamin Pasero 已提交
858 859 860 861
		}

		// External DND
		else {
B
Benjamin Pasero 已提交
862
			const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false /* open workspace file as file if dropped */ });
863
			dropHandler.handleDrop(e, () => this.editorGroupService.focusGroup(targetPosition), targetPosition, targetIndex);
864 865 866
		}
	}

867 868 869 870 871
	private isMoveOperation(e: DragEvent, source: IEditorGroup, target: IEditorGroup) {
		const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh);

		return !isCopy || source.id === target.id;
	}
872 873 874 875 876 877

	public dispose(): void {
		super.dispose();

		this.layoutScheduled = dispose(this.layoutScheduled);
	}
J
Johannes Rieken 已提交
878
}
B
Benjamin Pasero 已提交
879 880 881

class TabActionRunner extends ActionRunner {

B
Benjamin Pasero 已提交
882 883 884 885
	constructor(
		private group: () => IEditorGroup,
		private index: number
	) {
B
Benjamin Pasero 已提交
886 887 888
		super();
	}

B
Benjamin Pasero 已提交
889
	public run(action: IAction, context?: any): TPromise<void> {
B
Benjamin Pasero 已提交
890
		const group = this.group();
B
Benjamin Pasero 已提交
891 892 893
		if (!group) {
			return TPromise.as(void 0);
		}
B
Benjamin Pasero 已提交
894

895
		return super.run(action, { groupId: group.id, editorIndex: this.index });
B
Benjamin Pasero 已提交
896
	}
B
Benjamin Pasero 已提交
897 898 899 900
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {

B
Benjamin Pasero 已提交
901
	// Styling with Outline color (e.g. high contrast theme)
902 903
	const activeContrastBorderColor = theme.getColor(activeContrastBorder);
	if (activeContrastBorderColor) {
B
Benjamin Pasero 已提交
904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923
		collector.addRule(`
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active,
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover  {
				outline: 1px solid;
				outline-offset: -5px;
			}

			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover  {
				outline: 1px dashed;
				outline-offset: -5px;
			}

			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active > .tab-close .action-label,
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover > .tab-close .action-label,
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty > .tab-close .action-label,
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover > .tab-close .action-label {
				opacity: 1 !important;
			}
		`);
	}
924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961

	// Hover Background
	const tabHoverBackground = theme.getColor(TAB_HOVER_BACKGROUND);
	if (tabHoverBackground) {
		collector.addRule(`
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover  {
				background: ${tabHoverBackground} !important;
			}
		`);
	}

	const tabUnfocusedHoverBackground = theme.getColor(TAB_UNFOCUSED_HOVER_BACKGROUND);
	if (tabUnfocusedHoverBackground) {
		collector.addRule(`
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover  {
				background: ${tabUnfocusedHoverBackground} !important;
			}
		`);
	}

	// Hover Border
	const tabHoverBorder = theme.getColor(TAB_HOVER_BORDER);
	if (tabHoverBorder) {
		collector.addRule(`
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover  {
				box-shadow: ${tabHoverBorder} 0 -1px inset !important;
			}
		`);
	}

	const tabUnfocusedHoverBorder = theme.getColor(TAB_UNFOCUSED_HOVER_BORDER);
	if (tabUnfocusedHoverBorder) {
		collector.addRule(`
			.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover  {
				box-shadow: ${tabUnfocusedHoverBorder} 0 -1px inset !important;
			}
		`);
	}
962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053

	// Fade out styles via linear gradient (when tabs are set to shrink)
	if (theme.type !== 'hc') {
		const workbenchBackground = WORKBENCH_BACKGROUND(theme);
		const editorBackgroundColor = theme.getColor(editorBackground);
		const editorGroupBackground = theme.getColor(EDITOR_GROUP_BACKGROUND);
		const editorGroupHeaderTabsBackground = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND);
		const editorDragAndDropBackground = theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND);

		let adjustedTabBackground: Color;
		if (editorGroupHeaderTabsBackground && editorBackgroundColor && editorGroupBackground) {
			adjustedTabBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorGroupBackground, editorBackgroundColor, workbenchBackground);
		}

		let adjustedTabDragBackground: Color;
		if (editorGroupHeaderTabsBackground && editorBackgroundColor && editorDragAndDropBackground && editorBackgroundColor) {
			adjustedTabDragBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorDragAndDropBackground, editorBackgroundColor, workbenchBackground);
		}

		// Adjust gradient for (focused) hover background
		if (tabHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
			const adjustedColor = tabHoverBackground.flatten(adjustedTabBackground);
			const adjustedColorDrag = tabHoverBackground.flatten(adjustedTabDragBackground);
			collector.addRule(`
				.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
					background: linear-gradient(to left, ${adjustedColor}, transparent);
				}

				.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
					background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
				}
			`);
		}

		// Adjust gradient for unfocused hover background
		if (tabUnfocusedHoverBackground && adjustedTabBackground && adjustedTabDragBackground) {
			const adjustedColor = tabUnfocusedHoverBackground.flatten(adjustedTabBackground);
			const adjustedColorDrag = tabUnfocusedHoverBackground.flatten(adjustedTabDragBackground);
			collector.addRule(`
				.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
					background: linear-gradient(to left, ${adjustedColor}, transparent);
				}

				.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink:not(.dragged):hover > .tab-label::after {
					background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
				}
			`);
		}

		// Adjust gradient for drag and drop background
		if (editorDragAndDropBackground && adjustedTabDragBackground) {
			const adjustedColorDrag = editorDragAndDropBackground.flatten(adjustedTabDragBackground);
			collector.addRule(`
			.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.active .tabs-container > .tab.sizing-shrink.dragged-over:not(.active):not(.dragged) > .tab-label::after,
			.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title.inactive .tabs-container > .tab.sizing-shrink.dragged-over:not(.dragged) > .tab-label::after {
				background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
			}
		`);
		}

		// Adjust gradient for active tab background
		const tabActiveBackground = theme.getColor(TAB_ACTIVE_BACKGROUND);
		if (tabActiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
			const adjustedColor = tabActiveBackground.flatten(adjustedTabBackground);
			const adjustedColorDrag = tabActiveBackground.flatten(adjustedTabDragBackground);
			collector.addRule(`
				.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
					background: linear-gradient(to left, ${adjustedColor}, transparent);
				}

				.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink.active:not(.dragged) > .tab-label::after {
					background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
				}
			`);
		}

		// Adjust gradient for inactive tab background
		const tabInactiveBackground = theme.getColor(TAB_INACTIVE_BACKGROUND);
		if (tabInactiveBackground && adjustedTabBackground && adjustedTabDragBackground) {
			const adjustedColor = tabInactiveBackground.flatten(adjustedTabBackground);
			const adjustedColorDrag = tabInactiveBackground.flatten(adjustedTabDragBackground);
			collector.addRule(`
			.monaco-workbench > .part.editor > .content:not(.dragged-over) > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink:not(.dragged) > .tab-label::after {
				background: linear-gradient(to left, ${adjustedColor}, transparent);
			}

			.monaco-workbench > .part.editor > .content.dragged-over > .one-editor-silo > .container > .title .tabs-container > .tab.sizing-shrink:not(.dragged) > .tab-label::after {
				background: linear-gradient(to left, ${adjustedColorDrag}, transparent);
			}
		`);
		}
	}
1054
});