openEditorsView.ts 26.2 KB
Newer Older
I
isidor 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

I
isidor 已提交
6
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
7
import { RunOnceScheduler } from 'vs/base/common/async';
8
import { IAction, ActionRunner, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
I
isidor 已提交
9
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
10 11
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
12
import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
13
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
J
Johannes Rieken 已提交
14
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
I
isidor 已提交
15
import { IEditorInput, Verbosity } from 'vs/workbench/common/editor';
16
import { SaveAllAction, SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions';
17
import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/contrib/files/common/files';
J
Johannes Rieken 已提交
18 19
import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
20
import { CloseAllEditorsAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions';
B
Benjamin Pasero 已提交
21
import { ToggleEditorLayoutAction } from 'vs/workbench/browser/actions/layoutActions';
22
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
23
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
24
import { IThemeService } from 'vs/platform/theme/common/themeService';
25
import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
26
import { WorkbenchList } from 'vs/platform/list/browser/listService';
27
import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction } from 'vs/base/browser/ui/list/list';
28
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
I
isidor 已提交
29
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
I
isidor 已提交
30
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
31
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
32
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
33
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
34
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
35
import { DirtyEditorContext, OpenEditorsGroupContext } from 'vs/workbench/contrib/files/browser/fileCommands';
B
Benjamin Pasero 已提交
36
import { ResourceContextKey } from 'vs/workbench/common/resources';
37
import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers } from 'vs/workbench/browser/dnd';
38 39
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
40
import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd';
41
import { memoize } from 'vs/base/common/decorators';
42
import { ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/browser/ui/list/listView';
I
isidor 已提交
43
import { URI } from 'vs/base/common/uri';
44
import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types';
I
isidor 已提交
45

J
Joao Moreno 已提交
46
const $ = dom.$;
I
isidor 已提交
47

48
export class OpenEditorsView extends ViewletPanel {
I
isidor 已提交
49

50
	private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9;
51
	static readonly ID = 'workbench.explorer.openEditorsView';
52
	static NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors");
I
isidor 已提交
53 54

	private dirtyCountElement: HTMLElement;
I
isidor 已提交
55
	private listRefreshScheduler: RunOnceScheduler;
56
	private structuralRefreshDelay: number;
57
	private list: WorkbenchList<OpenEditor | IEditorGroup>;
B
Benjamin Pasero 已提交
58
	private listLabels: ResourceLabels;
59
	private contributedContextMenu: IMenu;
I
isidor 已提交
60
	private needsRefresh: boolean;
B
Benjamin Pasero 已提交
61
	private resourceContext: ResourceContextKey;
62
	private groupFocusedContext: IContextKey<boolean>;
63
	private dirtyEditorFocusedContext: IContextKey<boolean>;
I
isidor 已提交
64

J
Joao 已提交
65 66
	constructor(
		options: IViewletViewOptions,
67
		@IInstantiationService private readonly instantiationService: IInstantiationService,
I
isidor 已提交
68
		@IContextMenuService contextMenuService: IContextMenuService,
69 70 71
		@ITextFileService private readonly textFileService: ITextFileService,
		@IEditorService private readonly editorService: IEditorService,
		@IEditorGroupsService private readonly editorGroupService: IEditorGroupsService,
72
		@IConfigurationService configurationService: IConfigurationService,
B
Benjamin Pasero 已提交
73
		@IKeybindingService keybindingService: IKeybindingService,
74 75 76 77 78
		@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
		@IThemeService private readonly themeService: IThemeService,
		@ITelemetryService private readonly telemetryService: ITelemetryService,
		@IMenuService private readonly menuService: IMenuService
I
isidor 已提交
79
	) {
80
		super({
81
			...(options as IViewletPanelOptions),
S
#27823  
Sandeep Somavarapu 已提交
82
			ariaHeaderLabel: nls.localize({ key: 'openEditosrSection', comment: ['Open is an adjective'] }, "Open Editors Section"),
S
Sandeep Somavarapu 已提交
83
		}, keybindingService, contextMenuService, configurationService, contextKeyService);
I
isidor 已提交
84

85
		this.structuralRefreshDelay = 0;
I
isidor 已提交
86
		this.listRefreshScheduler = new RunOnceScheduler(() => {
I
isidor 已提交
87
			const previousLength = this.list.length;
I
isidor 已提交
88 89
			this.list.splice(0, this.list.length, this.elements);
			this.focusActiveEditor();
I
isidor 已提交
90 91 92
			if (previousLength !== this.list.length) {
				this.updateSize();
			}
I
isidor 已提交
93 94 95
			this.needsRefresh = false;
		}, this.structuralRefreshDelay);

96
		this.registerUpdateEvents();
I
isidor 已提交
97 98

		// Also handle configuration updates
M
Matt Bierner 已提交
99
		this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e)));
I
isidor 已提交
100 101

		// Handle dirty counter
M
Matt Bierner 已提交
102 103 104 105 106
		this._register(this.untitledEditorService.onDidChangeDirty(() => this.updateDirtyIndicator()));
		this._register(this.textFileService.models.onModelsDirty(() => this.updateDirtyIndicator()));
		this._register(this.textFileService.models.onModelsSaved(() => this.updateDirtyIndicator()));
		this._register(this.textFileService.models.onModelsSaveError(() => this.updateDirtyIndicator()));
		this._register(this.textFileService.models.onModelsReverted(() => this.updateDirtyIndicator()));
107 108 109 110
	}

	private registerUpdateEvents(): void {
		const updateWholeList = () => {
111
			if (!this.isBodyVisible() || !this.list) {
112 113 114 115 116 117 118
				this.needsRefresh = true;
				return;
			}

			this.listRefreshScheduler.schedule(this.structuralRefreshDelay);
		};

I
isidor 已提交
119
		const groupDisposables = new Map<number, IDisposable>();
120
		const addGroupListener = (group: IEditorGroup) => {
I
isidor 已提交
121 122 123 124
			groupDisposables.set(group.id, group.onDidGroupChange(e => {
				if (this.listRefreshScheduler.isScheduled()) {
					return;
				}
125
				if (!this.isBodyVisible() || !this.list) {
I
isidor 已提交
126 127 128 129 130 131 132 133 134 135 136 137
					this.needsRefresh = true;
					return;
				}

				const index = this.getIndex(group, e.editor);
				switch (e.kind) {
					case GroupChangeKind.GROUP_LABEL: {
						if (this.showGroups) {
							this.list.splice(index, 1, [group]);
						}
						break;
					}
138
					case GroupChangeKind.GROUP_ACTIVE:
I
isidor 已提交
139 140 141 142 143 144 145
					case GroupChangeKind.EDITOR_ACTIVE: {
						this.focusActiveEditor();
						break;
					}
					case GroupChangeKind.EDITOR_DIRTY:
					case GroupChangeKind.EDITOR_LABEL:
					case GroupChangeKind.EDITOR_PIN: {
I
isidor 已提交
146
						this.list.splice(index, 1, [new OpenEditor(e.editor!, group)]);
I
isidor 已提交
147 148 149
						break;
					}
					case GroupChangeKind.EDITOR_OPEN: {
I
isidor 已提交
150
						this.list.splice(index, 0, [new OpenEditor(e.editor!, group)]);
I
isidor 已提交
151
						setTimeout(() => this.updateSize(), this.structuralRefreshDelay);
I
isidor 已提交
152 153 154
						break;
					}
					case GroupChangeKind.EDITOR_CLOSE: {
I
isidor 已提交
155
						const previousIndex = this.getIndex(group, undefined) + (e.editorIndex || 0) + (this.showGroups ? 1 : 0);
I
isidor 已提交
156 157 158 159 160
						this.list.splice(previousIndex, 1);
						this.updateSize();
						break;
					}
					case GroupChangeKind.EDITOR_MOVE: {
I
isidor 已提交
161 162 163 164 165
						this.listRefreshScheduler.schedule();
						break;
					}
				}
			}));
M
Matt Bierner 已提交
166
			this._register(groupDisposables.get(group.id)!);
I
isidor 已提交
167 168 169
		};

		this.editorGroupService.groups.forEach(g => addGroupListener(g));
M
Matt Bierner 已提交
170
		this._register(this.editorGroupService.onDidAddGroup(group => {
I
isidor 已提交
171
			addGroupListener(group);
I
isidor 已提交
172 173
			updateWholeList();
		}));
M
Matt Bierner 已提交
174 175
		this._register(this.editorGroupService.onDidMoveGroup(() => updateWholeList()));
		this._register(this.editorGroupService.onDidRemoveGroup(group => {
I
isidor 已提交
176 177 178
			dispose(groupDisposables.get(group.id));
			updateWholeList();
		}));
I
isidor 已提交
179 180
	}

