explorerViewer.ts 35.5 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
8 9 10 11 12
import nls = require('vs/nls');
import lifecycle = require('vs/base/common/lifecycle');
import objects = require('vs/base/common/objects');
import DOM = require('vs/base/browser/dom');
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
13
import { MIME_BINARY } from 'vs/base/common/mime';
J
Joao Moreno 已提交
14
import { once } from 'vs/base/common/functional';
E
Erich Gamma 已提交
15
import paths = require('vs/base/common/paths');
I
isidor 已提交
16
import resources = require('vs/base/common/resources');
E
Erich Gamma 已提交
17
import errors = require('vs/base/common/errors');
J
Johannes Rieken 已提交
18
import { IAction, ActionRunner as BaseActionRunner, IActionRunner } from 'vs/base/common/actions';
E
Erich Gamma 已提交
19
import comparers = require('vs/base/common/comparers');
J
Johannes Rieken 已提交
20
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
21
import { isMacintosh, isLinux } from 'vs/base/common/platform';
E
Erich Gamma 已提交
22
import glob = require('vs/base/common/glob');
J
Johannes Rieken 已提交
23
import { FileLabel, IFileLabelOptions } from 'vs/workbench/browser/labels';
B
Benjamin Pasero 已提交
24
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
25
import { IFilesConfiguration, SortOrder } from 'vs/workbench/parts/files/common/files';
26
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
27
import { FileOperationError, FileOperationResult, IFileService, FileKind } from 'vs/platform/files/common/files';
B
wip  
Benjamin Pasero 已提交
28
import { ResourceMap } from 'vs/base/common/map';
29
import { DuplicateFileAction, ImportFileAction, IEditableData, IFileViewletState } from 'vs/workbench/parts/files/electron-browser/fileActions';
30 31
import { IDataSource, ITree, IAccessibilityProvider, IRenderer, ContextMenuEvent, ISorter, IFilter, IDragAndDropData, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY, DRAG_OVER_ACCEPT_BUBBLE_UP, DRAG_OVER_ACCEPT_BUBBLE_UP_COPY, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import { DesktopDragAndDropData, ExternalElementsDragAndDropData, SimpleFileResourceDragAndDrop } from 'vs/base/parts/tree/browser/treeDnd';
J
Johannes Rieken 已提交
32
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
33
import { FileStat, NewStatPlaceholder, Model } from 'vs/workbench/parts/files/common/explorerModel';
J
Johannes Rieken 已提交
34 35 36
import { DragMouseEvent, IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IPartService } from 'vs/workbench/services/part/common/partService';
37
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
38
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
J
Johannes Rieken 已提交
39 40 41
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
I
isidor 已提交
42
import { IMessageService, IConfirmation, Severity, IConfirmationResult, getConfirmMessage } from 'vs/platform/message/common/message';
J
Johannes Rieken 已提交
43 44
import { IProgressService } from 'vs/platform/progress/common/progress';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
45
import { KeyCode } from 'vs/base/common/keyCodes';
J
Johannes Rieken 已提交
46 47 48
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
49
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
B
Benjamin Pasero 已提交
50 51
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
52 53
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
B
Benjamin Pasero 已提交
54
import { getPathLabel } from 'vs/base/common/labels';
55
import { extractResources } from 'vs/workbench/browser/editor';
56
import { relative } from 'path';
57
import { DataTransfers } from 'vs/base/browser/dnd';
58
import { distinctParents } from 'vs/base/common/resources';
59
import { WorkbenchTree, multiSelectModifierSettingKey } from 'vs/platform/list/browser/listService';
E
Erich Gamma 已提交
60

B
Benjamin Pasero 已提交
61
export class FileDataSource implements IDataSource {
E
Erich Gamma 已提交
62 63 64 65
	constructor(
		@IProgressService private progressService: IProgressService,
		@IMessageService private messageService: IMessageService,
		@IFileService private fileService: IFileService,
B
Benjamin Pasero 已提交
66
		@IPartService private partService: IPartService
67
	) { }
E
Erich Gamma 已提交
68

69 70 71 72 73
	public getId(tree: ITree, stat: FileStat | Model): string {
		if (stat instanceof Model) {
			return 'model';
		}

I
isidor 已提交
74
		return `${stat.root.resource.toString()}:${stat.getId()}`;
E
Erich Gamma 已提交
75 76
	}

77 78
	public hasChildren(tree: ITree, stat: FileStat | Model): boolean {
		return stat instanceof Model || (stat instanceof FileStat && stat.isDirectory);
E
Erich Gamma 已提交
79 80
	}

81 82 83 84
	public getChildren(tree: ITree, stat: FileStat | Model): TPromise<FileStat[]> {
		if (stat instanceof Model) {
			return TPromise.as(stat.roots);
		}
E
Erich Gamma 已提交
85 86 87

		// Return early if stat is already resolved
		if (stat.isDirectoryResolved) {
A
Alex Dima 已提交
88
			return TPromise.as(stat.children);
E
Erich Gamma 已提交
89 90 91 92 93 94
		}

		// Resolve children and add to fileStat for future lookup
		else {

			// Resolve
95
			const promise = this.fileService.resolveFile(stat.resource, { resolveSingleChildDescendants: true }).then(dirStat => {
E
Erich Gamma 已提交
96 97

				// Convert to view model
I
isidor 已提交
98
				const modelDirStat = FileStat.create(dirStat, stat.root);
E
Erich Gamma 已提交
99

100
				// Add children to folder
E
Erich Gamma 已提交
101 102 103 104 105 106 107 108
				for (let i = 0; i < modelDirStat.children.length; i++) {
					stat.addChild(modelDirStat.children[i]);
				}

				stat.isDirectoryResolved = true;

				return stat.children;
			}, (e: any) => {
109
				this.messageService.show(Severity.Error, e);
E
Erich Gamma 已提交
110 111 112 113

				return []; // we could not resolve any children because of an error
			});

114
			this.progressService.showWhile(promise, this.partService.isCreated() ? 800 : 3200 /* less ugly initial startup */);
E
Erich Gamma 已提交
115 116 117 118 119

			return promise;
		}
	}

I
isidor 已提交
120
	public getParent(tree: ITree, stat: FileStat | Model): TPromise<FileStat> {
E
Erich Gamma 已提交
121
		if (!stat) {
A
Alex Dima 已提交
122
			return TPromise.as(null); // can be null if nothing selected in the tree
E
Erich Gamma 已提交
123 124 125
		}

		// Return if root reached
I
isidor 已提交
126
		if (tree.getInput() === stat) {
A
Alex Dima 已提交
127
			return TPromise.as(null);
E
Erich Gamma 已提交
128 129 130
		}

		// Return if parent already resolved
I
isidor 已提交
131
		if (stat instanceof FileStat && stat.parent) {
A
Alex Dima 已提交
132
			return TPromise.as(stat.parent);
E
Erich Gamma 已提交
133 134 135 136 137
		}

		// We never actually resolve the parent from the disk for performance reasons. It wouldnt make
		// any sense to resolve parent by parent with requests to walk up the chain. Instead, the explorer
		// makes sure to properly resolve a deep path to a specific file and merges the result with the model.
A
Alex Dima 已提交
138
		return TPromise.as(null);
E
Erich Gamma 已提交
139 140 141 142
	}
}

export class FileViewletState implements IFileViewletState {
B
wip  
Benjamin Pasero 已提交
143
	private editableStats: ResourceMap<IEditableData>;
E
Erich Gamma 已提交
144 145

	constructor() {
B
wip  
Benjamin Pasero 已提交
146
		this.editableStats = new ResourceMap<IEditableData>();
E
Erich Gamma 已提交
147 148 149
	}

	public getEditableData(stat: FileStat): IEditableData {
B
wip  
Benjamin Pasero 已提交
150
		return this.editableStats.get(stat.resource);
E
Erich Gamma 已提交
151 152 153 154
	}

	public setEditable(stat: FileStat, editableData: IEditableData): void {
		if (editableData) {
B
wip  
Benjamin Pasero 已提交
155
			this.editableStats.set(stat.resource, editableData);
E
Erich Gamma 已提交
156 157 158 159
		}
	}

	public clearEditable(stat: FileStat): void {
B
wip  
Benjamin Pasero 已提交
160
		this.editableStats.delete(stat.resource);
E
Erich Gamma 已提交
161 162 163
	}
}

164
export class ActionRunner extends BaseActionRunner implements IActionRunner {
E
Erich Gamma 已提交
165 166 167 168 169 170 171 172
	private viewletState: FileViewletState;

	constructor(state: FileViewletState) {
		super();

		this.viewletState = state;
	}

173
	public run(action: IAction, context?: any): TPromise<any> {
E
Erich Gamma 已提交
174 175 176 177
		return super.run(action, { viewletState: this.viewletState });
	}
}

178 179 180 181 182
export interface IFileTemplateData {
	label: FileLabel;
	container: HTMLElement;
}

E
Erich Gamma 已提交
183
// Explorer Renderer
184
export class FileRenderer implements IRenderer {
185

186 187
	private static readonly ITEM_HEIGHT = 22;
	private static readonly FILE_TEMPLATE_ID = 'file';
188

E
Erich Gamma 已提交
189
	private state: FileViewletState;
190 191
	private config: IFilesConfiguration;
	private configListener: IDisposable;
E
Erich Gamma 已提交
192 193 194

	constructor(
		state: FileViewletState,
195
		@IContextViewService private contextViewService: IContextViewService,
B
Benjamin Pasero 已提交
196
		@IInstantiationService private instantiationService: IInstantiationService,
197 198
		@IThemeService private themeService: IThemeService,
		@IConfigurationService private configurationService: IConfigurationService
E
Erich Gamma 已提交
199 200
	) {
		this.state = state;
201
		this.config = this.configurationService.getValue<IFilesConfiguration>();
202 203
		this.configListener = this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration('explorer')) {
204
				this.config = this.configurationService.getValue();
205 206 207 208 209 210
			}
		});
	}

	dispose(): void {
		this.configListener.dispose();
E
Erich Gamma 已提交
211 212
	}

