fileActions.ts 56.3 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.
 *--------------------------------------------------------------------------------------------*/

import 'vs/css!./media/fileactions';
J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
8
import * as nls from 'vs/nls';
9
import * as types from 'vs/base/common/types';
I
isidor 已提交
10
import { isWindows, isLinux } from 'vs/base/common/platform';
B
Benjamin Pasero 已提交
11
import { sequence, ITask, always } from 'vs/base/common/async';
12 13
import * as paths from 'vs/base/common/paths';
import * as resources from 'vs/base/common/resources';
14
import { URI } from 'vs/base/common/uri';
J
Johannes Rieken 已提交
15
import { toErrorMessage } from 'vs/base/common/errorMessage';
16
import * as strings from 'vs/base/common/strings';
J
Johannes Rieken 已提交
17 18
import { Action, IAction } from 'vs/base/common/actions';
import { MessageType, IInputValidator } from 'vs/base/browser/ui/inputbox/inputBox';
19
import { ITree, IHighlightEvent } from 'vs/base/parts/tree/browser/tree';
J
Johannes Rieken 已提交
20
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
21
import { VIEWLET_ID } from 'vs/workbench/parts/files/common/files';
22
import { ITextFileService, ITextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
23
import { IFileService, IFileStat, AutoSaveConfiguration } from 'vs/platform/files/common/files';
B
Benjamin Pasero 已提交
24
import { toResource, IUntitledResourceInput } from 'vs/workbench/common/editor';
I
isidor 已提交
25
import { ExplorerItem, Model, NewStatPlaceholder } from 'vs/workbench/parts/files/common/explorerModel';
26 27
import { ExplorerView } from 'vs/workbench/parts/files/electron-browser/views/explorerView';
import { ExplorerViewlet } from 'vs/workbench/parts/files/electron-browser/explorerViewlet';
J
Johannes Rieken 已提交
28 29
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
B
Benjamin Pasero 已提交
30
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
B
Benjamin Pasero 已提交
31
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
I
isidor 已提交
32
import { IInstantiationService, ServicesAccessor, IConstructorSignature2 } from 'vs/platform/instantiation/common/instantiation';
A
Alex Dima 已提交
33
import { ITextModel } from 'vs/editor/common/model';
B
Benjamin Pasero 已提交
34
import { IWindowService } from 'vs/platform/windows/common/windows';
35
import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/parts/files/electron-browser/fileCommands';
M
Max Furman 已提交
36
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
37
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
M
Max Furman 已提交
38 39 40
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
I
isidor 已提交
41
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
I
isidor 已提交
42
import { IListService, ListWidget } from 'vs/platform/list/browser/listService';
43
import { RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
44
import { Schemas } from 'vs/base/common/network';
45
import { IDialogService, IConfirmationResult, IConfirmation, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs';
46
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
47
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
K
Krzysztof Cieslak 已提交
48
import { Constants } from 'vs/editor/common/core/uint';
I
isidor 已提交
49
import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
I
isidor 已提交
50
import { IViewlet } from 'vs/workbench/common/viewlet';
M
Matt Bierner 已提交
51
import { coalesce } from 'vs/base/common/arrays';
M
Max Furman 已提交
52

E
Erich Gamma 已提交
53 54 55 56 57 58
export interface IEditableData {
	action: IAction;
	validator: IInputValidator;
}

export interface IFileViewletState {
59 60 61
	getEditableData(stat: ExplorerItem): IEditableData;
	setEditable(stat: ExplorerItem, editableData: IEditableData): void;
	clearEditable(stat: ExplorerItem): void;
E
Erich Gamma 已提交
62 63
}

I
isidor 已提交
64
export const NEW_FILE_COMMAND_ID = 'explorer.newFile';
I
isidor 已提交
65 66
export const NEW_FILE_LABEL = nls.localize('newFile', "New File");

I
isidor 已提交
67
export const NEW_FOLDER_COMMAND_ID = 'explorer.newFolder';
I
isidor 已提交
68 69
export const NEW_FOLDER_LABEL = nls.localize('newFolder', "New Folder");

I
isidor 已提交
70 71
export const TRIGGER_RENAME_LABEL = nls.localize('rename', "Rename");

I
isidor 已提交
72 73
export const MOVE_FILE_TO_TRASH_LABEL = nls.localize('delete', "Delete");

I
isidor 已提交
74 75 76 77 78 79
export const COPY_FILE_LABEL = nls.localize('copyFile', "Copy");

export const PASTE_FILE_LABEL = nls.localize('pasteFile', "Paste");

export const FileCopiedContext = new RawContextKey<boolean>('fileCopied', false);

80
export class BaseErrorReportingAction extends Action {
E
Erich Gamma 已提交
81 82 83 84

	constructor(
		id: string,
		label: string,
85
		private _notificationService: INotificationService
E
Erich Gamma 已提交
86 87 88 89
	) {
		super(id, label);
	}

90 91
	public get notificationService() {
		return this._notificationService;
E
Erich Gamma 已提交
92 93
	}

94
	protected onError(error: any): void {
95 96
		if (error.message === 'string') {
			error = error.message;
97 98
		}

99
		this._notificationService.error(toErrorMessage(error, false));
100 101
	}

102
	protected onErrorWithRetry(error: any, retry: () => TPromise<any>): void {
103 104 105 106 107 108
		this._notificationService.prompt(Severity.Error, toErrorMessage(error, false),
			[{
				label: nls.localize('retry', "Retry"),
				run: () => retry()
			}]
		);
109 110 111 112
	}
}

export class BaseFileAction extends BaseErrorReportingAction {
I
isidor 已提交
113
	public element: ExplorerItem;
114 115 116 117

	constructor(
		id: string,
		label: string,
I
isidor 已提交
118
		@IFileService protected fileService: IFileService,
119
		@INotificationService notificationService: INotificationService,
I
isidor 已提交
120
		@ITextFileService protected textFileService: ITextFileService
121
	) {
122
		super(id, label, notificationService);
123 124 125 126

		this.enabled = false;
	}

E
Erich Gamma 已提交
127 128 129 130 131
	_isEnabled(): boolean {
		return true;
	}

	_updateEnablement(): void {
I
isidor 已提交
132
		this.enabled = !!(this.fileService && this._isEnabled());
E
Erich Gamma 已提交
133 134 135
	}
}

I
isidor 已提交
136
class TriggerRenameFileAction extends BaseFileAction {
E
Erich Gamma 已提交
137

M
Matt Bierner 已提交
138
	public static readonly ID = 'renameFile';
E
Erich Gamma 已提交
139 140 141 142 143 144

	private tree: ITree;
	private renameAction: BaseRenameAction;

	constructor(
		tree: ITree,
I
isidor 已提交
145
		element: ExplorerItem,
E
Erich Gamma 已提交
146
		@IFileService fileService: IFileService,
147
		@INotificationService notificationService: INotificationService,
E
Erich Gamma 已提交
148 149 150
		@ITextFileService textFileService: ITextFileService,
		@IInstantiationService instantiationService: IInstantiationService
	) {
151
		super(TriggerRenameFileAction.ID, TRIGGER_RENAME_LABEL, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
152 153 154 155 156 157 158

		this.tree = tree;
		this.element = element;
		this.renameAction = instantiationService.createInstance(RenameFileAction, element);
		this._updateEnablement();
	}

159
	public validateFileName(name: string): string {
M
Matt Bierner 已提交
160
		const names: string[] = coalesce(name.split(/[\\/]/));
T
Till Salinger 已提交
161 162 163
		if (names.length > 1) {	// error only occurs on multi-path
			const comparer = isLinux ? strings.compare : strings.compareIgnoreCase;
			if (comparer(names[0], this.element.name) === 0) {
T
Till Salinger 已提交
164
				return nls.localize('renameWhenSourcePathIsParentOfTargetError', "Please use the 'New Folder' or 'New File' command to add children to an existing folder");
T
Till Salinger 已提交
165 166 167
			}
		}

E
Erich Gamma 已提交
168 169 170
		return this.renameAction.validateFileName(this.element.parent, name);
	}

171
	public run(context?: any): TPromise<any> {
E
Erich Gamma 已提交
172
		if (!context) {
173
			return Promise.reject(new Error('No context provided to BaseEnableFileRenameAction.'));
E
Erich Gamma 已提交
174 175
		}

176
		const viewletState = <IFileViewletState>context.viewletState;
E
Erich Gamma 已提交
177
		if (!viewletState) {
178
			return Promise.reject(new Error('Invalid viewlet state provided to BaseEnableFileRenameAction.'));
E
Erich Gamma 已提交
179 180
		}

181
		const stat = <ExplorerItem>context.stat;
E
Erich Gamma 已提交
182
		if (!stat) {
183
			return Promise.reject(new Error('Invalid stat provided to BaseEnableFileRenameAction.'));
E
Erich Gamma 已提交
184 185 186 187 188
		}

		viewletState.setEditable(stat, {
			action: this.renameAction,
			validator: (value) => {
189
				const message = this.validateFileName(value);
E
Erich Gamma 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205

				if (!message) {
					return null;
				}

				return {
					content: message,
					formatContent: true,
					type: MessageType.ERROR
				};
			}
		});

		this.tree.refresh(stat, false).then(() => {
			this.tree.setHighlight(stat);

206
			const unbind = this.tree.onDidChangeHighlight((e: IHighlightEvent) => {
E
Erich Gamma 已提交
207 208
				if (!e.highlight) {
					viewletState.clearEditable(stat);
209
					this.tree.refresh(stat);
A
Alex Dima 已提交
210
					unbind.dispose();
E
Erich Gamma 已提交
211 212
				}
			});
213
		});
M
Matt Bierner 已提交
214

215
		return void 0;
E
Erich Gamma 已提交
216 217 218 219 220 221 222 223
	}
}

export abstract class BaseRenameAction extends BaseFileAction {

	constructor(
		id: string,
		label: string,
I
isidor 已提交
224
		element: ExplorerItem,
E
Erich Gamma 已提交
225
		@IFileService fileService: IFileService,
226
		@INotificationService notificationService: INotificationService,
227
		@ITextFileService textFileService: ITextFileService
E
Erich Gamma 已提交
228
	) {
229
		super(id, label, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
230 231 232 233

		this.element = element;
	}

234 235 236 237
	_isEnabled(): boolean {
		return super._isEnabled() && this.element && !this.element.isReadonly;
	}

238
	public run(context?: any): TPromise<any> {
E
Erich Gamma 已提交
239
		if (!context) {
240
			return Promise.reject(new Error('No context provided to BaseRenameFileAction.'));
E
Erich Gamma 已提交
241 242 243 244
		}

		let name = <string>context.value;
		if (!name) {
245
			return Promise.reject(new Error('No new name provided to BaseRenameFileAction.'));
E
Erich Gamma 已提交
246 247 248 249
		}

		// Automatically trim whitespaces and trailing dots to produce nice file names
		name = getWellFormedFileName(name);
250
		const existingName = getWellFormedFileName(this.element.name);
E
Erich Gamma 已提交
251

252
		// Return early if name is invalid or didn't change
E
Erich Gamma 已提交
253
		if (name === existingName || this.validateFileName(this.element.parent, name)) {
I
isidor 已提交
254
			return Promise.resolve(null);
E
Erich Gamma 已提交
255 256 257
		}

		// Call function and Emit Event through viewer
258
		const promise = this.runAction(name).then(null, (error: any) => {
E
Erich Gamma 已提交
259 260 261 262 263 264
			this.onError(error);
		});

		return promise;
	}

265
	public validateFileName(parent: ExplorerItem, name: string): string {
E
Erich Gamma 已提交
266 267 268 269 270 271 272 273 274 275 276 277
		let source = this.element.name;
		let target = name;

		if (!isLinux) { // allow rename of same file also when case differs (e.g. Game.js => game.js)
			source = source.toLowerCase();
			target = target.toLowerCase();
		}

		if (getWellFormedFileName(source) === getWellFormedFileName(target)) {
			return null;
		}

I
isidor 已提交
278
		return validateFileName(parent, name);
E
Erich Gamma 已提交
279 280
	}

281
	public abstract runAction(newName: string): TPromise<any>;
E
Erich Gamma 已提交
282 283
}

284
class RenameFileAction extends BaseRenameAction {
E
Erich Gamma 已提交
285

M
Matt Bierner 已提交
286
	public static readonly ID = 'workbench.files.action.renameFile';
E
Erich Gamma 已提交
287 288

	constructor(
I
isidor 已提交
289
		element: ExplorerItem,
E
Erich Gamma 已提交
290
		@IFileService fileService: IFileService,
291
		@INotificationService notificationService: INotificationService,
B
Benjamin Pasero 已提交
292
		@ITextFileService textFileService: ITextFileService
E
Erich Gamma 已提交
293
	) {
294
		super(RenameFileAction.ID, nls.localize('rename', "Rename"), element, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
295 296 297 298

		this._updateEnablement();
	}

299
	public runAction(newName: string): TPromise<any> {
B
Benjamin Pasero 已提交
300
		const parentResource = this.element.parent.resource;
301
		const targetResource = resources.joinPath(parentResource, newName);
302

B
Benjamin Pasero 已提交
303
		return this.textFileService.move(this.element.resource, targetResource);
E
Erich Gamma 已提交
304 305 306 307 308
	}
}

/* Base New File/Folder Action */
export class BaseNewAction extends BaseFileAction {
I
isidor 已提交
309
	private presetFolder: ExplorerItem;
E
Erich Gamma 已提交
310 311 312 313 314 315 316 317 318 319
	private tree: ITree;
	private isFile: boolean;
	private renameAction: BaseRenameAction;

	constructor(
		id: string,
		label: string,
		tree: ITree,
		isFile: boolean,
		editableAction: BaseRenameAction,
I
isidor 已提交
320
		element: ExplorerItem,
E
Erich Gamma 已提交
321
		@IFileService fileService: IFileService,
322
		@INotificationService notificationService: INotificationService,
323
		@ITextFileService textFileService: ITextFileService
E
Erich Gamma 已提交
324
	) {
325
		super(id, label, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
326 327 328 329 330 331 332 333 334 335

		if (element) {
			this.presetFolder = element.isDirectory ? element : element.parent;
		}

		this.tree = tree;
		this.isFile = isFile;
		this.renameAction = editableAction;
	}

336
	public run(context?: any): TPromise<any> {
E
Erich Gamma 已提交
337
		if (!context) {
338
			return Promise.reject(new Error('No context provided to BaseNewAction.'));
E
Erich Gamma 已提交
339 340
		}

341
		const viewletState = <IFileViewletState>context.viewletState;
E
Erich Gamma 已提交
342
		if (!viewletState) {
343
			return Promise.reject(new Error('Invalid viewlet state provided to BaseNewAction.'));
E
Erich Gamma 已提交
344 345
		}

I
isidor 已提交
346
		let folder = this.presetFolder;
E
Erich Gamma 已提交
347
		if (!folder) {
I
isidor 已提交
348
			const focus = <ExplorerItem>this.tree.getFocus();
E
Erich Gamma 已提交
349 350 351
			if (focus) {
				folder = focus.isDirectory ? focus : focus.parent;
			} else {
I
isidor 已提交
352
				const input: ExplorerItem | Model = this.tree.getInput();
353
				folder = input instanceof Model ? input.roots[0] : input;
E
Erich Gamma 已提交
354 355 356 357
			}
		}

		if (!folder) {
358
			return Promise.reject(new Error('Invalid parent folder to create.'));
E
Erich Gamma 已提交
359
		}
360
		if (folder.isReadonly) {
361
			return Promise.reject(new Error('Parent folder is readonly.'));
362
		}
363 364
		if (!!folder.getChild(NewStatPlaceholder.NAME)) {
			// Do not allow to creatae a new file/folder while in the process of creating a new file/folder #47606
I
isidor 已提交
365
			return Promise.resolve(new Error('Parent folder is already in the process of creating a file'));
366
		}
E
Erich Gamma 已提交
367 368 369

		return this.tree.reveal(folder, 0.5).then(() => {
			return this.tree.expand(folder).then(() => {
370
				const stat = NewStatPlaceholder.addNewStatPlaceholder(folder, !this.isFile);
E
Erich Gamma 已提交
371 372 373 374 375 376

				this.renameAction.element = stat;

				viewletState.setEditable(stat, {
					action: this.renameAction,
					validator: (value) => {
377
						const message = this.renameAction.validateFileName(folder, value);
E
Erich Gamma 已提交
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395

						if (!message) {
							return null;
						}

						return {
							content: message,
							formatContent: true,
							type: MessageType.ERROR
						};
					}
				});

				return this.tree.refresh(folder).then(() => {
					return this.tree.expand(folder).then(() => {
						return this.tree.reveal(stat, 0.5).then(() => {
							this.tree.setHighlight(stat);

396
							const unbind = this.tree.onDidChangeHighlight((e: IHighlightEvent) => {
E
Erich Gamma 已提交
397 398
								if (!e.highlight) {
									stat.destroy();
399
									this.tree.refresh(folder);
A
Alex Dima 已提交
400
									unbind.dispose();
E
Erich Gamma 已提交
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
								}
							});
						});
					});
				});
			});
		});
	}
}

/* New File */
export class NewFileAction extends BaseNewAction {

	constructor(
		tree: ITree,
I
isidor 已提交
416
		element: ExplorerItem,
E
Erich Gamma 已提交
417
		@IFileService fileService: IFileService,
418
		@INotificationService notificationService: INotificationService,
E
Erich Gamma 已提交
419 420 421
		@ITextFileService textFileService: ITextFileService,
		@IInstantiationService instantiationService: IInstantiationService
	) {
422
		super('explorer.newFile', NEW_FILE_LABEL, tree, true, instantiationService.createInstance(CreateFileAction, element), null, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
423 424 425 426 427 428 429 430 431 432 433

		this.class = 'explorer-action new-file';
		this._updateEnablement();
	}
}

/* New Folder */
export class NewFolderAction extends BaseNewAction {

	constructor(
		tree: ITree,
I
isidor 已提交
434
		element: ExplorerItem,
E
Erich Gamma 已提交
435
		@IFileService fileService: IFileService,
436
		@INotificationService notificationService: INotificationService,
E
Erich Gamma 已提交
437 438 439
		@ITextFileService textFileService: ITextFileService,
		@IInstantiationService instantiationService: IInstantiationService
	) {
440
		super('explorer.newFolder', NEW_FOLDER_LABEL, tree, false, instantiationService.createInstance(CreateFolderAction, element), null, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
441 442 443 444 445 446 447

		this.class = 'explorer-action new-folder';
		this._updateEnablement();
	}
}

/* Create new file from anywhere: Open untitled */
448
export class GlobalNewUntitledFileAction extends Action {
M
Matt Bierner 已提交
449 450
	public static readonly ID = 'workbench.action.files.newUntitledFile';
	public static readonly LABEL = nls.localize('newUntitledFile', "New Untitled File");
E
Erich Gamma 已提交
451 452 453 454

	constructor(
		id: string,
		label: string,
455
		@IEditorService private editorService: IEditorService
E
Erich Gamma 已提交
456 457 458 459
	) {
		super(id, label);
	}

460
	public run(): TPromise<any> {
461
		return this.editorService.openEditor({ options: { pinned: true } } as IUntitledResourceInput); // untitled are always pinned
E
Erich Gamma 已提交
462 463 464 465 466 467
	}
}

/* Create New File/Folder (only used internally by explorerViewer) */
export abstract class BaseCreateAction extends BaseRenameAction {

468
	public validateFileName(parent: ExplorerItem, name: string): string {
E
Erich Gamma 已提交
469
		if (this.element instanceof NewStatPlaceholder) {
I
isidor 已提交
470
			return validateFileName(parent, name);
E
Erich Gamma 已提交
471 472
		}

473
		return super.validateFileName(parent, name);
E
Erich Gamma 已提交
474 475 476 477
	}
}

/* Create New File (only used internally by explorerViewer) */
478
class CreateFileAction extends BaseCreateAction {
E
Erich Gamma 已提交
479

M
Matt Bierner 已提交
480 481
	public static readonly ID = 'workbench.files.action.createFileFromExplorer';
	public static readonly LABEL = nls.localize('createNewFile', "New File");
E
Erich Gamma 已提交
482 483

	constructor(
I
isidor 已提交
484
		element: ExplorerItem,
E
Erich Gamma 已提交
485
		@IFileService fileService: IFileService,
486
		@IEditorService private editorService: IEditorService,
487
		@INotificationService notificationService: INotificationService,
488
		@ITextFileService textFileService: ITextFileService
E
Erich Gamma 已提交
489
	) {
490
		super(CreateFileAction.ID, CreateFileAction.LABEL, element, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
491 492 493 494

		this._updateEnablement();
	}

495
	public runAction(fileName: string): TPromise<any> {
496
		const resource = this.element.parent.resource;
497
		return this.fileService.createFile(resources.joinPath(resource, fileName)).then(stat => {
498 499
			return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
		}, (error) => {
E
Erich Gamma 已提交
500 501 502 503 504 505
			this.onErrorWithRetry(error, () => this.runAction(fileName));
		});
	}
}

/* Create New Folder (only used internally by explorerViewer) */
506
class CreateFolderAction extends BaseCreateAction {
E
Erich Gamma 已提交
507

M
Matt Bierner 已提交
508 509
	public static readonly ID = 'workbench.files.action.createFolderFromExplorer';
	public static readonly LABEL = nls.localize('createNewFolder', "New Folder");
E
Erich Gamma 已提交
510 511

	constructor(
I
isidor 已提交
512
		element: ExplorerItem,
E
Erich Gamma 已提交
513
		@IFileService fileService: IFileService,
514
		@INotificationService notificationService: INotificationService,
515
		@ITextFileService textFileService: ITextFileService
E
Erich Gamma 已提交
516
	) {
517
		super(CreateFolderAction.ID, CreateFolderAction.LABEL, null, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
518 519 520 521

		this._updateEnablement();
	}

522
	public runAction(fileName: string): TPromise<any> {
523
		const resource = this.element.parent.resource;
524
		return this.fileService.createFolder(resources.joinPath(resource, fileName)).then(null, (error) => {
E
Erich Gamma 已提交
525 526 527 528 529
			this.onErrorWithRetry(error, () => this.runAction(fileName));
		});
	}
}

I
isidor 已提交
530
class BaseDeleteFileAction extends BaseFileAction {
531

532
	private static readonly CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete';
533

534
	private skipConfirm: boolean;
E
Erich Gamma 已提交
535 536

	constructor(
537
		private tree: ITree,
I
isidor 已提交
538
		private elements: ExplorerItem[],
539
		private useTrash: boolean,
E
Erich Gamma 已提交
540
		@IFileService fileService: IFileService,
541
		@INotificationService notificationService: INotificationService,
542
		@IDialogService private dialogService: IDialogService,
543
		@ITextFileService textFileService: ITextFileService,
544
		@IConfigurationService private configurationService: IConfigurationService
E
Erich Gamma 已提交
545
	) {
546
		super('moveFileToTrash', MOVE_FILE_TO_TRASH_LABEL, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
547 548

		this.tree = tree;
549
		this.useTrash = useTrash && elements.every(e => !paths.isUNC(e.resource.fsPath)); // on UNC shares there is no trash
E
Erich Gamma 已提交
550 551 552 553

		this._updateEnablement();
	}

554 555 556 557
	_isEnabled(): boolean {
		return super._isEnabled() && this.elements && this.elements.every(e => !e.isReadonly);
	}

558
	public run(): TPromise<any> {
E
Erich Gamma 已提交
559 560 561 562 563 564

		// Remove highlight
		if (this.tree) {
			this.tree.clearHighlight();
		}

565 566 567 568 569 570 571
		let primaryButton: string;
		if (this.useTrash) {
			primaryButton = isWindows ? nls.localize('deleteButtonLabelRecycleBin', "&&Move to Recycle Bin") : nls.localize({ key: 'deleteButtonLabelTrash', comment: ['&& denotes a mnemonic'] }, "&&Move to Trash");
		} else {
			primaryButton = nls.localize({ key: 'deleteButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete");
		}

572
		const distinctElements = resources.distinctParents(this.elements, e => e.resource);
573

574
		// Handle dirty
I
isidor 已提交
575
		let confirmDirtyPromise: TPromise<boolean> = Promise.resolve(true);
576
		const dirty = this.textFileService.getDirty().filter(d => distinctElements.some(e => resources.isEqualOrParent(d, e.resource, !isLinux /* ignorecase */)));
577 578
		if (dirty.length) {
			let message: string;
579
			if (distinctElements.length > 1) {
580
				message = nls.localize('dirtyMessageFilesDelete', "You are deleting files with unsaved changes. Do you want to continue?");
581
			} else if (distinctElements[0].isDirectory) {
582 583 584 585 586
				if (dirty.length === 1) {
					message = nls.localize('dirtyMessageFolderOneDelete', "You are deleting a folder with unsaved changes in 1 file. Do you want to continue?");
				} else {
					message = nls.localize('dirtyMessageFolderDelete', "You are deleting a folder with unsaved changes in {0} files. Do you want to continue?", dirty.length);
				}
587
			} else {
588
				message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?");
589
			}
E
Erich Gamma 已提交
590

591
			confirmDirtyPromise = this.dialogService.confirm({
592 593 594 595
				message,
				type: 'warning',
				detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."),
				primaryButton
596 597
			}).then(res => {
				if (!res.confirmed) {
598 599
					return false;
				}
600

601 602 603
				this.skipConfirm = true; // since we already asked for confirmation
				return this.textFileService.revertAll(dirty).then(() => true);
			});
E
Erich Gamma 已提交
604 605
		}

606
		// Check if file is dirty in editor and save it to avoid data loss
607 608 609 610 611 612
		return confirmDirtyPromise.then(confirmed => {
			if (!confirmed) {
				return null;
			}

			let confirmDeletePromise: TPromise<IConfirmationResult>;
613

614
			// Check if we need to ask for confirmation at all
S
Sandeep Somavarapu 已提交
615
			if (this.skipConfirm || (this.useTrash && this.configurationService.getValue<boolean>(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY) === false)) {
I
isidor 已提交
616
				confirmDeletePromise = Promise.resolve({ confirmed: true } as IConfirmationResult);
617
			}
B
Benjamin Pasero 已提交
618

619 620
			// Confirm for moving to trash
			else if (this.useTrash) {
621 622
				const message = this.getMoveToTrashMessage(distinctElements);

623
				confirmDeletePromise = this.dialogService.confirm({
624
					message,
625
					detail: isWindows ? nls.localize('undoBin', "You can restore from the Recycle Bin.") : nls.localize('undoTrash', "You can restore from the Trash."),
626 627 628 629 630 631
					primaryButton,
					checkbox: {
						label: nls.localize('doNotAskAgain', "Do not ask me again")
					},
					type: 'question'
				});
E
Erich Gamma 已提交
632 633
			}

634 635
			// Confirm for deleting permanently
			else {
636
				const message = this.getDeleteMessage(distinctElements);
637
				confirmDeletePromise = this.dialogService.confirm({
638
					message,
639 640 641 642 643 644
					detail: nls.localize('irreversible', "This action is irreversible!"),
					primaryButton,
					type: 'warning'
				});
			}

645
			return confirmDeletePromise.then(confirmation => {
E
Erich Gamma 已提交
646

647
				// Check for confirmation checkbox
I
isidor 已提交
648
				let updateConfirmSettingsPromise: TPromise<void> = Promise.resolve(void 0);
649
				if (confirmation.confirmed && confirmation.checkboxChecked === true) {
650
					updateConfirmSettingsPromise = this.configurationService.updateValue(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY, false, ConfigurationTarget.USER);
651
				}
E
Erich Gamma 已提交
652

653
				return updateConfirmSettingsPromise.then(() => {
B
Benjamin Pasero 已提交
654

655 656
					// Check for confirmation
					if (!confirmation.confirmed) {
I
isidor 已提交
657
						return Promise.resolve(null);
658 659 660
					}

					// Call function
I
isidor 已提交
661
					const servicePromise = Promise.all(distinctElements.map(e => this.fileService.del(e.resource, { useTrash: this.useTrash, recursive: true }))).then(() => {
662 663
						if (distinctElements[0].parent) {
							this.tree.setFocus(distinctElements[0].parent); // move focus to parent
664 665
						}
					}, (error: any) => {
666 667 668 669 670

						// Handle error to delete file(s) from a modal confirmation dialog
						let errorMessage: string;
						let detailMessage: string;
						let primaryButton: string;
671
						if (this.useTrash) {
672 673 674 675 676 677
							errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?");
							detailMessage = nls.localize('irreversible', "This action is irreversible!");
							primaryButton = nls.localize({ key: 'deletePermanentlyButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Delete Permanently");
						} else {
							errorMessage = toErrorMessage(error, false);
							primaryButton = nls.localize({ key: 'retryButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Retry");
678 679
						}

680
						return this.dialogService.confirm({
681 682 683 684
							message: errorMessage,
							detail: detailMessage,
							type: 'warning',
							primaryButton
685
						}).then(res => {
686 687

							// Focus back to tree
688
							this.tree.domFocus();
689

690
							if (res.confirmed) {
691 692
								if (this.useTrash) {
									this.useTrash = false; // Delete Permanently
693 694
								}

695 696 697
								this.skipConfirm = true;

								return this.run();
698 699
							}

I
isidor 已提交
700
							return Promise.resolve(void 0);
701
						});
702 703 704 705 706
					});

					return servicePromise;
				});
			});
707
		});
E
Erich Gamma 已提交
708
	}
709 710 711

	private getMoveToTrashMessage(distinctElements: ExplorerItem[]): string {
		if (this.containsBothDirectoryAndFile(distinctElements)) {
I
isidor 已提交
712
			return getConfirmMessage(nls.localize('confirmMoveTrashMessageFilesAndDirectories', "Are you sure you want to delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
713 714 715 716
		}

		if (distinctElements.length > 1) {
			if (distinctElements[0].isDirectory) {
I
isidor 已提交
717
				return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultipleDirectories', "Are you sure you want to delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
718
			}
B
Benjamin Pasero 已提交
719 720 721 722 723 724

			return getConfirmMessage(nls.localize('confirmMoveTrashMessageMultiple', "Are you sure you want to delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource));
		}

		if (distinctElements[0].isDirectory) {
			return nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", distinctElements[0].name);
725
		}
B
Benjamin Pasero 已提交
726 727

		return nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", distinctElements[0].name);
728 729 730 731
	}

	private getDeleteMessage(distinctElements: ExplorerItem[]): string {
		if (this.containsBothDirectoryAndFile(distinctElements)) {
I
isidor 已提交
732
			return getConfirmMessage(nls.localize('confirmDeleteMessageFilesAndDirectories', "Are you sure you want to permanently delete the following {0} files/directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
733 734 735 736
		}

		if (distinctElements.length > 1) {
			if (distinctElements[0].isDirectory) {
I
isidor 已提交
737
				return getConfirmMessage(nls.localize('confirmDeleteMessageMultipleDirectories', "Are you sure you want to permanently delete the following {0} directories and their contents?", distinctElements.length), distinctElements.map(e => e.resource));
738
			}
B
Benjamin Pasero 已提交
739 740

			return getConfirmMessage(nls.localize('confirmDeleteMessageMultiple', "Are you sure you want to permanently delete the following {0} files?", distinctElements.length), distinctElements.map(e => e.resource));
741
		}
B
Benjamin Pasero 已提交
742 743 744 745 746 747

		if (distinctElements[0].isDirectory) {
			return nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", distinctElements[0].name);
		}

		return nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", distinctElements[0].name);
748 749
	}

B
Benjamin Pasero 已提交
750
	private containsBothDirectoryAndFile(distinctElements: ExplorerItem[]): boolean {
751 752
		const directories = distinctElements.filter(element => element.isDirectory);
		const files = distinctElements.filter(element => !element.isDirectory);
B
Benjamin Pasero 已提交
753

754 755
		return directories.length > 0 && files.length > 0;
	}
E
Erich Gamma 已提交
756 757
}

758 759
/* Add File */
export class AddFilesAction extends BaseFileAction {
E
Erich Gamma 已提交
760 761 762 763 764

	private tree: ITree;

	constructor(
		tree: ITree,
I
isidor 已提交
765
		element: ExplorerItem,
E
Erich Gamma 已提交
766 767
		clazz: string,
		@IFileService fileService: IFileService,
768
		@IEditorService private editorService: IEditorService,
769
		@IDialogService private dialogService: IDialogService,
770
		@INotificationService notificationService: INotificationService,
771
		@ITextFileService textFileService: ITextFileService
E
Erich Gamma 已提交
772
	) {
773
		super('workbench.files.action.addFile', nls.localize('addFiles', "Add Files"), fileService, notificationService, textFileService);
E
Erich Gamma 已提交
774 775 776 777 778 779 780 781 782 783 784

		this.tree = tree;
		this.element = element;

		if (clazz) {
			this.class = clazz;
		}

		this._updateEnablement();
	}

785
	public run(resourcesToAdd: URI[]): TPromise<any> {
I
isidor 已提交
786
		const addPromise = Promise.resolve(null).then(() => {
787
			if (resourcesToAdd && resourcesToAdd.length > 0) {
E
Erich Gamma 已提交
788

789
				// Find parent to add to
I
isidor 已提交
790
				let targetElement: ExplorerItem;
E
Erich Gamma 已提交
791 792 793
				if (this.element) {
					targetElement = this.element;
				} else {
I
isidor 已提交
794
					const input: ExplorerItem | Model = this.tree.getInput();
I
isidor 已提交
795
					targetElement = this.tree.getFocus() || (input instanceof Model ? input.roots[0] : input);
E
Erich Gamma 已提交
796 797 798 799 800 801 802 803 804 805
				}

				if (!targetElement.isDirectory) {
					targetElement = targetElement.parent;
				}

				// Resolve target to check for name collisions and ask user
				return this.fileService.resolveFile(targetElement.resource).then((targetStat: IFileStat) => {

					// Check for name collisions
806
					const targetNames = new Set<string>();
E
Erich Gamma 已提交
807
					targetStat.children.forEach((child) => {
808
						targetNames.add(isLinux ? child.name : child.name.toLowerCase());
E
Erich Gamma 已提交
809 810
					});

I
isidor 已提交
811
					let overwritePromise: TPromise<IConfirmationResult> = Promise.resolve({ confirmed: true });
812 813
					if (resourcesToAdd.some(resource => {
						return targetNames.has(!resources.hasToIgnoreCase(resource) ? resources.basename(resource) : resources.basename(resource).toLowerCase());
E
Erich Gamma 已提交
814
					})) {
815
						const confirm: IConfirmation = {
E
Erich Gamma 已提交
816 817
							message: nls.localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"),
							detail: nls.localize('irreversible', "This action is irreversible!"),
B
Benjamin Pasero 已提交
818 819
							primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
							type: 'warning'
E
Erich Gamma 已提交
820 821
						};

822
						overwritePromise = this.dialogService.confirm(confirm);
E
Erich Gamma 已提交
823 824
					}

825 826
					return overwritePromise.then(res => {
						if (!res.confirmed) {
827 828 829
							return void 0;
						}

830 831
						// Run add in sequence
						const addPromisesFactory: ITask<TPromise<void>>[] = [];
832
						resourcesToAdd.forEach(resource => {
833
							addPromisesFactory.push(() => {
834
								const sourceFile = resource;
835
								const targetFile = resources.joinPath(targetElement.resource, resources.basename(sourceFile));
836 837

								// if the target exists and is dirty, make sure to revert it. otherwise the dirty contents
838
								// of the target file would replace the contents of the added file. since we already
839
								// confirmed the overwrite before, this is OK.
840
								let revertPromise: Thenable<ITextFileOperationResult> = Promise.resolve(null);
841 842 843
								if (this.textFileService.isDirty(targetFile)) {
									revertPromise = this.textFileService.revertAll([targetFile], { soft: true });
								}
E
Erich Gamma 已提交
844

845
								return revertPromise.then(() => {
846
									const target = resources.joinPath(targetElement.resource, resources.basename(sourceFile));
847
									return this.fileService.copyFile(sourceFile, target, true).then(stat => {
848

849
										// if we only add one file, just open it directly
850
										if (resourcesToAdd.length === 1) {
B
Benjamin Pasero 已提交
851
											this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
852 853 854
										}
									}, error => this.onError(error));
								});
E
Erich Gamma 已提交
855 856 857
							});
						});

858
						return sequence(addPromisesFactory);
859
					});
E
Erich Gamma 已提交
860 861
				});
			}
862 863

			return void 0;
E
Erich Gamma 已提交
864 865
		});

866
		return addPromise.then(() => {
E
Erich Gamma 已提交
867 868 869 870 871 872 873 874 875
			this.tree.clearHighlight();
		}, (error: any) => {
			this.onError(error);
			this.tree.clearHighlight();
		});
	}
}

// Copy File/Folder
I
isidor 已提交
876
class CopyFileAction extends BaseFileAction {
E
Erich Gamma 已提交
877 878 879 880

	private tree: ITree;
	constructor(
		tree: ITree,
I
isidor 已提交
881
		private elements: ExplorerItem[],
E
Erich Gamma 已提交
882
		@IFileService fileService: IFileService,
883
		@INotificationService notificationService: INotificationService,
I
isidor 已提交
884
		@ITextFileService textFileService: ITextFileService,
885 886
		@IContextKeyService contextKeyService: IContextKeyService,
		@IClipboardService private clipboardService: IClipboardService
E
Erich Gamma 已提交
887
	) {
888
		super('filesExplorer.copy', COPY_FILE_LABEL, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
889 890 891 892 893

		this.tree = tree;
		this._updateEnablement();
	}

894
	public run(): TPromise<any> {
E
Erich Gamma 已提交
895

896
		// Write to clipboard as file/folder to copy
897
		this.clipboardService.writeResources(this.elements.map(e => e.resource));
E
Erich Gamma 已提交
898 899 900 901 902 903

		// Remove highlight
		if (this.tree) {
			this.tree.clearHighlight();
		}

904
		this.tree.domFocus();
E
Erich Gamma 已提交
905

I
isidor 已提交
906
		return Promise.resolve(null);
E
Erich Gamma 已提交
907 908 909 910
	}
}

// Paste File/Folder
I
isidor 已提交
911
class PasteFileAction extends BaseFileAction {
E
Erich Gamma 已提交
912

M
Matt Bierner 已提交
913
	public static readonly ID = 'filesExplorer.paste';
E
Erich Gamma 已提交
914 915 916 917 918

	private tree: ITree;

	constructor(
		tree: ITree,
I
isidor 已提交
919
		element: ExplorerItem,
E
Erich Gamma 已提交
920
		@IFileService fileService: IFileService,
921
		@INotificationService notificationService: INotificationService,
E
Erich Gamma 已提交
922
		@ITextFileService textFileService: ITextFileService,
923
		@IEditorService private editorService: IEditorService
E
Erich Gamma 已提交
924
	) {
925
		super(PasteFileAction.ID, PASTE_FILE_LABEL, fileService, notificationService, textFileService);
E
Erich Gamma 已提交
926 927

		this.tree = tree;
I
isidor 已提交
928 929
		this.element = element;
		if (!this.element) {
I
isidor 已提交
930
			const input: ExplorerItem | Model = this.tree.getInput();
I
isidor 已提交
931 932
			this.element = input instanceof Model ? input.roots[0] : input;
		}
E
Erich Gamma 已提交
933 934 935
		this._updateEnablement();
	}

936
	public run(fileToPaste: URI): TPromise<any> {
E
Erich Gamma 已提交
937 938

		// Check if target is ancestor of pasted folder
939 940
		if (this.element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(this.element.resource, fileToPaste, !isLinux /* ignorecase */)) {
			throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder"));
E
Erich Gamma 已提交
941 942
		}

943
		return this.fileService.resolveFile(fileToPaste).then(fileToPasteStat => {
E
Erich Gamma 已提交
944

945 946 947 948 949 950
			// Remove highlight
			if (this.tree) {
				this.tree.clearHighlight();
			}

			// Find target
I
isidor 已提交
951
			let target: ExplorerItem;
952 953 954 955 956 957 958 959 960 961 962 963 964
			if (this.element.resource.toString() === fileToPaste.toString()) {
				target = this.element.parent;
			} else {
				target = this.element.isDirectory ? this.element : this.element.parent;
			}

			const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory });

			// Copy File
			return this.fileService.copyFile(fileToPaste, targetFile).then(stat => {
				if (!stat.isDirectory) {
					return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
				}
E
Erich Gamma 已提交
965

966 967
				return void 0;
			}, error => this.onError(error)).then(() => {
968
				this.tree.domFocus();
969 970 971
			});
		}, error => {
			this.onError(new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile")));
E
Erich Gamma 已提交
972 973 974 975 976 977 978
		});
	}
}

// Duplicate File/Folder
export class DuplicateFileAction extends BaseFileAction {
	private tree: ITree;
I
isidor 已提交
979
	private target: ExplorerItem;
E
Erich Gamma 已提交
980 981 982

	constructor(
		tree: ITree,
I
isidor 已提交
983 984
		fileToDuplicate: ExplorerItem,
		target: ExplorerItem,
E
Erich Gamma 已提交
985
		@IFileService fileService: IFileService,
986
		@IEditorService private editorService: IEditorService,
987
		@INotificationService notificationService: INotificationService,
988
		@ITextFileService textFileService: ITextFileService
E
Erich Gamma 已提交
989
	) {
990
		super('workbench.files.action.duplicateFile', nls.localize('duplicateFile', "Duplicate"), fileService, notificationService, textFileService);
E
Erich Gamma 已提交
991 992

		this.tree = tree;
993 994
		this.element = fileToDuplicate;
		this.target = (target && target.isDirectory) ? target : fileToDuplicate.parent;
E
Erich Gamma 已提交
995 996 997
		this._updateEnablement();
	}

998
	public run(): TPromise<any> {
E
Erich Gamma 已提交
999 1000 1001 1002 1003 1004

		// Remove highlight
		if (this.tree) {
			this.tree.clearHighlight();
		}

1005
		// Copy File
1006
		const result = this.fileService.copyFile(this.element.resource, findValidPasteFileTarget(this.target, { resource: this.element.resource, isDirectory: this.element.isDirectory })).then(stat => {
1007 1008 1009
			if (!stat.isDirectory) {
				return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } });
			}
1010 1011

			return void 0;
1012
		}, error => this.onError(error));
E
Erich Gamma 已提交
1013 1014 1015

		return result;
	}
1016
}
E
Erich Gamma 已提交
1017

I
isidor 已提交
1018
function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean }): URI {
1019
	let name = resources.basenameOrAuthority(fileToPaste.resource);
E
Erich Gamma 已提交
1020

1021
	let candidate = resources.joinPath(targetFolder.resource, name);
1022 1023 1024
	while (true) {
		if (!targetFolder.root.find(candidate)) {
			break;
E
Erich Gamma 已提交
1025 1026
		}

1027
		name = incrementFileName(name, fileToPaste.isDirectory);
1028
		candidate = resources.joinPath(targetFolder.resource, name);
E
Erich Gamma 已提交
1029 1030
	}

1031 1032
	return candidate;
}
E
Erich Gamma 已提交
1033

1034
export function incrementFileName(name: string, isFolder: boolean): string {
1035
	const separators = '[\\.\\-_]';
K
Krzysztof Cieslak 已提交
1036
	const maxNumber = Constants.MAX_SAFE_SMALL_INTEGER;
E
Erich Gamma 已提交
1037

1038
	// file.1.txt=>file.2.txt
1039 1040 1041
	let suffixFileRegex = RegExp('(.*' + separators + ')(\\d+)(\\..*)$');
	if (!isFolder && name.match(suffixFileRegex)) {
		return name.replace(suffixFileRegex, (match, g1?, g2?, g3?) => {
1042 1043 1044 1045
			let number = parseInt(g2);
			return number < maxNumber
				? g1 + strings.pad(number + 1, g2.length) + g3
				: strings.format('{0}{1}.1{2}', g1, g2, g3);
1046
		});
1047
	}
E
Erich Gamma 已提交
1048

1049
	// 1.file.txt=>2.file.txt
1050
	let prefixFileRegex = RegExp('(\\d+)(' + separators + '.*)(\\..*)$');
1051 1052
	if (!isFolder && name.match(prefixFileRegex)) {
		return name.replace(prefixFileRegex, (match, g1?, g2?, g3?) => {
1053 1054 1055 1056
			let number = parseInt(g1);
			return number < maxNumber
				? strings.pad(number + 1, g1.length) + g2 + g3
				: strings.format('{0}{1}.1{2}', g1, g2, g3);
1057
		});
1058
	}
E
Erich Gamma 已提交
1059

1060
	// 1.txt=>2.txt
1061
	let prefixFileNoNameRegex = RegExp('(\\d+)(\\..*)$');
1062 1063 1064 1065 1066 1067 1068 1069 1070
	if (!isFolder && name.match(prefixFileNoNameRegex)) {
		return name.replace(prefixFileNoNameRegex, (match, g1?, g2?) => {
			let number = parseInt(g1);
			return number < maxNumber
				? strings.pad(number + 1, g1.length) + g2
				: strings.format('{0}.1{1}', g1, g2);
		});
	}

1071 1072 1073 1074 1075
	// file.txt=>file.1.txt
	const lastIndexOfDot = name.lastIndexOf('.');
	if (!isFolder && lastIndexOfDot >= 0) {
		return strings.format('{0}.1{1}', name.substr(0, lastIndexOfDot), name.substr(lastIndexOfDot));
	}
E
Erich Gamma 已提交
1076

1077 1078
	// folder.1=>folder.2
	if (isFolder && name.match(/(\d+)$/)) {
1079 1080 1081 1082 1083 1084
		return name.replace(/(\d+)$/, (match: string, ...groups: any[]) => {
			let number = parseInt(groups[0]);
			return number < maxNumber
				? strings.pad(number + 1, groups[0].length)
				: strings.format('{0}.1', groups[0]);
		});
E
Erich Gamma 已提交
1085
	}
1086

1087 1088
	// 1.folder=>2.folder
	if (isFolder && name.match(/^(\d+)/)) {
1089 1090 1091 1092 1093 1094
		return name.replace(/^(\d+)(.*)$/, (match: string, ...groups: any[]) => {
			let number = parseInt(groups[0]);
			return number < maxNumber
				? strings.pad(number + 1, groups[0].length) + groups[1]
				: strings.format('{0}{1}.1', groups[0], groups[1]);
		});
E
Erich Gamma 已提交
1095
	}
1096 1097 1098

	// file/folder=>file.1/folder.1
	return strings.format('{0}.1', name);
E
Erich Gamma 已提交
1099 1100 1101 1102 1103
}

// Global Compare with
export class GlobalCompareResourcesAction extends Action {

M
Matt Bierner 已提交
1104 1105
	public static readonly ID = 'workbench.files.action.compareFileWith';
	public static readonly LABEL = nls.localize('globalCompareFile', "Compare Active File With...");
E
Erich Gamma 已提交
1106 1107 1108 1109 1110

	constructor(
		id: string,
		label: string,
		@IQuickOpenService private quickOpenService: IQuickOpenService,
1111
		@IEditorService private editorService: IEditorService,
1112
		@INotificationService private notificationService: INotificationService,
E
Erich Gamma 已提交
1113 1114 1115 1116
	) {
		super(id, label);
	}

1117
	public run(): TPromise<any> {
1118
		const activeInput = this.editorService.activeEditor;
B
Benjamin Pasero 已提交
1119
		const activeResource = activeInput ? activeInput.getResource() : void 0;
1120
		if (activeResource) {
E
Erich Gamma 已提交
1121

B
Benjamin Pasero 已提交
1122
			// Compare with next editor that opens
1123
			const toDispose = this.editorService.overrideOpenEditor(editor => {
1124 1125 1126 1127 1128 1129

				// Only once!
				toDispose.dispose();

				// Open editor as diff
				const resource = editor.getResource();
B
Benjamin Pasero 已提交
1130
				if (resource) {
1131 1132
					return {
						override: this.editorService.openEditor({
B
Benjamin Pasero 已提交
1133 1134
							leftResource: activeResource,
							rightResource: resource
1135 1136
						}).then(() => void 0)
					};
1137
				}
1138 1139

				return void 0;
B
Benjamin Pasero 已提交
1140
			});
1141

B
Benjamin Pasero 已提交
1142 1143
			// Bring up quick open
			this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } }).then(() => {
1144
				toDispose.dispose(); // make sure to unbind if quick open is closing
E
Erich Gamma 已提交
1145 1146
			});
		} else {
1147
			this.notificationService.info(nls.localize('openFileToCompare', "Open a file first to compare it with another file."));
E
Erich Gamma 已提交
1148 1149
		}

I
isidor 已提交
1150
		return Promise.resolve(true);
E
Erich Gamma 已提交
1151 1152 1153 1154 1155 1156
	}
}

// Refresh Explorer Viewer
export class RefreshViewExplorerAction extends Action {

1157
	constructor(explorerView: ExplorerView, clazz: string) {
B
Benjamin Pasero 已提交
1158
		super('workbench.files.action.refreshFilesExplorer', nls.localize('refresh', "Refresh"), clazz, true, (context: any) => explorerView.refresh());
E
Erich Gamma 已提交
1159 1160 1161
	}
}

1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191
export class ToggleAutoSaveAction extends Action {
	public static readonly ID = 'workbench.action.toggleAutoSave';
	public static readonly LABEL = nls.localize('toggleAutoSave', "Toggle Auto Save");

	constructor(
		id: string,
		label: string,
		@IConfigurationService private configurationService: IConfigurationService
	) {
		super(id, label);
	}

	public run(): TPromise<any> {
		const setting = this.configurationService.inspect('files.autoSave');
		let userAutoSaveConfig = setting.user;
		if (types.isUndefinedOrNull(userAutoSaveConfig)) {
			userAutoSaveConfig = setting.default; // use default if setting not defined
		}

		let newAutoSaveValue: string;
		if ([AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => s === userAutoSaveConfig)) {
			newAutoSaveValue = AutoSaveConfiguration.OFF;
		} else {
			newAutoSaveValue = AutoSaveConfiguration.AFTER_DELAY;
		}

		return this.configurationService.updateValue('files.autoSave', newAutoSaveValue, ConfigurationTarget.USER);
	}
}

1192
export abstract class BaseSaveAllAction extends BaseErrorReportingAction {
E
Erich Gamma 已提交
1193 1194 1195 1196 1197 1198 1199 1200
	private toDispose: IDisposable[];
	private lastIsDirty: boolean;

	constructor(
		id: string,
		label: string,
		@ITextFileService private textFileService: ITextFileService,
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
I
isidor 已提交
1201
		@ICommandService protected commandService: ICommandService,
1202
		@INotificationService notificationService: INotificationService,
E
Erich Gamma 已提交
1203
	) {
1204
		super(id, label, notificationService);
E
Erich Gamma 已提交
1205 1206 1207 1208 1209 1210 1211 1212 1213

		this.toDispose = [];
		this.lastIsDirty = this.textFileService.isDirty();
		this.enabled = this.lastIsDirty;

		this.registerListeners();
	}

	protected abstract includeUntitled(): boolean;
I
isidor 已提交
1214
	protected abstract doRun(context: any): TPromise<any>;
E
Erich Gamma 已提交
1215 1216 1217 1218

	private registerListeners(): void {

		// listen to files being changed locally
1219 1220 1221 1222
		this.toDispose.push(this.textFileService.models.onModelsDirty(e => this.updateEnablement(true)));
		this.toDispose.push(this.textFileService.models.onModelsSaved(e => this.updateEnablement(false)));
		this.toDispose.push(this.textFileService.models.onModelsReverted(e => this.updateEnablement(false)));
		this.toDispose.push(this.textFileService.models.onModelsSaveError(e => this.updateEnablement(true)));
E
Erich Gamma 已提交
1223 1224

		if (this.includeUntitled()) {
B
Benjamin Pasero 已提交
1225
			this.toDispose.push(this.untitledEditorService.onDidChangeDirty(resource => this.updateEnablement(this.untitledEditorService.isDirty(resource))));
E
Erich Gamma 已提交
1226 1227 1228 1229 1230 1231 1232 1233 1234 1235
		}
	}

	private updateEnablement(isDirty: boolean): void {
		if (this.lastIsDirty !== isDirty) {
			this.enabled = this.textFileService.isDirty();
			this.lastIsDirty = this.enabled;
		}
	}

1236 1237 1238 1239 1240 1241 1242
	public run(context?: any): TPromise<boolean> {
		return this.doRun(context).then(() => true, error => {
			this.onError(error);
			return null;
		});
	}

E
Erich Gamma 已提交
1243
	public dispose(): void {
J
Joao Moreno 已提交
1244
		this.toDispose = dispose(this.toDispose);
E
Erich Gamma 已提交
1245 1246 1247 1248 1249 1250 1251

		super.dispose();
	}
}

export class SaveAllAction extends BaseSaveAllAction {

M
Matt Bierner 已提交
1252
	public static readonly ID = 'workbench.action.files.saveAll';
I
isidor 已提交
1253
	public static readonly LABEL = SAVE_ALL_LABEL;
E
Erich Gamma 已提交
1254 1255 1256 1257 1258

	public get class(): string {
		return 'explorer-action save-all';
	}

I
isidor 已提交
1259 1260
	protected doRun(context: any): TPromise<any> {
		return this.commandService.executeCommand(SAVE_ALL_COMMAND_ID);
1261 1262 1263 1264 1265 1266 1267 1268 1269
	}

	protected includeUntitled(): boolean {
		return true;
	}
}

export class SaveAllInGroupAction extends BaseSaveAllAction {

M
Matt Bierner 已提交
1270
	public static readonly ID = 'workbench.files.action.saveAllInGroup';
1271
	public static readonly LABEL = nls.localize('saveAllInGroup', "Save All in Group");
1272 1273 1274 1275 1276

	public get class(): string {
		return 'explorer-action save-all';
	}

I
isidor 已提交
1277
	protected doRun(context: any): TPromise<any> {
I
isidor 已提交
1278
		return this.commandService.executeCommand(SAVE_ALL_IN_GROUP_COMMAND_ID, {}, context);
1279 1280
	}

E
Erich Gamma 已提交
1281 1282 1283 1284 1285
	protected includeUntitled(): boolean {
		return true;
	}
}

I
isidor 已提交
1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299
export class CloseGroupAction extends Action {

	public static readonly ID = 'workbench.files.action.closeGroup';
	public static readonly LABEL = nls.localize('closeGroup', "Close Group");

	constructor(id: string, label: string, @ICommandService private commandService: ICommandService) {
		super(id, label, 'action-close-all-files');
	}

	public run(context?: any): TPromise<any> {
		return this.commandService.executeCommand(CLOSE_EDITORS_AND_GROUP_COMMAND_ID, {}, context);
	}
}

1300 1301
export class FocusFilesExplorer extends Action {

M
Matt Bierner 已提交
1302 1303
	public static readonly ID = 'workbench.files.action.focusFilesExplorer';
	public static readonly LABEL = nls.localize('focusFilesExplorer', "Focus on Files Explorer");
1304 1305 1306 1307 1308 1309 1310 1311 1312 1313

	constructor(
		id: string,
		label: string,
		@IViewletService private viewletService: IViewletService
	) {
		super(id, label);
	}

	public run(): TPromise<any> {
1314
		return this.viewletService.openViewlet(VIEWLET_ID, true).then((viewlet: ExplorerViewlet) => {
1315 1316
			const view = viewlet.getExplorerView();
			if (view) {
1317
				view.setExpanded(true);
1318
				view.getViewer().domFocus();
1319 1320 1321 1322 1323
			}
		});
	}
}

1324 1325
export class ShowActiveFileInExplorer extends Action {

M
Matt Bierner 已提交
1326 1327
	public static readonly ID = 'workbench.files.action.showActiveFileInExplorer';
	public static readonly LABEL = nls.localize('showInExplorer', "Reveal Active File in Side Bar");
1328 1329 1330 1331

	constructor(
		id: string,
		label: string,
1332
		@IEditorService private editorService: IEditorService,
1333
		@INotificationService private notificationService: INotificationService,
I
isidor 已提交
1334
		@ICommandService private commandService: ICommandService
1335 1336 1337 1338 1339
	) {
		super(id, label);
	}

	public run(): TPromise<any> {
1340
		const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true });
1341
		if (resource) {
I
isidor 已提交
1342
			this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource);
1343
		} else {
1344
			this.notificationService.info(nls.localize('openFileToShow', "Open a file first to show it in the explorer"));
1345 1346
		}

I
isidor 已提交
1347
		return Promise.resolve(true);
1348 1349 1350
	}
}

1351 1352
export class CollapseExplorerView extends Action {

M
Matt Bierner 已提交
1353 1354
	public static readonly ID = 'workbench.files.action.collapseExplorerFolders';
	public static readonly LABEL = nls.localize('collapseExplorerFolders', "Collapse Folders in Explorer");
1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370

	constructor(
		id: string,
		label: string,
		@IViewletService private viewletService: IViewletService
	) {
		super(id, label);
	}

	public run(): TPromise<any> {
		return this.viewletService.openViewlet(VIEWLET_ID, true).then((viewlet: ExplorerViewlet) => {
			const explorerView = viewlet.getExplorerView();
			if (explorerView) {
				const viewer = explorerView.getViewer();
				if (viewer) {
					const action = new CollapseAction(viewer, true, null);
1371
					action.run();
1372 1373 1374 1375 1376 1377 1378 1379 1380
					action.dispose();
				}
			}
		});
	}
}

export class RefreshExplorerView extends Action {

M
Matt Bierner 已提交
1381 1382
	public static readonly ID = 'workbench.files.action.refreshFilesExplorer';
	public static readonly LABEL = nls.localize('refreshExplorer', "Refresh Explorer");
1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401

	constructor(
		id: string,
		label: string,
		@IViewletService private viewletService: IViewletService
	) {
		super(id, label);
	}

	public run(): TPromise<any> {
		return this.viewletService.openViewlet(VIEWLET_ID, true).then((viewlet: ExplorerViewlet) => {
			const explorerView = viewlet.getExplorerView();
			if (explorerView) {
				explorerView.refresh();
			}
		});
	}
}

1402 1403
export class ShowOpenedFileInNewWindow extends Action {

M
Matt Bierner 已提交
1404 1405
	public static readonly ID = 'workbench.action.files.showOpenedFileInNewWindow';
	public static readonly LABEL = nls.localize('openFileInNewWindow', "Open Active File in New Window");
1406 1407 1408 1409

	constructor(
		id: string,
		label: string,
1410
		@IEditorService private editorService: IEditorService,
B
Benjamin Pasero 已提交
1411
		@IWindowService private windowService: IWindowService,
1412
		@INotificationService private notificationService: INotificationService
1413 1414 1415 1416 1417
	) {
		super(id, label);
	}

	public run(): TPromise<any> {
1418
		const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: Schemas.file /* todo@remote */ });
1419
		if (fileResource) {
1420
			this.windowService.openWindow([fileResource], { forceNewWindow: true, forceOpenWorkspaceAsFile: true });
1421
		} else {
1422
			this.notificationService.info(nls.localize('openFileToShowInNewWindow', "Open a file first to open in new window"));
1423 1424
		}

I
isidor 已提交
1425
		return Promise.resolve(true);
1426 1427 1428
	}
}

I
isidor 已提交
1429
export function validateFileName(parent: ExplorerItem, name: string): string {
E
Erich Gamma 已提交
1430 1431 1432 1433 1434

	// Produce a well formed file name
	name = getWellFormedFileName(name);

	// Name not provided
1435
	if (!name || name.length === 0 || /^\s+$/.test(name)) {
E
Erich Gamma 已提交
1436 1437 1438
		return nls.localize('emptyFileNameError', "A file or folder name must be provided.");
	}

1439 1440 1441 1442 1443
	// Relative paths only
	if (name[0] === '/' || name[0] === '\\') {
		return nls.localize('fileNameStartsWithSlashError', "A file or folder name cannot start with a slash.");
	}

M
Matt Bierner 已提交
1444
	const names = coalesce(name.split(/[\\/]/));
1445

E
Erich Gamma 已提交
1446
	// Do not allow to overwrite existing file
I
isidor 已提交
1447 1448
	const childExists = !!parent.getChild(name);
	if (childExists) {
1449
		return nls.localize('fileNameExistsError', "A file or folder **{0}** already exists at this location. Please choose a different name.", name);
E
Erich Gamma 已提交
1450 1451
	}

1452
	// Invalid File name
1453
	if (names.some((folderName) => !paths.isValidBasename(folderName))) {
1454
		return nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name));
E
Erich Gamma 已提交
1455 1456
	}

1457
	// Max length restriction (on Windows)
E
Erich Gamma 已提交
1458
	if (isWindows) {
1459 1460
		const fullPathLength = name.length + parent.resource.fsPath.length + 1 /* path segment */;
		if (fullPathLength > 255) {
1461
			return nls.localize('filePathTooLongError', "The name **{0}** results in a path that is too long. Please choose a shorter name.", trimLongName(name));
E
Erich Gamma 已提交
1462 1463 1464 1465 1466 1467
		}
	}

	return null;
}

1468 1469 1470 1471 1472 1473 1474 1475
function trimLongName(name: string): string {
	if (name && name.length > 255) {
		return `${name.substr(0, 255)}...`;
	}

	return name;
}

E
Erich Gamma 已提交
1476 1477 1478 1479 1480
export function getWellFormedFileName(filename: string): string {
	if (!filename) {
		return filename;
	}

1481 1482
	// Trim tabs
	filename = strings.trim(filename, '\t');
E
Erich Gamma 已提交
1483

1484
	// Remove trailing dots, slashes, and spaces
E
Erich Gamma 已提交
1485
	filename = strings.rtrim(filename, '.');
T
Till Salinger 已提交
1486 1487 1488
	filename = strings.rtrim(filename, '/');
	filename = strings.rtrim(filename, '\\');

E
Erich Gamma 已提交
1489 1490 1491
	return filename;
}

M
Max Furman 已提交
1492 1493
export class CompareWithClipboardAction extends Action {

M
Matt Bierner 已提交
1494 1495
	public static readonly ID = 'workbench.files.action.compareWithClipboard';
	public static readonly LABEL = nls.localize('compareWithClipboard', "Compare Active File with Clipboard");
M
Max Furman 已提交
1496

1497
	private static readonly SCHEME = 'clipboardCompare';
M
Max Furman 已提交
1498

B
Benjamin Pasero 已提交
1499
	private registrationDisposal: IDisposable;
M
Max Furman 已提交
1500 1501 1502 1503

	constructor(
		id: string,
		label: string,
1504
		@IEditorService private editorService: IEditorService,
M
Max Furman 已提交
1505 1506
		@IInstantiationService private instantiationService: IInstantiationService,
		@ITextModelService private textModelService: ITextModelService,
1507
		@IFileService private fileService: IFileService
M
Max Furman 已提交
1508 1509 1510 1511 1512 1513 1514
	) {
		super(id, label);

		this.enabled = true;
	}

	public run(): TPromise<any> {
1515
		const resource: URI = toResource(this.editorService.activeEditor, { supportSideBySide: true });
1516
		if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) {
B
Benjamin Pasero 已提交
1517
			if (!this.registrationDisposal) {
1518
				const provider = this.instantiationService.createInstance(ClipboardContentProvider);
B
Benjamin Pasero 已提交
1519 1520 1521
				this.registrationDisposal = this.textModelService.registerTextModelContentProvider(CompareWithClipboardAction.SCHEME, provider);
			}

1522
			const name = resources.basename(resource);
M
Max Furman 已提交
1523 1524
			const editorLabel = nls.localize('clipboardComparisonLabel', "Clipboard ↔ {0}", name);

B
Benjamin Pasero 已提交
1525 1526 1527 1528
			const cleanUp = () => {
				this.registrationDisposal = dispose(this.registrationDisposal);
			};

I
isidor 已提交
1529
			return always(this.editorService.openEditor({ leftResource: resource.with({ scheme: CompareWithClipboardAction.SCHEME }), rightResource: resource, label: editorLabel }), cleanUp);
M
Max Furman 已提交
1530 1531
		}

I
isidor 已提交
1532
		return Promise.resolve(true);
M
Max Furman 已提交
1533 1534 1535 1536 1537
	}

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

B
Benjamin Pasero 已提交
1538
		this.registrationDisposal = dispose(this.registrationDisposal);
M
Max Furman 已提交
1539 1540 1541 1542 1543 1544 1545 1546 1547 1548
	}
}

class ClipboardContentProvider implements ITextModelContentProvider {
	constructor(
		@IClipboardService private clipboardService: IClipboardService,
		@IModeService private modeService: IModeService,
		@IModelService private modelService: IModelService
	) { }

A
Alex Dima 已提交
1549
	provideTextContent(resource: URI): TPromise<ITextModel> {
A
Alex Dima 已提交
1550
		const model = this.modelService.createModel(this.clipboardService.readText(), this.modeService.create('text/plain'), resource);
B
Benjamin Pasero 已提交
1551

I
isidor 已提交
1552
		return Promise.resolve(model);
M
Max Furman 已提交
1553 1554 1555
	}
}

I
isidor 已提交
1556 1557
interface IExplorerContext {
	viewletState: IFileViewletState;
I
isidor 已提交
1558 1559
	stat: ExplorerItem;
	selection: ExplorerItem[];
I
isidor 已提交
1560 1561
}

1562
function getContext(listWidget: ListWidget, viewletService: IViewletService): IExplorerContext {
I
isidor 已提交
1563
	// These commands can only be triggered when explorer viewlet is visible so get it using the active viewlet
1564 1565 1566 1567 1568 1569
	const tree = <ITree>listWidget;
	const stat = tree.getFocus();
	const selection = tree.getSelection();

	// Only respect the selection if user clicked inside it (focus belongs to it)
	return { stat, selection: selection && selection.indexOf(stat) >= 0 ? selection : [], viewletState: (<ExplorerViewlet>viewletService.getActiveViewlet()).getViewletState() };
I
isidor 已提交
1570 1571
}

I
isidor 已提交
1572 1573
// TODO@isidor these commands are calling into actions due to the complex inheritance action structure.
// It should be the other way around, that actions call into commands.
1574
function openExplorerAndRunAction(accessor: ServicesAccessor, constructor: IConstructorSignature2<ITree, ExplorerItem, Action>): TPromise<any> {
1575 1576 1577 1578
	const instantationService = accessor.get(IInstantiationService);
	const listService = accessor.get(IListService);
	const viewletService = accessor.get(IViewletService);
	const activeViewlet = viewletService.getActiveViewlet();
I
isidor 已提交
1579
	let explorerPromise: Thenable<IViewlet> = Promise.resolve(activeViewlet);
1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597
	if (!activeViewlet || activeViewlet.getId() !== VIEWLET_ID) {
		explorerPromise = viewletService.openViewlet(VIEWLET_ID, true);
	}

	return explorerPromise.then((explorer: ExplorerViewlet) => {
		const explorerView = explorer.getExplorerView();
		if (explorerView && explorerView.isVisible() && explorerView.isExpanded()) {
			explorerView.focus();
			const explorerContext = getContext(listService.lastFocusedList, viewletService);
			const action = instantationService.createInstance(constructor, listService.lastFocusedList, explorerContext.stat);

			return action.run(explorerContext);
		}

		return undefined;
	});
}

I
isidor 已提交
1598 1599
CommandsRegistry.registerCommand({
	id: NEW_FILE_COMMAND_ID,
I
isidor 已提交
1600
	handler: (accessor) => {
I
isidor 已提交
1601
		return openExplorerAndRunAction(accessor, NewFileAction);
I
isidor 已提交
1602 1603
	}
});
I
isidor 已提交
1604 1605 1606

CommandsRegistry.registerCommand({
	id: NEW_FOLDER_COMMAND_ID,
I
isidor 已提交
1607
	handler: (accessor) => {
I
isidor 已提交
1608
		return openExplorerAndRunAction(accessor, NewFolderAction);
I
isidor 已提交
1609 1610
	}
});
I
isidor 已提交
1611

I
isidor 已提交
1612
export const renameHandler = (accessor: ServicesAccessor) => {
I
isidor 已提交
1613 1614
	const instantationService = accessor.get(IInstantiationService);
	const listService = accessor.get(IListService);
I
isidor 已提交
1615
	const explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService));
I
isidor 已提交
1616

I
isidor 已提交
1617 1618 1619
	const renameAction = instantationService.createInstance(TriggerRenameFileAction, listService.lastFocusedList, explorerContext.stat);
	return renameAction.run(explorerContext);
};
I
isidor 已提交
1620

1621
export const moveFileToTrashHandler = (accessor: ServicesAccessor) => {
I
isidor 已提交
1622 1623
	const instantationService = accessor.get(IInstantiationService);
	const listService = accessor.get(IListService);
I
isidor 已提交
1624
	const explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService));
1625
	const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
I
isidor 已提交
1626

1627
	const moveFileToTrashAction = instantationService.createInstance(BaseDeleteFileAction, listService.lastFocusedList, stats, true);
1628
	return moveFileToTrashAction.run();
I
isidor 已提交
1629
};
I
isidor 已提交
1630

1631
export const deleteFileHandler = (accessor: ServicesAccessor) => {
I
isidor 已提交
1632 1633
	const instantationService = accessor.get(IInstantiationService);
	const listService = accessor.get(IListService);
I
isidor 已提交
1634
	const explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService));
1635
	const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
I
isidor 已提交
1636

1637
	const deleteFileAction = instantationService.createInstance(BaseDeleteFileAction, listService.lastFocusedList, stats, false);
1638
	return deleteFileAction.run();
I
isidor 已提交
1639
};
I
isidor 已提交
1640

1641
export const copyFileHandler = (accessor: ServicesAccessor) => {
I
isidor 已提交
1642 1643
	const instantationService = accessor.get(IInstantiationService);
	const listService = accessor.get(IListService);
I
isidor 已提交
1644
	const explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService));
I
isidor 已提交
1645
	const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
I
isidor 已提交
1646

I
isidor 已提交
1647
	const copyFileAction = instantationService.createInstance(CopyFileAction, listService.lastFocusedList, stats);
I
isidor 已提交
1648 1649 1650
	return copyFileAction.run();
};

1651
export const pasteFileHandler = (accessor: ServicesAccessor) => {
I
isidor 已提交
1652 1653
	const instantationService = accessor.get(IInstantiationService);
	const listService = accessor.get(IListService);
1654
	const clipboardService = accessor.get(IClipboardService);
I
isidor 已提交
1655
	const explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService));
I
isidor 已提交
1656

I
isidor 已提交
1657
	return Promise.all(resources.distinctParents(clipboardService.readResources(), r => r).map(toCopy => {
I
isidor 已提交
1658 1659 1660
		const pasteFileAction = instantationService.createInstance(PasteFileAction, listService.lastFocusedList, explorerContext.stat);
		return pasteFileAction.run(toCopy);
	}));
I
isidor 已提交
1661
};