J
Jackson Kearl 已提交
181
	protected renderHeaderTitle(container: HTMLElement): void {
J
Jackson Kearl 已提交
182
		super.renderHeaderTitle(container, this.title);
I
isidor 已提交
183

J
Jackson Kearl 已提交
184
		const count = dom.append(container, $('.count'));
185
		this.dirtyCountElement = dom.append(count, $('.monaco-count-badge'));
186

M
Matt Bierner 已提交
187
		this._register((attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => {
188 189
			const background = colors.badgeBackground ? colors.badgeBackground.toString() : null;
			const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : null;
190
			const border = colors.contrastBorder ? colors.contrastBorder.toString() : null;
191 192 193 194 195 196 197 198 199

			this.dirtyCountElement.style.backgroundColor = background;
			this.dirtyCountElement.style.color = foreground;

			this.dirtyCountElement.style.borderWidth = border ? '1px' : null;
			this.dirtyCountElement.style.borderStyle = border ? 'solid' : null;
			this.dirtyCountElement.style.borderColor = border;
		})));

I
isidor 已提交
200 201 202
		this.updateDirtyIndicator();
	}

I
isidor 已提交
203 204 205 206 207
	public renderBody(container: HTMLElement): void {
		dom.addClass(container, 'explorer-open-editors');
		dom.addClass(container, 'show-file-icons');

		const delegate = new OpenEditorsDelegate();
208 209 210 211

		if (this.list) {
			this.list.dispose();
		}
B
Benjamin Pasero 已提交
212 213
		if (this.listLabels) {
			this.listLabels.clear();
214
		}
215
		this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
216
		this.list = this.instantiationService.createInstance(WorkbenchList, container, delegate, [
217 218
			new EditorGroupRenderer(this.keybindingService, this.instantiationService),
			new OpenEditorRenderer(this.listLabels, this.instantiationService, this.keybindingService, this.configurationService)
I
isidor 已提交
219
		], {
220 221
				identityProvider: { getId: (element: OpenEditor | IEditorGroup) => element instanceof OpenEditor ? element.getId() : element.id.toString() },
				dnd: new OpenEditorsDragAndDrop(this.instantiationService, this.editorGroupService)
222
			});
M
Matt Bierner 已提交
223 224
		this._register(this.list);
		this._register(this.listLabels);
I
isidor 已提交
225

226
		this.contributedContextMenu = this.menuService.createMenu(MenuId.OpenEditorsContext, this.list.contextKeyService);
M
Matt Bierner 已提交
227
		this._register(this.contributedContextMenu);
228

I
isidor 已提交
229
		this.updateSize();
B
Benjamin Pasero 已提交
230

I
isidor 已提交
231 232 233
		// Bind context keys
		OpenEditorsFocusedContext.bindTo(this.list.contextKeyService);
		ExplorerFocusedContext.bindTo(this.list.contextKeyService);
B
Benjamin Pasero 已提交
234 235

		this.resourceContext = this.instantiationService.createInstance(ResourceContextKey);
M
Matt Bierner 已提交
236
		this._register(this.resourceContext);
B
Benjamin Pasero 已提交
237
		this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService);