213
	public getHeight(tree: ITree, element: any): number {
214
		return FileRenderer.ITEM_HEIGHT;
E
Erich Gamma 已提交
215 216
	}

217 218 219
	public getTemplateId(tree: ITree, element: any): string {
		return FileRenderer.FILE_TEMPLATE_ID;
	}
220

221 222 223 224 225 226
	public disposeTemplate(tree: ITree, templateId: string, templateData: IFileTemplateData): void {
		templateData.label.dispose();
	}

	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IFileTemplateData {
		const label = this.instantiationService.createInstance(FileLabel, container, void 0);
E
Erich Gamma 已提交
227

228
		return { label, container };
229 230
	}

231 232
	public renderElement(tree: ITree, stat: FileStat, templateId: string, templateData: IFileTemplateData): void {
		const editableData: IEditableData = this.state.getEditableData(stat);
B
Benjamin Pasero 已提交
233

234 235
		// File Label
		if (!editableData) {
236
			templateData.label.element.style.display = 'flex';
237
			const extraClasses = ['explorer-item'];
238 239 240 241
			templateData.label.setFile(stat.resource, {
				hidePath: true,
				fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE,
				extraClasses,
242
				fileDecorations: this.config.explorer.decorations
243
			});
244
		}
245

246 247 248 249 250
		// Input Box
		else {
			templateData.label.element.style.display = 'none';
			this.renderInputBox(templateData.container, tree, stat, editableData);
		}
251 252
	}

