tabsTitleControl.ts 7.9 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
wip  
Benjamin Pasero 已提交
10 11
import {prepareActions} from 'vs/workbench/browser/actionBarRegistry';
import arrays = require('vs/base/common/arrays');
B
Benjamin Pasero 已提交
12 13
import errors = require('vs/base/common/errors');
import DOM = require('vs/base/browser/dom');
B
wip  
Benjamin Pasero 已提交
14 15 16
import {Builder, $} from 'vs/base/browser/builder';
import {IEditorGroup} from 'vs/workbench/common/editor';
import {ToolBar} from 'vs/base/browser/ui/toolbar/toolbar';
B
Benjamin Pasero 已提交
17
import {ActionBar} from 'vs/base/browser/ui/actionbar/actionbar';
B
wip  
Benjamin Pasero 已提交
18 19 20 21 22 23 24 25
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {IContextMenuService} from 'vs/platform/contextview/browser/contextView';
import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService';
import {IMessageService} from 'vs/platform/message/common/message';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService';
import {TitleControl} from 'vs/workbench/browser/parts/editor/titleControl';
B
Benjamin Pasero 已提交
26
import {IEditorInput} from 'vs/platform/editor/common/editor';
B
wip  
Benjamin Pasero 已提交
27 28 29

export class TabsTitleControl extends TitleControl {
	private titleContainer: Builder;
B
Benjamin Pasero 已提交
30
	private tabsContainer: Builder;
B
wip  
Benjamin Pasero 已提交
31 32

	private groupActionsToolbar: ToolBar;
B
Benjamin Pasero 已提交
33
	private tabActionBars: ActionBar[];
B
wip  
Benjamin Pasero 已提交
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

	private currentPrimaryGroupActionIds: string[];
	private currentSecondaryGroupActionIds: string[];

	constructor(
		@IContextMenuService contextMenuService: IContextMenuService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IWorkbenchEditorService editorService: IWorkbenchEditorService,
		@IEditorGroupService editorGroupService: IEditorGroupService,
		@IKeybindingService keybindingService: IKeybindingService,
		@ITelemetryService telemetryService: ITelemetryService,
		@IMessageService messageService: IMessageService
	) {
		super(contextMenuService, instantiationService, editorService, editorGroupService, keybindingService, telemetryService, messageService);

		this.currentPrimaryGroupActionIds = [];
		this.currentSecondaryGroupActionIds = [];
B
Benjamin Pasero 已提交
51 52

		this.tabActionBars = [];
B
wip  
Benjamin Pasero 已提交
53 54 55 56 57 58 59 60 61 62 63
	}

	public setContext(group: IEditorGroup): void {
		super.setContext(group);

		this.groupActionsToolbar.context = { group };
	}