238
		this.dirtyEditorFocusedContext = DirtyEditorContext.bindTo(this.contextKeyService);
I
isidor 已提交
239

M
Matt Bierner 已提交
240
		this._register(this.list.onContextMenu(e => this.onListContextMenu(e)));
241
		this.list.onFocusChange(e => {
B
Benjamin Pasero 已提交
242
			this.resourceContext.reset();
243
			this.groupFocusedContext.reset();
244
			this.dirtyEditorFocusedContext.reset();
245 246
			const element = e.elements.length ? e.elements[0] : undefined;
			if (element instanceof OpenEditor) {
I
isidor 已提交
247
				this.dirtyEditorFocusedContext.set(this.textFileService.isDirty(withNullAsUndefined(element.getResource())));
248
				this.resourceContext.set(withUndefinedAsNull(element.getResource()));
249
			} else if (!!element) {
250 251 252
				this.groupFocusedContext.set(true);
			}
		});
I
isidor 已提交
253 254

		// Open when selecting via keyboard
M
Matt Bierner 已提交
255
		this._register(this.list.onMouseMiddleClick(e => {
256
			if (e && e.element instanceof OpenEditor) {
I
isidor 已提交
257
				e.element.group.closeEditor(e.element.editor, { preserveFocus: true });
258 259
			}
		}));