253
	private renderInputBox(container: HTMLElement, tree: ITree, stat: FileStat, editableData: IEditableData): void {
B
Benjamin Pasero 已提交
254

255 256
		// Use a file label only for the icon next to the input box
		const label = this.instantiationService.createInstance(FileLabel, container, void 0);
257
		const extraClasses = ['explorer-item', 'explorer-item-edited'];
258 259
		const fileKind = stat.isRoot ? FileKind.ROOT_FOLDER : (stat.isDirectory || (stat instanceof NewStatPlaceholder && stat.isDirectoryPlaceholder())) ? FileKind.FOLDER : FileKind.FILE;
		const labelOptions: IFileLabelOptions = { hidePath: true, hideLabel: true, fileKind, extraClasses };
B
Benjamin Pasero 已提交
260
		label.setFile(stat.resource, labelOptions);
261

262
		// Input field for name
263
		const inputBox = new InputBox(label.element, this.contextViewService, {
J
Joao Moreno 已提交
264
			validationOptions: {
B
Benjamin Pasero 已提交
265
				validation: editableData.validator
266 267
			},
			ariaLabel: nls.localize('fileInputAriaLabel', "Type file name. Press Enter to confirm or Escape to cancel.")
J
Joao Moreno 已提交
268
		});
B
Benjamin Pasero 已提交
269
		const styler = attachInputBoxStyler(inputBox, this.themeService);
E
Erich Gamma 已提交
270

I
isidor 已提交
271
		const parent = resources.dirname(stat.resource);
B
Benjamin Pasero 已提交
272
		inputBox.onDidChange(value => {
I
isidor 已提交
273
			label.setFile(parent.with({ path: paths.join(parent.path, value) }), labelOptions); // update label icon while typing!
B
Benjamin Pasero 已提交
274 275
		});

276 277
		const value = stat.name || '';
		const lastDot = value.lastIndexOf('.');
E
Erich Gamma 已提交
278

J
Joao Moreno 已提交
279 280 281
		inputBox.value = value;
		inputBox.select({ start: 0, end: lastDot > 0 && !stat.isDirectory ? lastDot : value.length });
		inputBox.focus();
E
Erich Gamma 已提交
282

283
		const done = once((commit: boolean, blur: boolean) => {
J
Joao Moreno 已提交
284 285
			tree.clearHighlight();

J
Joao Moreno 已提交
286
			if (commit && inputBox.value) {
287
				editableData.action.run({ value: inputBox.value });
J
Joao Moreno 已提交
288
			}
E
Erich Gamma 已提交
289

J
Joao Moreno 已提交
290
			setTimeout(() => {
291
				if (!blur) { // https://github.com/Microsoft/vscode/issues/20269
292 293
					tree.DOMFocus();
				}
J
Joao Moreno 已提交
294
				lifecycle.dispose(toDispose);
295
				container.removeChild(label.element);
J
Joao Moreno 已提交
296
			}, 0);
J
Joao Moreno 已提交
297
		});
E
Erich Gamma 已提交
298

B
Benjamin Pasero 已提交
299
		const toDispose = [
J
Joao Moreno 已提交
300
			inputBox,
A
Cleanup  
Alex Dima 已提交
301
			DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => {
A
Alexandru Dima 已提交
302
				if (e.equals(KeyCode.Enter)) {
J
Joao Moreno 已提交
303
					if (inputBox.validate()) {
304
						done(true, false);
J
Joao Moreno 已提交
305
					}
A
Alexandru Dima 已提交
306
				} else if (e.equals(KeyCode.Escape)) {
307
					done(false, false);
J
Joao Moreno 已提交
308 309
				}
			}),
310
			DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => {
311
				done(inputBox.isInputValid(), true);
B
Benjamin Pasero 已提交
312
			}),
B
Benjamin Pasero 已提交
313 314
			label,
			styler
J
Joao Moreno 已提交
315
		];
E
Erich Gamma 已提交
316 317 318
	}
}

319
// Explorer Accessibility Provider
B
Benjamin Pasero 已提交
320
export class FileAccessibilityProvider implements IAccessibilityProvider {
321

B
Benjamin Pasero 已提交
322
	public getAriaLabel(tree: ITree, stat: FileStat): string {
323
		return nls.localize('filesExplorerViewerAriaLabel', "{0}, Files Explorer", stat.name);
324 325 326
	}
}

E
Erich Gamma 已提交
327
// Explorer Controller
I
isidor 已提交
328
export class FileController extends DefaultController implements IDisposable {
E
Erich Gamma 已提交
329

330
	private contributedContextMenu: IMenu;
I
isidor 已提交
331
	private toDispose: IDisposable[];
I
isidor 已提交
332
	private previousSelectionRangeStop: FileStat;
333
	private useAltAsMultiSelectModifier: boolean;
334

335 336
	constructor(
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
E
Erich Gamma 已提交
337 338
		@IContextMenuService private contextMenuService: IContextMenuService,
		@ITelemetryService private telemetryService: ITelemetryService,
339
		@IMenuService private menuService: IMenuService,
340 341
		@IContextKeyService contextKeyService: IContextKeyService,
		@IConfigurationService private configurationService: IConfigurationService
E
Erich Gamma 已提交
342
	) {
343
		super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false /* handled via IListService */ });
E
Erich Gamma 已提交
344

345
		this.useAltAsMultiSelectModifier = configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
I
isidor 已提交
346
		this.toDispose = [];
347 348 349 350 351 352 353

		this.registerListeners();
	}

	private registerListeners(): void {
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
354
				this.useAltAsMultiSelectModifier = this.configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
355 356
			}
		}));
E
Erich Gamma 已提交
357 358
	}