	public create(parent: Builder): void {
		this.titleContainer = $(parent);

64 65 66 67
		// Tabs Container
		parent.div({
			'class': 'tabs-container'
		}, (div) => {
B
Benjamin Pasero 已提交
68
			this.tabsContainer = div;
69 70 71 72 73 74 75 76 77

			// Support to scroll the tabs container with the mouse wheel
			// if we detect that scrolling happens in Y-axis
			div.on('wheel', (e: WheelEvent) => {
				if (e.deltaY && !e.deltaX) {
					DOM.EventHelper.stop(e);
					this.tabsContainer.getHTMLElement().scrollLeft += e.deltaY;
				}
			});
78 79
		});

B
Benjamin Pasero 已提交
80
		// Group Actions
B
wip  
Benjamin Pasero 已提交
81
		parent.div({
82
			'class': 'group-actions'
B
wip  
Benjamin Pasero 已提交
83 84 85 86 87
		}, (div) => {
			this.groupActionsToolbar = this.doCreateToolbar(div);
		});
	}

B
Benjamin Pasero 已提交
88 89 90 91
	public allowDragging(element: HTMLElement): boolean {
		return (element.className === 'tabs-container');
	}

B
wip  
Benjamin Pasero 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
	public refresh(): void {
		if (!this.context) {
			return;
		}

		const group = this.context;
		const editor = group.activeEditor;
		if (!editor) {
			this.groupActionsToolbar.setActions([], [])();

			this.currentPrimaryGroupActionIds = [];
			this.currentSecondaryGroupActionIds = [];

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

108
		// Activity state
B
Benjamin Pasero 已提交
109
		const isActive = this.stacks.isActive(group);
110
		if (isActive) {
B
Benjamin Pasero 已提交
111
			this.titleContainer.addClass('active');
112
		} else {
B
Benjamin Pasero 已提交
113
			this.titleContainer.removeClass('active');
114 115
		}

B
wip  
Benjamin Pasero 已提交
116 117 118 119 120 121 122 123 124 125 126 127
		// Update Group Actions Toolbar
		const groupActions = this.getGroupActions(group);
		const primaryGroupActions = prepareActions(groupActions.primary);
		const secondaryGroupActions = prepareActions(groupActions.secondary);
		const primaryGroupActionIds = primaryGroupActions.map(a => a.id);
		const secondaryGroupActionIds = secondaryGroupActions.map(a => a.id);

		if (!arrays.equals(primaryGroupActionIds, this.currentPrimaryGroupActionIds) || !arrays.equals(secondaryGroupActionIds, this.currentSecondaryGroupActionIds)) {
			this.groupActionsToolbar.setActions(primaryGroupActions, secondaryGroupActions)();
			this.currentPrimaryGroupActionIds = primaryGroupActionIds;
			this.currentSecondaryGroupActionIds = secondaryGroupActionIds;
		}
B
Benjamin Pasero 已提交
128 129 130

		// Refresh Tabs
		this.refreshTabs(group);
B
wip  
Benjamin Pasero 已提交
131 132
	}

B
Benjamin Pasero 已提交
133 134 135 136
	private refreshTabs(group: IEditorGroup): void {

		// Empty container first
		this.tabsContainer.empty();
B
Benjamin Pasero 已提交
137
		while (this.tabActionBars.length) {
B
Benjamin Pasero 已提交
138 139 140
			this.tabActionBars.pop().dispose();
		}

B
Benjamin Pasero 已提交
141 142
		let activeTab: HTMLElement;

B
Benjamin Pasero 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
		// Add a tab for each opened editor
		this.context.getEditors().forEach(editor => {
			const isPinned = group.isPinned(editor);
			const isActive = group.isActive(editor);
			const isDirty = editor.isDirty();

			$(this.tabsContainer).div({ 'class': 'tab monaco-editor-background' }, tab => {

				// Eventing
				this.hookTabListeners(tab, editor, group);

				// Pinned state
				if (isPinned) {
					tab.addClass('pinned');
				} else {
					tab.removeClass('pinned');
				}

				// Active state
				if (isActive) {
B
Benjamin Pasero 已提交
163
					tab.addClass('active');
B
Benjamin Pasero 已提交
164
					activeTab = tab.getHTMLElement();
B
Benjamin Pasero 已提交
165
				} else {
B
Benjamin Pasero 已提交
166
					tab.removeClass('active');
B
Benjamin Pasero 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
				}

				// Dirty State
				if (isDirty) {
					tab.addClass('dirty');
				} else {
					tab.removeClass('dirty');
				}

				// Tab Label
				tab.div({
					'class': 'tab-label'
				}, (div) => {
					$(div).a().safeInnerHtml(editor.getName()).title(editor.getDescription(true));
				});

				// Tab Close
				tab.div({
					'class': 'tab-close'
				}, (div) => {
B
Benjamin Pasero 已提交
187
					const bar = new ActionBar(div, { context: { editor, group }, ariaLabel: nls.localize('araLabelTabActions', "Tab actions") });
B
Benjamin Pasero 已提交
188 189 190 191 192 193
					bar.push(this.closeEditorAction, { icon: true, label: false });

					this.tabActionBars.push(bar);
				});
			});
		});
B
Benjamin Pasero 已提交
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

		// Always reveal the active one
		const container = this.tabsContainer.getHTMLElement();
		const containerWidth = container.offsetWidth;
		const containerScrollPosX = container.scrollLeft;
		const activeTabPosX = activeTab.offsetLeft;
		const activeTabWidth = activeTab.offsetWidth;

		// Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right
		if (containerScrollPosX + containerWidth < activeTabPosX + activeTabWidth) {
			container.scrollLeft += ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + containerWidth) /* right corner of view port */);
		}

		// Tab is overlflowng to the left: Scroll it into view to the left
		else if (containerScrollPosX > activeTabPosX) {
			container.scrollLeft = activeTab.offsetLeft;
		}
B
Benjamin Pasero 已提交
211 212 213 214 215 216 217 218 219
	}

	private hookTabListeners(tab: Builder, editor: IEditorInput, group: IEditorGroup): void {
		const position = this.stacks.positionOfGroup(group);

		// Open on Click
		tab.on(DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => {
			DOM.EventHelper.stop(e);

220
			if (e.button === 0 /* Left Button */ && !DOM.findParentWithClass(<any>e.target || e.srcElement, 'monaco-action-bar', 'tab')) {
B
Benjamin Pasero 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
				this.editorService.openEditor(editor, null, position).done(null, errors.onUnexpectedError);
			}
		});

		// Pin on double click
		tab.on(DOM.EventType.DBLCLICK, (e: MouseEvent) => {
			DOM.EventHelper.stop(e);

			this.editorGroupService.pinEditor(position, editor);
		});

		// Close on mouse middle click
		tab.on(DOM.EventType.MOUSE_UP, (e: MouseEvent) => {
			DOM.EventHelper.stop(e);

			if (e.button === 1 /* Middle Button */) {
				this.editorService.closeEditor(position, editor).done(null, errors.onUnexpectedError);
			}
		});
	}

B
wip  
Benjamin Pasero 已提交
242 243 244
	public dispose(): void {
		super.dispose();

B
Benjamin Pasero 已提交
245
		// Toolbar
B
wip  
Benjamin Pasero 已提交
246 247 248
		this.groupActionsToolbar.dispose();
	}
}