M
Matt Bierner 已提交
260
		this._register(this.list.onDidOpen(e => {
261 262 263 264 265 266 267 268 269 270 271 272 273 274
			const browserEvent = e.browserEvent;

			let openToSide = false;
			let isSingleClick = false;
			let isDoubleClick = false;
			if (browserEvent instanceof MouseEvent) {
				isSingleClick = browserEvent.detail === 1;
				isDoubleClick = browserEvent.detail === 2;
				openToSide = this.list.useAltAsMultipleSelectionModifier ? (browserEvent.ctrlKey || browserEvent.metaKey) : browserEvent.altKey;
			}

			const focused = this.list.getFocusedElements();
			const element = focused.length ? focused[0] : undefined;
			if (element instanceof OpenEditor) {
275
				this.openEditor(element, { preserveFocus: isSingleClick, pinned: isDoubleClick, sideBySide: openToSide });
I
isidor 已提交
276
			} else if (element) {
277
				this.editorGroupService.activateGroup(element);
I
isidor 已提交
278 279 280 281
			}
		}));

		this.listRefreshScheduler.schedule(0);
282

M
Matt Bierner 已提交
283
		this._register(this.onDidChangeBodyVisibility(visible => {
284 285 286 287
			if (visible && this.needsRefresh) {
				this.listRefreshScheduler.schedule(0);
			}
		}));
I
isidor 已提交
288 289
	}

I
isidor 已提交
290 291
	public getActions(): IAction[] {
		return [
292
			this.instantiationService.createInstance(ToggleEditorLayoutAction, ToggleEditorLayoutAction.ID, ToggleEditorLayoutAction.LABEL),
I
isidor 已提交
293
			this.instantiationService.createInstance(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL),
I
isidor 已提交
294
			this.instantiationService.createInstance(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL)
I
isidor 已提交
295 296 297
		];
	}

S
Sandeep Somavarapu 已提交
298 299
	public focus(): void {
		super.focus();
S
Sandeep Somavarapu 已提交
300
		this.list.domFocus();
S
Sandeep Somavarapu 已提交
301 302
	}

303
	public getList(): WorkbenchList<OpenEditor | IEditorGroup> {
I
isidor 已提交
304 305
		return this.list;
	}
306

307
	protected layoutBody(height: number, width: number): void {
I
isidor 已提交
308
		if (this.list) {
309
			this.list.layout(height, width);
I
isidor 已提交
310 311 312
		}
	}

313
	private get showGroups(): boolean {
I
isidor 已提交
314
		return this.editorGroupService.groups.length > 1;
315 316
	}

317 318
	private get elements(): Array<IEditorGroup | OpenEditor> {
		const result: Array<IEditorGroup | OpenEditor> = [];
319
		this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(g => {
320
			if (this.showGroups) {
I
isidor 已提交
321
				result.push(g);
B
Benjamin Pasero 已提交
322
			}
I
isidor 已提交
323
			result.push(...g.editors.map(ei => new OpenEditor(ei, g)));
I
isidor 已提交
324
		});
B
Benjamin Pasero 已提交
325

I
isidor 已提交
326
		return result;
I
isidor 已提交
327 328
	}