359
	public onLeftClick(tree: ITree, stat: FileStat | Model, event: IMouseEvent, origin: string = 'mouse'): boolean {
360 361
		const payload = { origin: origin };
		const isDoubleClick = (origin === 'mouse' && event.detail === 2);
E
Erich Gamma 已提交
362 363 364 365 366 367 368 369 370 371 372 373 374 375

		// Handle Highlight Mode
		if (tree.getHighlight()) {

			// Cancel Event
			event.preventDefault();
			event.stopPropagation();

			tree.clearHighlight(payload);

			return false;
		}

		// Handle root
376
		if (stat instanceof Model) {
E
Erich Gamma 已提交
377 378 379 380 381 382 383
			tree.clearFocus(payload);
			tree.clearSelection(payload);

			return false;
		}

		// Cancel Event
384
		const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
E
Erich Gamma 已提交
385 386 387 388 389 390 391
		if (!isMouseDown) {
			event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
		}
		event.stopPropagation();

		// Set DOM focus
		tree.DOMFocus();
I
isidor 已提交
392 393 394
		if (stat instanceof NewStatPlaceholder) {
			return true;
		}
E
Erich Gamma 已提交
395

396 397
		// Allow to multiselect
		if ((this.useAltAsMultiSelectModifier && event.altKey) || !this.useAltAsMultiSelectModifier && (event.ctrlKey || event.metaKey)) {
I
isidor 已提交
398 399 400 401 402 403 404 405 406
			const selection = tree.getSelection();
			this.previousSelectionRangeStop = undefined;
			if (selection.indexOf(stat) >= 0) {
				tree.setSelection(selection.filter(s => s !== stat));
			} else {
				tree.setSelection(selection.concat(stat));
			}
			tree.setFocus(stat, payload);
		}
E
Erich Gamma 已提交
407 408

		// Allow to unselect
I
isidor 已提交
409 410 411 412 413 414 415 416
		else if (event.shiftKey) {
			const focus = tree.getFocus();
			if (focus) {
				if (this.previousSelectionRangeStop) {
					tree.deselectRange(stat, this.previousSelectionRangeStop);
				}
				tree.selectRange(focus, stat, payload);
				this.previousSelectionRangeStop = stat;
E
Erich Gamma 已提交
417 418 419 420
			}
		}

		// Select, Focus and open files
I
isidor 已提交
421 422 423 424 425
		else {
			// Expand / Collapse
			tree.toggleExpansion(stat, event.altKey);
			this.previousSelectionRangeStop = undefined;

426
			const preserveFocus = !isDoubleClick;
E
Erich Gamma 已提交
427 428 429 430 431 432
			tree.setFocus(stat, payload);

			if (isDoubleClick) {
				event.preventDefault(); // focus moves to editor, we need to prevent default
			}

433
			tree.setSelection([stat], payload);
E
Erich Gamma 已提交
434

435
			if (!stat.isDirectory) {
436 437 438 439 440 441
				let sideBySide = false;
				if (event) {
					sideBySide = this.useAltAsMultiSelectModifier ? (event.ctrlKey || event.metaKey) : event.altKey;
				}

				this.openEditor(stat, { preserveFocus, sideBySide, pinned: isDoubleClick });
E
Erich Gamma 已提交
442 443 444 445 446 447
			}
		}

		return true;
	}

448
	public onContextMenu(tree: WorkbenchTree, stat: FileStat | Model, event: ContextMenuEvent): boolean {
E
Erich Gamma 已提交
449 450 451 452 453 454 455 456 457
		if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
			return false;
		}

		event.preventDefault();
		event.stopPropagation();

		tree.setFocus(stat);

458 459 460 461 462
		if (!this.contributedContextMenu) {
			this.contributedContextMenu = this.menuService.createMenu(MenuId.ExplorerContext, tree.contextKeyService);
			this.toDispose.push(this.contributedContextMenu);
		}

B
Benjamin Pasero 已提交
463
		const anchor = { x: event.posx, y: event.posy };
464
		const selection = tree.getSelection();
E
Erich Gamma 已提交
465 466
		this.contextMenuService.showContextMenu({
			getAnchor: () => anchor,
467
			getActions: () => {
468
				const actions = [];
I
isidor 已提交
469
				fillInActions(this.contributedContextMenu, { arg: stat instanceof FileStat ? stat.resource : undefined, shouldForwardArgs: true }, actions, this.contextMenuService);
470
				return TPromise.as(actions);
471
			},
E
Erich Gamma 已提交
472 473 474 475
			onHide: (wasCancelled?: boolean) => {
				if (wasCancelled) {
					tree.DOMFocus();
				}
476
			},
477
			getActionsContext: () => selection && selection.indexOf(stat) >= 0 ? selection.map((fs: FileStat) => fs.resource) : [stat]
E
Erich Gamma 已提交
478 479 480 481 482
		});

		return true;
	}

483
	public openEditor(stat: FileStat, options: { preserveFocus: boolean; sideBySide: boolean; pinned: boolean; }): void {
E
Erich Gamma 已提交
484
		if (stat && !stat.isDirectory) {
K
kieferrm 已提交
485
			/* __GDPR__
K
kieferrm 已提交
486 487 488 489 490
				"workbenchActionExecuted" : {
					"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
				}
			*/
E
Erich Gamma 已提交
491 492
			this.telemetryService.publicLog('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' });

493
			this.editorService.openEditor({ resource: stat.resource, options }, options.sideBySide).done(null, errors.onUnexpectedError);
E
Erich Gamma 已提交
494 495
		}
	}
I
isidor 已提交
496 497 498 499

	public dispose(): void {
		this.toDispose = dispose(this.toDispose);
	}
E
Erich Gamma 已提交
500 501 502
}

// Explorer Sorter
B
Benjamin Pasero 已提交
503
export class FileSorter implements ISorter {
504 505 506 507
	private toDispose: IDisposable[];
	private sortOrder: SortOrder;