I
isidor 已提交
329
	private getIndex(group: IEditorGroup, editor: IEditorInput | undefined | null): number {
I
isidor 已提交
330
		let index = editor ? group.getIndexOfEditor(editor) : 0;
331
		if (!this.showGroups) {
332 333 334
			return index;
		}

335
		for (let g of this.editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE)) {
336
			if (g.id === group.id) {
337
				return index + (!!editor ? 1 : 0);
I
isidor 已提交
338
			} else {
339
				index += g.count + 1;
I
isidor 已提交
340 341
			}
		}
I
isidor 已提交
342

I
isidor 已提交
343 344
		return -1;
	}
I
isidor 已提交
345

I
isidor 已提交
346 347
	private openEditor(element: OpenEditor, options: { preserveFocus: boolean; pinned: boolean; sideBySide: boolean; }): void {
		if (element) {
348
			this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'openEditors' });
349 350 351

			const preserveActivateGroup = options.sideBySide && options.preserveFocus; // needed for https://github.com/Microsoft/vscode/issues/42399
			if (!preserveActivateGroup) {
I
isidor 已提交
352
				this.editorGroupService.activateGroup(element.groupId); // needed for https://github.com/Microsoft/vscode/issues/6672
353
			}
354
			this.editorService.openEditor(element.editor, options, options.sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => {
I
isidor 已提交
355
				if (editor && !preserveActivateGroup && editor.group) {
356
					this.editorGroupService.activateGroup(editor.group);
357
				}
358
			});
I
isidor 已提交
359 360
		}
	}
I
isidor 已提交
361

362
	private onListContextMenu(e: IListContextMenuEvent<OpenEditor | IEditorGroup>): void {
J
Joao Moreno 已提交
363 364 365 366
		if (!e.element) {
			return;
		}

I
isidor 已提交
367
		const element = e.element;
368 369 370
		const actions: IAction[] = [];
		const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editor.getResource() : {} }, actions, this.contextMenuService);

I
isidor 已提交
371 372
		this.contextMenuService.showContextMenu({
			getAnchor: () => e.anchor,
373 374 375
			getActions: () => actions,
			getActionsContext: () => element instanceof OpenEditor ? { groupId: element.groupId, editorIndex: element.editorIndex } : { groupId: element.id },
			onHide: () => dispose(actionsDisposable)
I
isidor 已提交
376
		});
I
isidor 已提交
377 378
	}

I
isidor 已提交
379
	private focusActiveEditor(): void {
380
		if (this.list.length && this.editorGroupService.activeGroup) {
I
isidor 已提交
381
			const index = this.getIndex(this.editorGroupService.activeGroup, this.editorGroupService.activeGroup.activeEditor);
I
isidor 已提交
382 383 384
			this.list.setFocus([index]);
			this.list.setSelection([index]);
			this.list.reveal(index);
385 386 387
		} else {
			this.list.setFocus([]);
			this.list.setSelection([]);
I
isidor 已提交
388 389
		}
	}
390

391 392 393
	private onConfigurationChange(event: IConfigurationChangeEvent): void {
		if (event.affectsConfiguration('explorer.openEditors')) {
			this.updateSize();
I
isidor 已提交
394 395
		}

396 397
		// Trigger a 'repaint' when decoration settings change
		if (event.affectsConfiguration('explorer.decorations')) {
I
isidor 已提交
398
			this.listRefreshScheduler.schedule();
I
isidor 已提交
399
		}
400
	}
401

402
	private updateSize(): void {
403
		// Adjust expanded body size
C
Changyu 已提交
404 405
		this.minimumBodySize = this.getMinExpandedBodySize();
		this.maximumBodySize = this.getMaxExpandedBodySize();
I
isidor 已提交
406 407
	}

I
isidor 已提交
408
	private updateDirtyIndicator(): void {
409 410
		let dirty = this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY ? this.textFileService.getDirty().length
			: this.untitledEditorService.getDirty().length;
I
isidor 已提交
411
		if (dirty === 0) {
I
isidor 已提交
412
			dom.addClass(this.dirtyCountElement, 'hidden');
I
isidor 已提交
413 414
		} else {
			this.dirtyCountElement.textContent = nls.localize('dirtyCounter', "{0} unsaved", dirty);
I
isidor 已提交
415
			dom.removeClass(this.dirtyCountElement, 'hidden');
I
isidor 已提交
416 417 418
		}
	}