	constructor(
508 509
		@IConfigurationService private configurationService: IConfigurationService,
		@IWorkspaceContextService private contextService: IWorkspaceContextService
510 511 512
	) {
		this.toDispose = [];

513
		this.updateSortOrder();
514 515 516 517 518

		this.registerListeners();
	}

	private registerListeners(): void {
519
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.updateSortOrder()));
520 521
	}

522 523
	private updateSortOrder(): void {
		this.sortOrder = this.configurationService.getValue('explorer.sortOrder') || 'default';
524
	}
E
Erich Gamma 已提交
525

B
Benjamin Pasero 已提交
526
	public compare(tree: ITree, statA: FileStat, statB: FileStat): number {
B
Benjamin Pasero 已提交
527 528 529

		// Do not sort roots
		if (statA.isRoot) {
530
			if (statB.isRoot) {
531
				return this.contextService.getWorkspaceFolder(statA.resource).index - this.contextService.getWorkspaceFolder(statB.resource).index;
532
			}
533

B
Benjamin Pasero 已提交
534 535
			return -1;
		}
536

B
Benjamin Pasero 已提交
537 538 539 540 541
		if (statB.isRoot) {
			return 1;
		}

		// Sort Directories
542
		switch (this.sortOrder) {
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
			case 'type':
				if (statA.isDirectory && !statB.isDirectory) {
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
					return 1;
				}

				if (statA.isDirectory && statB.isDirectory) {
					return comparers.compareFileNames(statA.name, statB.name);
				}

				break;

558 559 560 561
			case 'filesFirst':
				if (statA.isDirectory && !statB.isDirectory) {
					return 1;
				}
B
Benjamin Pasero 已提交
562

563 564 565
				if (statB.isDirectory && !statA.isDirectory) {
					return -1;
				}
B
Benjamin Pasero 已提交
566

567 568
				break;

569 570 571
			case 'mixed':
				break; // not sorting when "mixed" is on

572 573 574 575 576 577 578 579 580
			default: /* 'default', 'modified' */
				if (statA.isDirectory && !statB.isDirectory) {
					return -1;
				}

				if (statB.isDirectory && !statA.isDirectory) {
					return 1;
				}

581
				break;
E
Erich Gamma 已提交
582 583
		}

B
Benjamin Pasero 已提交
584
		// Sort "New File/Folder" placeholders
E
Erich Gamma 已提交
585 586 587 588
		if (statA instanceof NewStatPlaceholder) {
			return -1;
		}

B
Benjamin Pasero 已提交
589
		if (statB instanceof NewStatPlaceholder) {
I
isidor 已提交
590 591 592
			return 1;
		}

B
Benjamin Pasero 已提交
593
		// Sort Files
594 595 596 597 598 599 600 601
		switch (this.sortOrder) {
			case 'type':
				return comparers.compareFileExtensions(statA.name, statB.name);

			case 'modified':
				if (statA.mtime !== statB.mtime) {
					return statA.mtime < statB.mtime ? 1 : -1;
				}
B
Benjamin Pasero 已提交
602 603

				return comparers.compareFileNames(statA.name, statB.name);
604 605 606

			default: /* 'default', 'mixed', 'filesFirst' */
				return comparers.compareFileNames(statA.name, statB.name);
607
		}
E
Erich Gamma 已提交
608
	}
B
Benjamin Pasero 已提交
609 610 611 612

	public dispose(): void {
		this.toDispose = dispose(this.toDispose);
	}
E
Erich Gamma 已提交
613 614 615
}

// Explorer Filter
B
Benjamin Pasero 已提交
616
export class FileFilter implements IFilter {
617

618
	private static readonly MAX_SIBLINGS_FILTER_THRESHOLD = 2000;
619

I
isidor 已提交
620
	private hiddenExpressionPerRoot: Map<string, glob.IExpression>;
B
Benjamin Pasero 已提交
621
	private workspaceFolderChangeListener: IDisposable;
E
Erich Gamma 已提交
622

I
isidor 已提交
623 624 625 626 627
	constructor(
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IConfigurationService private configurationService: IConfigurationService
	) {
		this.hiddenExpressionPerRoot = new Map<string, glob.IExpression>();
B
Benjamin Pasero 已提交
628 629 630 631 632 633

		this.registerListeners();
	}

	public registerListeners(): void {
		this.workspaceFolderChangeListener = this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration());
E
Erich Gamma 已提交
634 635
	}

I
isidor 已提交
636 637
	public updateConfiguration(): boolean {
		let needsRefresh = false;
S
Sandeep Somavarapu 已提交
638
		this.contextService.getWorkspace().folders.forEach(folder => {
639
			const configuration = this.configurationService.getValue<IFilesConfiguration>({ resource: folder.uri });
I
isidor 已提交
640
			const excludesConfig = (configuration && configuration.files && configuration.files.exclude) || Object.create(null);
641
			needsRefresh = needsRefresh || !objects.equals(this.hiddenExpressionPerRoot.get(folder.uri.toString()), excludesConfig);
J
Johannes Rieken 已提交
642
			this.hiddenExpressionPerRoot.set(folder.uri.toString(), objects.deepClone(excludesConfig)); // do not keep the config, as it gets mutated under our hoods
I
isidor 已提交
643
		});
E
Erich Gamma 已提交
644 645 646 647

		return needsRefresh;
	}