I
isidor 已提交
419
	private get elementCount(): number {
I
isidor 已提交
420 421
		return this.editorGroupService.groups.map(g => g.count)
			.reduce((first, second) => first + second, this.showGroups ? this.editorGroupService.groups.length : 0);
I
isidor 已提交
422 423
	}

C
Changyu 已提交
424
	private getMaxExpandedBodySize(): number {
I
isidor 已提交
425
		return this.elementCount * OpenEditorsDelegate.ITEM_HEIGHT;
C
Changyu 已提交
426 427 428
	}

	private getMinExpandedBodySize(): number {
429 430 431 432 433
		let visibleOpenEditors = this.configurationService.getValue<number>('explorer.openEditors.visible');
		if (typeof visibleOpenEditors !== 'number') {
			visibleOpenEditors = OpenEditorsView.DEFAULT_VISIBLE_OPEN_EDITORS;
		}

434
		return this.computeMinExpandedBodySize(visibleOpenEditors);
I
isidor 已提交
435
	}
436

437
	private computeMinExpandedBodySize(visibleOpenEditors = OpenEditorsView.DEFAULT_VISIBLE_OPEN_EDITORS): number {
I
isidor 已提交
438
		const itemsToShow = Math.min(Math.max(visibleOpenEditors, 1), this.elementCount);
I
isidor 已提交
439
		return itemsToShow * OpenEditorsDelegate.ITEM_HEIGHT;
440 441
	}

442 443 444 445
	public setStructuralRefreshDelay(delay: number): void {
		this.structuralRefreshDelay = delay;
	}

J
Johannes Rieken 已提交
446
	public getOptimalWidth(): number {
I
isidor 已提交
447
		let parentNode = this.list.getHTMLElement();
M
Matt Bierner 已提交
448
		let childNodes: HTMLElement[] = [].slice.call(parentNode.querySelectorAll('.open-editor > a'));
449

I
isidor 已提交
450 451
		return dom.getLargestChildWidth(parentNode, childNodes);
	}
I
isidor 已提交
452
}
I
isidor 已提交
453 454 455

interface IOpenEditorTemplateData {
	container: HTMLElement;
B
Benjamin Pasero 已提交
456
	root: IResourceLabel;
I
isidor 已提交
457
	actionBar: ActionBar;
458
	actionRunner: OpenEditorActionRunner;
I
isidor 已提交
459 460 461 462 463 464
}

interface IEditorGroupTemplateData {
	root: HTMLElement;
	name: HTMLSpanElement;
	actionBar: ActionBar;
465
	editorGroup: IEditorGroup;
I
isidor 已提交
466 467
}

468 469 470
class OpenEditorActionRunner extends ActionRunner {
	public editor: OpenEditor;

J
Johannes Rieken 已提交
471
	run(action: IAction, context?: any): Promise<void> {
472 473 474 475
		return super.run(action, { groupId: this.editor.groupId, editorIndex: this.editor.editorIndex });
	}
}

J
Joao Moreno 已提交
476
class OpenEditorsDelegate implements IListVirtualDelegate<OpenEditor | IEditorGroup> {
I
isidor 已提交
477 478 479

	public static readonly ITEM_HEIGHT = 22;

480
	getHeight(element: OpenEditor | IEditorGroup): number {
I
isidor 已提交
481 482 483
		return OpenEditorsDelegate.ITEM_HEIGHT;
	}

484
	getTemplateId(element: OpenEditor | IEditorGroup): string {
I
isidor 已提交
485 486
		if (element instanceof OpenEditor) {
			return OpenEditorRenderer.ID;
I
isidor 已提交
487 488
		}

I
isidor 已提交
489
		return EditorGroupRenderer.ID;
I
isidor 已提交
490 491 492
	}
}

J
Joao Moreno 已提交
493
class EditorGroupRenderer implements IListRenderer<IEditorGroup, IEditorGroupTemplateData> {
494
	static readonly ID = 'editorgroup';
I
isidor 已提交
495 496 497

	constructor(
		private keybindingService: IKeybindingService,
498
		private instantiationService: IInstantiationService,
I
isidor 已提交
499 500 501 502 503 504 505 506 507 508 509 510 511 512
	) {
		// noop
	}

	get templateId() {
		return EditorGroupRenderer.ID;
	}

	renderTemplate(container: HTMLElement): IEditorGroupTemplateData {
		const editorGroupTemplate: IEditorGroupTemplateData = Object.create(null);
		editorGroupTemplate.root = dom.append(container, $('.editor-group'));
		editorGroupTemplate.name = dom.append(editorGroupTemplate.root, $('span.name'));
		editorGroupTemplate.actionBar = new ActionBar(container);

513
		const saveAllInGroupAction = this.instantiationService.createInstance(SaveAllInGroupAction, SaveAllInGroupAction.ID, SaveAllInGroupAction.LABEL);
I
isidor 已提交
514
		const saveAllInGroupKey = this.keybindingService.lookupKeybinding(saveAllInGroupAction.id);
R
Rob Lourens 已提交
515
		editorGroupTemplate.actionBar.push(saveAllInGroupAction, { icon: true, label: false, keybinding: saveAllInGroupKey ? saveAllInGroupKey.getLabel() : undefined });
I
isidor 已提交
516 517 518

		const closeGroupAction = this.instantiationService.createInstance(CloseGroupAction, CloseGroupAction.ID, CloseGroupAction.LABEL);
		const closeGroupActionKey = this.keybindingService.lookupKeybinding(closeGroupAction.id);
R
Rob Lourens 已提交
519
		editorGroupTemplate.actionBar.push(closeGroupAction, { icon: true, label: false, keybinding: closeGroupActionKey ? closeGroupActionKey.getLabel() : undefined });
I
isidor 已提交
520 521 522 523

		return editorGroupTemplate;
	}

524
	renderElement(editorGroup: IEditorGroup, index: number, templateData: IEditorGroupTemplateData): void {
525
		templateData.editorGroup = editorGroup;
I
isidor 已提交
526
		templateData.name.textContent = editorGroup.label;
527
		templateData.actionBar.context = { groupId: editorGroup.id };
I
isidor 已提交
528 529 530 531 532 533 534
	}

	disposeTemplate(templateData: IEditorGroupTemplateData): void {
		templateData.actionBar.dispose();
	}
}

J
Joao Moreno 已提交
535
class OpenEditorRenderer implements IListRenderer<OpenEditor, IOpenEditorTemplateData> {
536
	static readonly ID = 'openeditor';
537

I
isidor 已提交
538
	constructor(
539
		private labels: ResourceLabels,
I
isidor 已提交
540 541
		private instantiationService: IInstantiationService,
		private keybindingService: IKeybindingService,
542
		private configurationService: IConfigurationService
I
isidor 已提交
543 544 545 546 547 548 549 550 551 552 553
	) {
		// noop
	}

	get templateId() {
		return OpenEditorRenderer.ID;
	}

	renderTemplate(container: HTMLElement): IOpenEditorTemplateData {
		const editorTemplate: IOpenEditorTemplateData = Object.create(null);
		editorTemplate.container = container;
554 555
		editorTemplate.actionRunner = new OpenEditorActionRunner();
		editorTemplate.actionBar = new ActionBar(container, { actionRunner: editorTemplate.actionRunner });
I
isidor 已提交
556
		container.draggable = true;
I
isidor 已提交
557 558 559

		const closeEditorAction = this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, CloseEditorAction.LABEL);
		const key = this.keybindingService.lookupKeybinding(closeEditorAction.id);
R
Rob Lourens 已提交
560
		editorTemplate.actionBar.push(closeEditorAction, { icon: true, label: false, keybinding: key ? key.getLabel() : undefined });
I
isidor 已提交
561

562
		editorTemplate.root = this.labels.create(container);
I
isidor 已提交
563 564 565 566 567