B
Benjamin Pasero 已提交
648
	public isVisible(tree: ITree, stat: FileStat): boolean {
E
Erich Gamma 已提交
649 650 651 652
		return this.doIsVisible(stat);
	}

	private doIsVisible(stat: FileStat): boolean {
653
		if (stat instanceof NewStatPlaceholder || stat.isRoot) {
E
Erich Gamma 已提交
654 655 656
			return true; // always visible
		}

657 658 659 660 661 662
		// Workaround for O(N^2) complexity (https://github.com/Microsoft/vscode/issues/9962)
		let siblings = stat.parent && stat.parent.children && stat.parent.children;
		if (siblings && siblings.length > FileFilter.MAX_SIBLINGS_FILTER_THRESHOLD) {
			siblings = void 0;
		}

E
Erich Gamma 已提交
663
		// Hide those that match Hidden Patterns
664
		const siblingsFn = () => siblings && siblings.map(c => c.name);
I
isidor 已提交
665
		const expression = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()) || Object.create(null);
666
		if (glob.match(expression, paths.normalize(relative(stat.root.resource.fsPath, stat.resource.fsPath), true), siblingsFn)) {
E
Erich Gamma 已提交
667 668 669 670 671
			return false; // hidden through pattern
		}

		return true;
	}
B
Benjamin Pasero 已提交
672 673 674 675

	public dispose(): void {
		this.workspaceFolderChangeListener = dispose(this.workspaceFolderChangeListener);
	}
E
Erich Gamma 已提交
676 677 678
}

// Explorer Drag And Drop Controller
679
export class FileDragAndDrop extends SimpleFileResourceDragAndDrop {
680

681
	private static readonly CONFIRM_DND_SETTING_KEY = 'explorer.confirmDragAndDrop';
682

683 684
	private toDispose: IDisposable[];
	private dropEnabled: boolean;
E
Erich Gamma 已提交
685 686 687 688 689

	constructor(
		@IMessageService private messageService: IMessageService,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IFileService private fileService: IFileService,
690
		@IConfigurationService private configurationService: IConfigurationService,
E
Erich Gamma 已提交
691
		@IInstantiationService private instantiationService: IInstantiationService,
692
		@ITextFileService private textFileService: ITextFileService,
693 694
		@IBackupFileService private backupFileService: IBackupFileService,
		@IWindowService private windowService: IWindowService,
B
Benjamin Pasero 已提交
695
		@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService
E
Erich Gamma 已提交
696
	) {
697 698
		super(stat => this.statToResource(stat));

699 700
		this.toDispose = [];

701
		this.updateDropEnablement();
702 703 704 705

		this.registerListeners();
	}

706
	private statToResource(stat: FileStat): URI {
707
		if (stat.isDirectory) {
I
isidor 已提交
708
			return URI.from({ scheme: 'folder', path: stat.resource.path }); // indicates that we are dragging a folder
709 710
		}

711
		return stat.resource;
E
Erich Gamma 已提交
712 713
	}

714
	private registerListeners(): void {
715
		this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => this.updateDropEnablement()));
716
	}
J
Joao Moreno 已提交
717

718 719
	private updateDropEnablement(): void {
		this.dropEnabled = this.configurationService.getValue('explorer.enableDragAndDrop');
J
Joao Moreno 已提交
720 721
	}

B
Benjamin Pasero 已提交
722
	public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
723
		const sources: FileStat[] = data.getData();
I
isidor 已提交
724 725 726 727 728 729 730
		if (sources && sources.length) {
			// When dragging folders, make sure to collapse them to free up some space
			sources.forEach(s => {
				if (s.isDirectory && tree.isExpanded(s)) {
					tree.collapse(s, false);
				}
			});
E
Erich Gamma 已提交
731

I
isidor 已提交
732
			// Apply some datatransfer types to allow for dragging the element outside of the application
733
			originalEvent.dataTransfer.setData(DataTransfers.TEXT, sources.map(fs => fs.resource.scheme === 'file' ? getPathLabel(fs.resource) : fs.resource.toString()).join('\n'));
I
isidor 已提交
734 735 736 737
			if (sources.length === 1) {
				if (!sources[0].isDirectory) {
					originalEvent.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, sources[0].name, sources[0].resource.toString()].join(':'));
				}
E
Erich Gamma 已提交
738

739
			} else {
740
				originalEvent.dataTransfer.setData(DataTransfers.URLS, JSON.stringify(sources.filter(s => !s.isDirectory).map(s => s.resource.toString())));
E
Erich Gamma 已提交
741 742 743 744
			}
		}
	}

I
isidor 已提交
745
	public onDragOver(tree: ITree, data: IDragAndDropData, target: FileStat | Model, originalEvent: DragMouseEvent): IDragOverReaction {
746
		if (!this.dropEnabled) {
747 748 749
			return DRAG_OVER_REJECT;
		}

B
Benjamin Pasero 已提交
750
		const isCopy = originalEvent && ((originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh));
751
		const fromDesktop = data instanceof DesktopDragAndDropData;
E
Erich Gamma 已提交
752 753 754

		// Desktop DND
		if (fromDesktop) {
755
			const dragData = (<DesktopDragAndDropData>data).getData();
E
Erich Gamma 已提交
756

757 758
			const types = dragData.types;
			const typesArray: string[] = [];
E
Erich Gamma 已提交
759 760 761 762
			for (let i = 0; i < types.length; i++) {
				typesArray.push(types[i]);
			}

763
			if (typesArray.length === 0 || !typesArray.some(type => { return type === 'Files'; })) {
B
Benjamin Pasero 已提交
764
				return DRAG_OVER_REJECT;
E
Erich Gamma 已提交
765 766 767 768 769
			}
		}

		// Other-Tree DND
		else if (data instanceof ExternalElementsDragAndDropData) {
B
Benjamin Pasero 已提交
770
			return DRAG_OVER_REJECT;
E
Erich Gamma 已提交
771 772 773 774
		}

		// In-Explorer DND
		else {
775 776 777 778
			if (target instanceof Model) {
				return DRAG_OVER_REJECT;
			}

779
			const sources: FileStat[] = data.getData();
E
Erich Gamma 已提交
780
			if (!Array.isArray(sources)) {
B
Benjamin Pasero 已提交
781
				return DRAG_OVER_REJECT;
E
Erich Gamma 已提交
782 783 784 785 786 787 788
			}

			if (sources.some((source) => {
				if (source instanceof NewStatPlaceholder) {
					return true; // NewStatPlaceholders can not be moved
				}

I
isidor 已提交
789 790 791 792
				if (source.isRoot) {
					return true; // Root folder can not be moved
				}

793
				if (source.resource.toString() === target.resource.toString()) {
E
Erich Gamma 已提交
794 795 796
					return true; // Can not move anything onto itself
				}

I
isidor 已提交
797
				if (!isCopy && resources.dirname(source.resource).toString() === target.resource.toString()) {
E
Erich Gamma 已提交
798 799 800
					return true; // Can not move a file to the same parent unless we copy
				}

I
isidor 已提交
801
				if (resources.isEqualOrParent(target.resource, source.resource, !isLinux /* ignorecase */)) {
E
Erich Gamma 已提交
802 803 804 805 806
					return true; // Can not move a parent folder into one of its children
				}

				return false;
			})) {
B
Benjamin Pasero 已提交
807
				return DRAG_OVER_REJECT;
E
Erich Gamma 已提交
808 809 810
			}
		}

811 812
		// All (target = model)
		if (target instanceof Model) {
813
			return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY(false) : DRAG_OVER_REJECT; // can only drop folders to workspace
E
Erich Gamma 已提交
814 815
		}

816 817 818 819 820 821
		// All (target = file/folder)
		else {
			if (target.isDirectory) {
				return fromDesktop || isCopy ? DRAG_OVER_ACCEPT_BUBBLE_DOWN_COPY(true) : DRAG_OVER_ACCEPT_BUBBLE_DOWN(true);
			}

822
			if (this.contextService.getWorkspace().folders.every(folder => folder.uri.toString() !== target.resource.toString())) {
823 824
				return fromDesktop || isCopy ? DRAG_OVER_ACCEPT_BUBBLE_UP_COPY : DRAG_OVER_ACCEPT_BUBBLE_UP;
			}
E
Erich Gamma 已提交
825 826
		}

B
Benjamin Pasero 已提交
827
		return DRAG_OVER_REJECT;
E
Erich Gamma 已提交
828 829
	}

830
	public drop(tree: ITree, data: IDragAndDropData, target: FileStat | Model, originalEvent: DragMouseEvent): void {
831
		let promise: TPromise<void> = TPromise.as(null);
E
Erich Gamma 已提交
832 833 834

		// Desktop DND (Import file)
		if (data instanceof DesktopDragAndDropData) {
835
			promise = this.handleExternalDrop(tree, data, target, originalEvent);
E
Erich Gamma 已提交
836 837 838 839
		}

		// In-Explorer DND (Move/Copy file)
		else {
840 841 842 843 844 845 846 847 848
			if (target instanceof FileStat) {
				promise = this.handleExplorerDrop(tree, data, target, originalEvent);
			}
		}

		promise.done(null, errors.onUnexpectedError);
	}

	private handleExternalDrop(tree: ITree, data: DesktopDragAndDropData, target: FileStat | Model, originalEvent: DragMouseEvent): TPromise<void> {
849
		const droppedResources = extractResources(originalEvent.browserEvent as DragEvent, true);
E
Erich Gamma 已提交
850

851
		// Check for dropped external files to be folders
852
		return this.fileService.resolveFiles(droppedResources).then(result => {
E
Erich Gamma 已提交
853

854 855 856 857
			// Pass focus to window
			this.windowService.focusWindow();

			// Handle folders by adding to workspace if we are in workspace context
I
isidor 已提交
858
			const folders = result.filter(r => r.success && r.stat.isDirectory).map(result => ({ uri: result.stat.resource }));
859
			if (folders.length > 0) {
B
Benjamin Pasero 已提交
860

861
				// If we are in no-workspace context, ask for confirmation to create a workspace
862
				let confirmedPromise = TPromise.wrap(true);
863
				if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
864
					confirmedPromise = this.messageService.confirm({
865 866 867 868 869
						message: folders.length > 1 ? nls.localize('dropFolders', "Do you want to add the folders to the workspace?") : nls.localize('dropFolder', "Do you want to add the folder to the workspace?"),
						type: 'question',
						primaryButton: folders.length > 1 ? nls.localize('addFolders', "&&Add Folders") : nls.localize('addFolder', "&&Add Folder")
					});
				}
B
Benjamin Pasero 已提交
870

871 872 873 874 875 876 877
				return confirmedPromise.then(confirmed => {
					if (confirmed) {
						return this.workspaceEditingService.addFolders(folders);
					}

					return void 0;
				});
878
			}
E
Erich Gamma 已提交
879

880 881 882
			// Handle dropped files (only support FileStat as target)
			else if (target instanceof FileStat) {
				const importAction = this.instantiationService.createInstance(ImportFileAction, tree, target, null);
883 884

				return importAction.run(droppedResources.map(res => res.resource));
885
			}
886

887 888 889
			return void 0;
		});
	}
E
Erich Gamma 已提交
890

891
	private handleExplorerDrop(tree: ITree, data: IDragAndDropData, target: FileStat, originalEvent: DragMouseEvent): TPromise<void> {
892
		const sources: FileStat[] = distinctParents(data.getData(), s => s.resource);
893
		const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh);
E
Erich Gamma 已提交
894

895 896 897
		let confirmPromise: TPromise<IConfirmationResult>;

		// Handle confirm setting
S
Sandeep Somavarapu 已提交
898
		const confirmDragAndDrop = !isCopy && this.configurationService.getValue<boolean>(FileDragAndDrop.CONFIRM_DND_SETTING_KEY);