		return editorTemplate;
	}

	renderElement(editor: OpenEditor, index: number, templateData: IOpenEditorTemplateData): void {
568
		templateData.actionRunner.editor = editor;
I
isidor 已提交
569
		editor.isDirty() ? dom.addClass(templateData.container, 'dirty') : dom.removeClass(templateData.container, 'dirty');
570
		templateData.root.setEditor(editor.editor, {
I
isidor 已提交
571 572
			italic: editor.isPreview(),
			extraClasses: ['open-editor'],
I
isidor 已提交
573 574
			fileDecorations: this.configurationService.getValue<IFilesConfiguration>().explorer.decorations,
			descriptionVerbosity: Verbosity.MEDIUM
I
isidor 已提交
575 576 577 578 579 580
		});
	}

	disposeTemplate(templateData: IOpenEditorTemplateData): void {
		templateData.actionBar.dispose();
		templateData.root.dispose();
581
		templateData.actionRunner.dispose();
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
	}
}

class OpenEditorsDragAndDrop implements IListDragAndDrop<OpenEditor | IEditorGroup> {

	constructor(
		private instantiationService: IInstantiationService,
		private editorGroupService: IEditorGroupsService
	) { }

	@memoize private get dropHandler(): ResourcesDropHandler {
		return this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: false });
	}

	getDragURI(element: OpenEditor | IEditorGroup): string | null {
I
isidor 已提交
597 598 599 600 601 602 603
		if (element instanceof OpenEditor) {
			const resource = element.getResource();
			if (resource) {
				return resource.toString();
			}
		}
		return null;
604 605
	}

I
isidor 已提交
606
	getDragLabel?(elements: (OpenEditor | IEditorGroup)[]): string | undefined {
607 608 609 610 611
		if (elements.length > 1) {
			return String(elements.length);
		}
		const element = elements[0];

I
isidor 已提交
612
		return element instanceof OpenEditor ? withNullAsUndefined(element.editor.getName()) : element.label;
613 614
	}

I
isidor 已提交
615 616 617 618 619 620
	onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
		const items = (data as ElementsDragAndDropData<OpenEditor | IEditorGroup>).elements;
		const resources: URI[] = [];
		if (items) {
			items.forEach(i => {
				if (i instanceof OpenEditor) {
I
isidor 已提交
621 622 623 624
					const resource = i.getResource();
					if (resource) {
						resources.push(resource);
					}
I
isidor 已提交
625 626 627 628 629 630 631 632 633 634
				}
			});
		}

		if (resources.length) {
			// Apply some datatransfer types to allow for dragging the element outside of the application
			this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent);
		}
	}

635
	onDragOver(data: IDragAndDropData, targetElement: OpenEditor | IEditorGroup, targetIndex: number, originalEvent: DragEvent): boolean | IListDragOverReaction {
I
isidor 已提交
636
		if (data instanceof DesktopDragAndDropData && originalEvent.dataTransfer) {
637 638 639 640 641 642 643 644 645 646 647
			const types = originalEvent.dataTransfer.types;
			const typesArray: string[] = [];
			for (let i = 0; i < types.length; i++) {
				typesArray.push(types[i].toLowerCase()); // somehow the types are lowercase
			}

			if (typesArray.indexOf(DataTransfers.FILES.toLowerCase()) === -1 && typesArray.indexOf(CodeDataTransfers.FILES.toLowerCase()) === -1) {
				return false;
			}
		}

648 649 650 651 652 653 654
		return true;
	}

	drop(data: IDragAndDropData, targetElement: OpenEditor | IEditorGroup, targetIndex: number, originalEvent: DragEvent): void {
		const group = targetElement instanceof OpenEditor ? targetElement.group : targetElement;
		const index = targetElement instanceof OpenEditor ? targetElement.group.getIndexOfEditor(targetElement.editor) : 0;

J
Joao Moreno 已提交
655 656
		if (data instanceof ElementsDragAndDropData) {
			const elementsData = data.elements;
657 658 659 660
			elementsData.forEach((oe, offset) => {
				oe.group.moveEditor(oe.editor, group, { index: index + offset, preserveFocus: true });
			});
			this.editorGroupService.activateGroup(group);
J
Joao Moreno 已提交
661 662
		} else {
			this.dropHandler.handleDrop(originalEvent, () => group, () => group.focus(), index);
663
		}
664 665
	}
}