899
		if (confirmDragAndDrop) {
900
			confirmPromise = this.messageService.confirmWithCheckbox({
I
isidor 已提交
901 902
				message: sources.length > 1 ? getConfirmMessage(nls.localize('confirmMultiMove', "Are you sure you want to move the following {0} files?", sources.length), sources.map(s => s.resource))
					: nls.localize('confirmMove', "Are you sure you want to move '{0}'?", sources[0].name),
903 904 905
				checkbox: {
					label: nls.localize('doNotAskAgain', "Do not ask me again")
				},
B
Benjamin Pasero 已提交
906 907
				type: 'question',
				primaryButton: nls.localize({ key: 'moveButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Move")
908 909 910 911 912 913 914 915 916
			});
		} else {
			confirmPromise = TPromise.as({ confirmed: true } as IConfirmationResult);
		}

		return confirmPromise.then(confirmation => {

			// Check for confirmation checkbox
			let updateConfirmSettingsPromise: TPromise<void> = TPromise.as(void 0);
917
			if (confirmation.confirmed && confirmation.checkboxChecked === true) {
918
				updateConfirmSettingsPromise = this.configurationService.updateValue(FileDragAndDrop.CONFIRM_DND_SETTING_KEY, false, ConfigurationTarget.USER);
919 920 921 922
			}

			return updateConfirmSettingsPromise.then(() => {
				if (confirmation.confirmed) {
I
isidor 已提交
923
					return TPromise.join(sources.map(source => this.doHandleExplorerDrop(tree, data, source, target, isCopy))).then(() => void 0);
924 925 926 927 928 929 930 931
				}

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

	private doHandleExplorerDrop(tree: ITree, data: IDragAndDropData, source: FileStat, target: FileStat, isCopy: boolean): TPromise<void> {
932
		return tree.expand(target).then(() => {
E
Erich Gamma 已提交
933

934 935 936 937
			// Reuse duplicate action if user copies
			if (isCopy) {
				return this.instantiationService.createInstance(DuplicateFileAction, tree, source, target).run();
			}
E
Erich Gamma 已提交
938

939 940 941 942 943 944 945 946 947
			const dirtyMoved: URI[] = [];

			// Success: load all files that are dirty again to restore their dirty contents
			// Error: discard any backups created during the process
			const onSuccess = () => TPromise.join(dirtyMoved.map(t => this.textFileService.models.loadOrCreate(t)));
			const onError = (error?: Error, showError?: boolean) => {
				if (showError) {
					this.messageService.show(Severity.Error, error);
				}
948

949 950
				return TPromise.join(dirtyMoved.map(d => this.backupFileService.discardResourceBackup(d)));
			};
E
Erich Gamma 已提交
951

952
			// 1. check for dirty files that are being moved and backup to new target
I
isidor 已提交
953
			const dirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, source.resource, !isLinux /* ignorecase */));
954 955
			return TPromise.join(dirty.map(d => {
				let moved: URI;
956

957
				// If the dirty file itself got moved, just reparent it to the target folder
I
isidor 已提交
958 959
				if (source.resource.toString() === d.toString()) {
					moved = target.resource.with({ path: paths.join(target.resource.path, source.name) });
960
				}
E
Erich Gamma 已提交
961

962 963
				// Otherwise, a parent of the dirty resource got moved, so we have to reparent more complicated. Example:
				else {
I
isidor 已提交
964
					moved = target.resource.with({ path: paths.join(target.resource.path, d.path.substr(source.parent.resource.path.length + 1)) });
965
				}
E
Erich Gamma 已提交
966

967
				dirtyMoved.push(moved);
E
Erich Gamma 已提交
968

969
				const model = this.textFileService.models.get(d);
E
Erich Gamma 已提交
970

971
				return this.backupFileService.backupResource(moved, model.createSnapshot(), model.getVersionId());
972
			}))
E
Erich Gamma 已提交
973

974 975
				// 2. soft revert all dirty since we have backed up their contents
				.then(() => this.textFileService.revertAll(dirty, { soft: true /* do not attempt to load content from disk */ }))
E
Erich Gamma 已提交
976

977 978
				// 3.) run the move operation
				.then(() => {
I
isidor 已提交
979
					const targetResource = target.resource.with({ path: paths.join(target.resource.path, source.name) });
980

981
					return this.fileService.moveFile(source.resource, targetResource).then(null, error => {
982

983 984 985 986 987 988 989 990
						// Conflict
						if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) {
							const confirm: IConfirmation = {
								message: nls.localize('confirmOverwriteMessage', "'{0}' already exists in the destination folder. Do you want to replace it?", source.name),
								detail: nls.localize('irreversible', "This action is irreversible!"),
								primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
								type: 'warning'
							};
E
Erich Gamma 已提交
991

992
							// Move with overwrite if the user confirms
993 994 995
							return this.messageService.confirm(confirm).then(confirmed => {
								if (confirmed) {
									const targetDirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, targetResource, !isLinux /* ignorecase */));
E
Erich Gamma 已提交
996

997 998
									// Make sure to revert all dirty in target first to be able to overwrite properly
									return this.textFileService.revertAll(targetDirty, { soft: true /* do not attempt to load content from disk */ }).then(() => {
E
Erich Gamma 已提交
999

1000 1001 1002 1003
										// Then continue to do the move operation
										return this.fileService.moveFile(source.resource, targetResource, true).then(onSuccess, error => onError(error, true));
									});
								}
E
Erich Gamma 已提交
1004

1005 1006
								return onError();
							});
1007 1008 1009 1010 1011 1012 1013 1014 1015
						}

						return onError(error, true);
					});
				})

				// 4.) resolve those that were dirty to load their previous dirty contents from disk
				.then(onSuccess, onError);
		}, errors.onUnexpectedError);
E
Erich Gamma 已提交
1016
	}
1017
}