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

B
Benjamin Pasero 已提交
6
import * as nls from 'vs/nls';
7
import { URI } from 'vs/base/common/uri';
8 9
import * as errors from 'vs/base/common/errors';
import * as objects from 'vs/base/common/objects';
M
Matt Bierner 已提交
10
import { Event, Emitter } from 'vs/base/common/event';
11
import * as platform from 'vs/base/common/platform';
M
Martin Aeschlimann 已提交
12
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
13
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
14
import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent } from 'vs/workbench/services/textfile/common/textfiles';
15
import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
16
import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
17
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
18
import { IFileService, IResolveContentOptions, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files';
J
Johannes Rieken 已提交
19
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
B
Benjamin Pasero 已提交
20
import { Disposable } from 'vs/base/common/lifecycle';
21
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
22
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
J
Johannes Rieken 已提交
23 24
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
25
import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
B
Benjamin Pasero 已提交
26
import { ResourceMap } from 'vs/base/common/map';
B
Benjamin Pasero 已提交
27 28
import { Schemas } from 'vs/base/common/network';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
I
isidor 已提交
29
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
30
import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
B
Benjamin Pasero 已提交
31
import { IModelService } from 'vs/editor/common/services/modelService';
32
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
33
import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename } from 'vs/base/common/resources';
A
Alex Ross 已提交
34
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
35 36 37 38 39
import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { coalesce } from 'vs/base/common/arrays';
import { trim } from 'vs/base/common/strings';
40
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
E
Erich Gamma 已提交
41

42 43 44 45
export interface IBackupResult {
	didBackup: boolean;
}

E
Erich Gamma 已提交
46 47 48 49 50
/**
 * The workbench file service implementation implements the raw file service spec and adds additional methods on top.
 *
 * It also adds diagnostics and logging around file system operations.
 */
51
export class TextFileService extends Disposable implements ITextFileService {
52

53
	_serviceBrand: ServiceIdentifier<any>;
E
Erich Gamma 已提交
54

B
Benjamin Pasero 已提交
55
	private readonly _onAutoSaveConfigurationChange: Emitter<IAutoSaveConfiguration> = this._register(new Emitter<IAutoSaveConfiguration>());
B
Benjamin Pasero 已提交
56
	get onAutoSaveConfigurationChange(): Event<IAutoSaveConfiguration> { return this._onAutoSaveConfigurationChange.event; }
57

B
Benjamin Pasero 已提交
58
	private readonly _onFilesAssociationChange: Emitter<void> = this._register(new Emitter<void>());
B
Benjamin Pasero 已提交
59
	get onFilesAssociationChange(): Event<void> { return this._onFilesAssociationChange.event; }
60

B
Benjamin Pasero 已提交
61 62
	private readonly _onWillMove = this._register(new Emitter<IWillMoveEvent>());
	get onWillMove(): Event<IWillMoveEvent> { return this._onWillMove.event; }
63

B
Benjamin Pasero 已提交
64 65
	private _models: TextFileEditorModelManager;
	private currentFilesAssociationConfig: { [key: string]: string; };
66
	private configuredAutoSaveDelay?: number;
67 68
	private configuredAutoSaveOnFocusChange: boolean;
	private configuredAutoSaveOnWindowChange: boolean;
69
	private configuredHotExit: string;
B
Benjamin Pasero 已提交
70
	private autoSaveContext: IContextKey<string>;
71

E
Erich Gamma 已提交
72
	constructor(
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
		@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
		@IFileService protected readonly fileService: IFileService,
		@IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService,
		@ILifecycleService private readonly lifecycleService: ILifecycleService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IConfigurationService private readonly configurationService: IConfigurationService,
		@IModeService private readonly modeService: IModeService,
		@IModelService private readonly modelService: IModelService,
		@IWindowService private readonly windowService: IWindowService,
		@IEnvironmentService private readonly environmentService: IEnvironmentService,
		@INotificationService private readonly notificationService: INotificationService,
		@IBackupFileService private readonly backupFileService: IBackupFileService,
		@IWindowsService private readonly windowsService: IWindowsService,
		@IHistoryService private readonly historyService: IHistoryService,
		@IContextKeyService contextKeyService: IContextKeyService,
		@IDialogService private readonly dialogService: IDialogService,
		@IFileDialogService private readonly fileDialogService: IFileDialogService,
		@IEditorService private readonly editorService: IEditorService
E
Erich Gamma 已提交
91
	) {
B
Benjamin Pasero 已提交
92
		super();
93

94
		this._models = instantiationService.createInstance(TextFileEditorModelManager);
I
isidor 已提交
95
		this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService);
96

97
		const configuration = configurationService.getValue<IFilesConfiguration>();
98 99
		this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations;

100
		this.onFilesConfigurationChange(configuration);
101

102
		this.registerListeners();
E
Erich Gamma 已提交
103 104
	}

B
Benjamin Pasero 已提交
105
	get models(): ITextFileEditorModelManager {
106 107 108
		return this._models;
	}

109 110 111
	resolveTextContent(resource: URI, options?: IResolveContentOptions): Promise<IRawTextContent> {
		return this.fileService.resolveStreamContent(resource, options).then(streamContent => {
			return createTextBufferFactoryFromStream(streamContent.value).then(res => {
112
				return {
113 114 115 116 117 118
					resource: streamContent.resource,
					name: streamContent.name,
					mtime: streamContent.mtime,
					etag: streamContent.etag,
					encoding: streamContent.encoding,
					isReadonly: streamContent.isReadonly,
119
					size: streamContent.size,
120
					value: res
121
				};
122 123 124 125
			});
		});
	}

M
Matt Bierner 已提交
126
	promptForPath(resource: URI, defaultUri: URI): Promise<URI | undefined> {
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147

		// Help user to find a name for the file by opening it first
		return this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true, } }).then(() => {
			return this.fileDialogService.showSaveDialog(this.getSaveDialogOptions(defaultUri));
		});
	}

	private getSaveDialogOptions(defaultUri: URI): ISaveDialogOptions {
		const options: ISaveDialogOptions = {
			defaultUri,
			title: nls.localize('saveAsTitle', "Save As")
		};

		// Filters are only enabled on Windows where they work properly
		if (!platform.isWindows) {
			return options;
		}

		interface IFilter { name: string; extensions: string[]; }

		// Build the file filter by using our known languages
M
Matt Bierner 已提交
148
		const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined;
149
		let matchingFilter: IFilter | undefined;
150 151 152 153 154 155 156 157 158 159 160 161 162
		const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => {
			const extensions = this.modeService.getExtensions(languageName);
			if (!extensions || !extensions.length) {
				return null;
			}

			const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) };

			if (ext && extensions.indexOf(ext) >= 0) {
				matchingFilter = filter;

				return null; // matching filter will be added last to the top
			}
A
Alex Dima 已提交
163

164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
			return filter;
		}));

		// Filters are a bit weird on Windows, based on having a match or not:
		// Match: we put the matching filter first so that it shows up selected and the all files last
		// No match: we put the all files filter first
		const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] };
		if (matchingFilter) {
			filters.unshift(matchingFilter);
			filters.unshift(allFilesFilter);
		} else {
			filters.unshift(allFilesFilter);
		}

		// Allow to save file without extension
		filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] });

		options.filters = filters;

		return options;
	}
185

186 187 188 189 190 191 192 193 194 195
	confirmSave(resources?: URI[]): Promise<ConfirmResult> {
		if (this.environmentService.isExtensionDevelopment) {
			return Promise.resolve(ConfirmResult.DONT_SAVE); // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests)
		}

		const resourcesToConfirm = this.getDirty(resources);
		if (resourcesToConfirm.length === 0) {
			return Promise.resolve(ConfirmResult.DONT_SAVE);
		}

196
		const message = resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0]))
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
			: getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm);

		const buttons: string[] = [
			resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"),
			nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"),
			nls.localize('cancel', "Cancel")
		];

		return this.dialogService.show(Severity.Warning, message, buttons, {
			cancelId: 2,
			detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.")
		}).then(index => {
			switch (index) {
				case 0: return ConfirmResult.SAVE;
				case 1: return ConfirmResult.DONT_SAVE;
				default: return ConfirmResult.CANCEL;
			}
		});
	}
216

217 218
	confirmOverwrite(resource: URI): Promise<boolean> {
		const confirm: IConfirmation = {
219 220
			message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)),
			detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))),
221 222 223 224 225 226
			primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"),
			type: 'warning'
		};

		return this.dialogService.confirm(confirm).then(result => result.confirmed);
	}
B
Benjamin Pasero 已提交
227

228
	private registerListeners(): void {
229

230
		// Lifecycle
231
		this.lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(event.reason)));
232 233
		this.lifecycleService.onShutdown(this.dispose, this);

234
		// Files configuration changes
B
Benjamin Pasero 已提交
235
		this._register(this.configurationService.onDidChangeConfiguration(e => {
236
			if (e.affectsConfiguration('files')) {
237
				this.onFilesConfigurationChange(this.configurationService.getValue<IFilesConfiguration>());
238 239
			}
		}));
240 241
	}

J
Johannes Rieken 已提交
242
	private beforeShutdown(reason: ShutdownReason): boolean | Promise<boolean> {
B
Benjamin Pasero 已提交
243

244 245 246 247
		// Dirty files need treatment on shutdown
		const dirty = this.getDirty();
		if (dirty.length) {

248
			// If auto save is enabled, save all files and then check again for dirty files
249
			// We DO NOT run any save participant if we are in the shutdown phase for performance reasons
250
			if (this.getAutoSaveMode() !== AutoSaveMode.OFF) {
B
Benjamin Pasero 已提交
251
				return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => {
B
Benjamin Pasero 已提交
252

B
Benjamin Pasero 已提交
253 254 255 256 257
					// If we still have dirty files, we either have untitled ones or files that cannot be saved
					const remainingDirty = this.getDirty();
					if (remainingDirty.length) {
						return this.handleDirtyBeforeShutdown(remainingDirty, reason);
					}
258

M
Matt Bierner 已提交
259
					return false;
B
Benjamin Pasero 已提交
260 261
				});
			}
B
Benjamin Pasero 已提交
262

B
Benjamin Pasero 已提交
263 264 265
			// Auto save is not enabled
			return this.handleDirtyBeforeShutdown(dirty, reason);
		}
266

B
Benjamin Pasero 已提交
267 268 269
		// No dirty files: no veto
		return this.noVeto({ cleanUpBackups: true });
	}
270

J
Johannes Rieken 已提交
271
	private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise<boolean> {
272

B
Benjamin Pasero 已提交
273 274 275 276 277
		// If hot exit is enabled, backup dirty files and allow to exit without confirmation
		if (this.isHotExitEnabled) {
			return this.backupBeforeShutdown(dirty, this.models, reason).then(result => {
				if (result.didBackup) {
					return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful)
278
				}
279

B
Benjamin Pasero 已提交
280 281 282 283 284 285 286
				// since a backup did not happen, we have to confirm for the dirty files now
				return this.confirmBeforeShutdown();
			}, errors => {
				const firstError = errors[0];
				this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", firstError.message));

				return true; // veto, the backups failed
287
			});
288 289
		}

B
Benjamin Pasero 已提交
290 291
		// Otherwise just confirm from the user what to do with the dirty files
		return this.confirmBeforeShutdown();
292 293
	}

J
Johannes Rieken 已提交
294
	private backupBeforeShutdown(dirtyToBackup: URI[], textFileEditorModelManager: ITextFileEditorModelManager, reason: ShutdownReason): Promise<IBackupResult> {
M
Matt Bierner 已提交
295
		return this.windowsService.getWindowCount().then<IBackupResult>(windowCount => {
296 297 298 299 300 301

			// When quit is requested skip the confirm callback and attempt to backup all workspaces.
			// When quit is not requested the confirm callback should be shown when the window being
			// closed is the only VS Code window open, except for on Mac where hot exit is only
			// ever activated when quit is requested.

302
			let doBackup: boolean | undefined;
303 304
			switch (reason) {
				case ShutdownReason.CLOSE:
305
					if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
D
Daniel Imms 已提交
306
						doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
307
					} else if (windowCount > 1 || platform.isMacintosh) {
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322
						doBackup = false; // do not backup if a window is closed that does not cause quitting of the application
					} else {
						doBackup = true; // backup if last window is closed on win/linux where the application quits right after
					}
					break;

				case ShutdownReason.QUIT:
					doBackup = true; // backup because next start we restore all backups
					break;

				case ShutdownReason.RELOAD:
					doBackup = true; // backup because after window reload, backups restore
					break;

				case ShutdownReason.LOAD:
323
					if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
324 325 326 327
						doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured
					} else {
						doBackup = false; // do not backup because we are switching contexts
					}
328 329 330 331
					break;
			}

			if (!doBackup) {
B
Benjamin Pasero 已提交
332
				return { didBackup: false };
333 334 335 336 337 338 339
			}

			// Backup
			return this.backupAll(dirtyToBackup, textFileEditorModelManager).then(() => { return { didBackup: true }; });
		});
	}

J
Johannes Rieken 已提交
340
	private backupAll(dirtyToBackup: URI[], textFileEditorModelManager: ITextFileEditorModelManager): Promise<void> {
341 342 343 344 345

		// split up between files and untitled
		const filesToBackup: ITextFileEditorModel[] = [];
		const untitledToBackup: URI[] = [];
		dirtyToBackup.forEach(s => {
346
			if (this.fileService.canHandleResource(s)) {
M
Matt Bierner 已提交
347 348 349 350
				const model = textFileEditorModelManager.get(s);
				if (model) {
					filesToBackup.push(model);
				}
351
			} else if (s.scheme === Schemas.untitled) {
352 353 354 355 356 357 358
				untitledToBackup.push(s);
			}
		});

		return this.doBackupAll(filesToBackup, untitledToBackup);
	}

J
Johannes Rieken 已提交
359
	private doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise<void> {
M
Matt Bierner 已提交
360 361 362 363 364 365 366
		const promises = dirtyFileModels.map(model => {
			const snapshot = model.createSnapshot();
			if (snapshot) {
				return this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId());
			}
			return Promise.resolve();
		});
367 368

		// Handle file resources first
M
Matt Bierner 已提交
369
		return Promise.all(promises).then(results => {
370 371

			// Handle untitled resources
372 373 374
			const untitledModelPromises = untitledResources
				.filter(untitled => this.untitledEditorService.exists(untitled))
				.map(untitled => this.untitledEditorService.loadOrCreate({ resource: untitled }));
375

B
Benjamin Pasero 已提交
376
			return Promise.all(untitledModelPromises).then(untitledModels => {
377
				const untitledBackupPromises = untitledModels.map(model => {
M
Matt Bierner 已提交
378 379 380 381 382
					const snapshot = model.createSnapshot();
					if (snapshot) {
						return this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId());
					}
					return Promise.resolve();
383 384
				});

R
Rob Lourens 已提交
385
				return Promise.all(untitledBackupPromises).then(() => undefined);
386 387 388 389
			});
		});
	}

J
Johannes Rieken 已提交
390
	private confirmBeforeShutdown(): boolean | Promise<boolean> {
391
		return this.confirmSave().then(confirm => {
392

393 394 395 396 397 398
			// Save
			if (confirm === ConfirmResult.SAVE) {
				return this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }).then(result => {
					if (result.results.some(r => !r.success)) {
						return true; // veto if some saves failed
					}
399

400 401 402
					return this.noVeto({ cleanUpBackups: true });
				});
			}
403

404 405
			// Don't Save
			else if (confirm === ConfirmResult.DONT_SAVE) {
406

407 408 409
				// Make sure to revert untitled so that they do not restore
				// see https://github.com/Microsoft/vscode/issues/29572
				this.untitledEditorService.revertAll();
410

411 412
				return this.noVeto({ cleanUpBackups: true });
			}
413

414 415 416 417
			// Cancel
			else if (confirm === ConfirmResult.CANCEL) {
				return true; // veto
			}
418

M
Matt Bierner 已提交
419
			return false;
420
		});
421 422
	}

J
Johannes Rieken 已提交
423
	private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise<boolean> {
B
Benjamin Pasero 已提交
424
		if (!options.cleanUpBackups) {
B
Benjamin Pasero 已提交
425 426 427
			return false;
		}

428 429 430 431
		if (this.lifecycleService.phase < LifecyclePhase.Restored) {
			return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them
		}

432 433 434
		return this.cleanupBackupsBeforeShutdown().then(() => false, () => false);
	}

J
Johannes Rieken 已提交
435
	protected cleanupBackupsBeforeShutdown(): Promise<void> {
436
		if (this.environmentService.isExtensionDevelopment) {
R
Rob Lourens 已提交
437
			return Promise.resolve(undefined);
438 439 440
		}

		return this.backupFileService.discardAllWorkspaceBackups();
B
Benjamin Pasero 已提交
441 442
	}

443
	protected onFilesConfigurationChange(configuration: IFilesConfiguration): void {
444
		const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF);
445

446
		const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF;
I
isidor 已提交
447
		this.autoSaveContext.set(autoSaveMode);
448
		switch (autoSaveMode) {
449
			case AutoSaveConfiguration.AFTER_DELAY:
450 451
				this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay;
				this.configuredAutoSaveOnFocusChange = false;
452
				this.configuredAutoSaveOnWindowChange = false;
453 454
				break;

455
			case AutoSaveConfiguration.ON_FOCUS_CHANGE:
R
Rob Lourens 已提交
456
				this.configuredAutoSaveDelay = undefined;
457
				this.configuredAutoSaveOnFocusChange = true;
458 459 460 461
				this.configuredAutoSaveOnWindowChange = false;
				break;

			case AutoSaveConfiguration.ON_WINDOW_CHANGE:
R
Rob Lourens 已提交
462
				this.configuredAutoSaveDelay = undefined;
463 464
				this.configuredAutoSaveOnFocusChange = false;
				this.configuredAutoSaveOnWindowChange = true;
465 466 467
				break;

			default:
R
Rob Lourens 已提交
468
				this.configuredAutoSaveDelay = undefined;
469
				this.configuredAutoSaveOnFocusChange = false;
470
				this.configuredAutoSaveOnWindowChange = false;
471 472
				break;
		}
473

474 475
		// Emit as event
		this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration());
476

477
		// save all dirty when enabling auto save
478
		if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) {
479
			this.saveAll();
480
		}
481 482 483 484 485 486 487

		// Check for change in files associations
		const filesAssociation = configuration && configuration.files && configuration.files.associations;
		if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) {
			this.currentFilesAssociationConfig = filesAssociation;
			this._onFilesAssociationChange.fire();
		}
488 489

		// Hot exit
B
Benjamin Pasero 已提交
490
		const hotExitMode = configuration && configuration.files && configuration.files.hotExit;
B
Benjamin Pasero 已提交
491
		if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) {
492
			this.configuredHotExit = hotExitMode;
B
Benjamin Pasero 已提交
493 494
		} else {
			this.configuredHotExit = HotExitConfiguration.ON_EXIT;
495
		}
E
Erich Gamma 已提交
496 497
	}

B
Benjamin Pasero 已提交
498
	getDirty(resources?: URI[]): URI[] {
499 500 501 502 503

		// Collect files
		const dirty = this.getDirtyFileModels(resources).map(m => m.getResource());

		// Add untitled ones
504
		dirty.push(...this.untitledEditorService.getDirty(resources));
505 506

		return dirty;
E
Erich Gamma 已提交
507 508
	}

B
Benjamin Pasero 已提交
509
	isDirty(resource?: URI): boolean {
510 511

		// Check for dirty file
512
		if (this._models.getAll(resource).some(model => model.isDirty())) {
513 514 515 516 517
			return true;
		}

		// Check for dirty untitled
		return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString());
E
Erich Gamma 已提交
518 519
	}

J
Johannes Rieken 已提交
520
	save(resource: URI, options?: ISaveOptions): Promise<boolean> {
521

522
		// Run a forced save if we detect the file is not dirty so that save participants can still run
523
		if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) {
524 525
			const model = this._models.get(resource);
			if (model) {
B
Benjamin Pasero 已提交
526 527
				options.reason = SaveReason.EXPLICIT;

B
Benjamin Pasero 已提交
528
				return model.save(options).then(() => !model.isDirty());
529
			}
530 531
		}

532
		return this.saveAll([resource], options).then(result => result.results.length === 1 && !!result.results[0].success);
E
Erich Gamma 已提交
533 534
	}

J
Johannes Rieken 已提交
535 536
	saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise<ITextFileOperationResult>;
	saveAll(resources: URI[], options?: ISaveOptions): Promise<ITextFileOperationResult>;
537
	saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise<ITextFileOperationResult> {
538 539 540 541 542 543 544 545 546 547 548 549 550

		// get all dirty
		let toSave: URI[] = [];
		if (Array.isArray(arg1)) {
			toSave = this.getDirty(arg1);
		} else {
			toSave = this.getDirty();
		}

		// split up between files and untitled
		const filesToSave: URI[] = [];
		const untitledToSave: URI[] = [];
		toSave.forEach(s => {
551
			if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && s.scheme === Schemas.untitled) {
552
				untitledToSave.push(s);
J
Johannes Rieken 已提交
553 554
			} else {
				filesToSave.push(s);
555 556 557
			}
		});

558
		return this.doSaveAll(filesToSave, untitledToSave, options);
559 560
	}

J
Johannes Rieken 已提交
561
	private doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise<ITextFileOperationResult> {
562 563

		// Handle files first that can just be saved
564
		return this.doSaveAllFiles(fileResources, options).then(async result => {
565 566 567

			// Preflight for untitled to handle cancellation from the dialog
			const targetsForUntitled: URI[] = [];
568
			for (const untitled of untitledResources) {
569
				if (this.untitledEditorService.exists(untitled)) {
570
					let targetUri: URI;
571 572

					// Untitled with associated file path don't need to prompt
573
					if (this.untitledEditorService.hasAssociatedFilePath(untitled)) {
B
Benjamin Pasero 已提交
574
						targetUri = this.untitledToAssociatedFileResource(untitled);
575 576 577 578
					}

					// Otherwise ask user
					else {
B
Benjamin Pasero 已提交
579
						const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled));
580
						if (!targetPath) {
B
Benjamin Pasero 已提交
581 582
							return Promise.resolve({
								results: [...fileResources, ...untitledResources].map(r => ({ source: r }))
583 584
							});
						}
585

M
Martin Aeschlimann 已提交
586
						targetUri = targetPath;
587 588
					}

589
					targetsForUntitled.push(targetUri);
590 591 592 593
				}
			}

			// Handle untitled
J
Johannes Rieken 已提交
594
			const untitledSaveAsPromises: Promise<void>[] = [];
595 596 597 598 599 600 601 602 603 604 605 606
			targetsForUntitled.forEach((target, index) => {
				const untitledSaveAsPromise = this.saveAs(untitledResources[index], target).then(uri => {
					result.results.push({
						source: untitledResources[index],
						target: uri,
						success: !!uri
					});
				});

				untitledSaveAsPromises.push(untitledSaveAsPromise);
			});

B
Benjamin Pasero 已提交
607
			return Promise.all(untitledSaveAsPromises).then(() => result);
608 609 610
		});
	}

B
Benjamin Pasero 已提交
611 612
	private untitledToAssociatedFileResource(untitled: URI): URI {
		const authority = this.windowService.getConfiguration().remoteAuthority;
M
Martin Aeschlimann 已提交
613 614 615 616 617 618 619 620
		if (authority) {
			let path = untitled.path;
			if (path && path[0] !== '/') {
				path = '/' + path;
			}
			return untitled.with({ scheme: REMOTE_HOST_SCHEME, authority, path });
		}
		return untitled.with({ scheme: Schemas.file });
B
Benjamin Pasero 已提交
621 622
	}

J
Johannes Rieken 已提交
623
	private doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise<ITextFileOperationResult> {
R
Rob Lourens 已提交
624
		const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */)
625
			.filter(model => {
626 627
				if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) {
					return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all
628 629 630 631
				}

				return true;
			});
E
Erich Gamma 已提交
632

B
Benjamin Pasero 已提交
633
		const mapResourceToResult = new ResourceMap<IResult>();
634
		dirtyFileModels.forEach(m => {
B
Benjamin Pasero 已提交
635
			mapResourceToResult.set(m.getResource(), {
E
Erich Gamma 已提交
636
				source: m.getResource()
B
Benjamin Pasero 已提交
637
			});
E
Erich Gamma 已提交
638 639
		});

B
Benjamin Pasero 已提交
640
		return Promise.all(dirtyFileModels.map(model => {
641
			return model.save(options).then(() => {
E
Erich Gamma 已提交
642
				if (!model.isDirty()) {
M
Matt Bierner 已提交
643 644 645 646
					const result = mapResourceToResult.get(model.getResource());
					if (result) {
						result.success = true;
					}
E
Erich Gamma 已提交
647 648
				}
			});
B
Benjamin Pasero 已提交
649
		})).then(r => ({ results: mapResourceToResult.values() }));
E
Erich Gamma 已提交
650 651
	}

652
	private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] {
E
Erich Gamma 已提交
653
		if (Array.isArray(arg1)) {
654
			const models: ITextFileEditorModel[] = [];
655
			(<URI[]>arg1).forEach(resource => {
E
Erich Gamma 已提交
656 657 658 659 660 661
				models.push(...this.getFileModels(resource));
			});

			return models;
		}

662
		return this._models.getAll(<URI>arg1);
E
Erich Gamma 已提交
663 664
	}

665 666
	private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] {
		return this.getFileModels(resources).filter(model => model.isDirty());
E
Erich Gamma 已提交
667 668
	}

M
Matt Bierner 已提交
669
	saveAs(resource: URI, target?: URI, options?: ISaveOptions): Promise<URI | undefined> {
670 671

		// Get to target resource
M
Matt Bierner 已提交
672
		let targetPromise: Promise<URI | undefined>;
673
		if (target) {
B
Benjamin Pasero 已提交
674
			targetPromise = Promise.resolve(target);
675
		} else {
M
Martin Aeschlimann 已提交
676
			let dialogPath = resource;
677
			if (resource.scheme === Schemas.untitled) {
678 679 680
				dialogPath = this.suggestFileName(resource);
			}

M
Martin Aeschlimann 已提交
681
			targetPromise = this.promptForPath(resource, dialogPath);
682 683
		}

M
Matt Bierner 已提交
684
		return targetPromise.then<URI | undefined>(target => {
685
			if (!target) {
M
Matt Bierner 已提交
686
				return undefined; // user canceled
687
			}
688

689 690 691 692 693 694 695 696
			// Just save if target is same as models own resource
			if (resource.toString() === target.toString()) {
				return this.save(resource, options).then(() => resource);
			}

			// Do it
			return this.doSaveAs(resource, target, options);
		});
697 698
	}

M
Matt Bierner 已提交
699
	private doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise<URI> {
700 701

		// Retrieve text model from provided resource if any
M
Matt Bierner 已提交
702
		let modelPromise: Promise<ITextFileEditorModel | UntitledEditorModel | undefined> = Promise.resolve(undefined);
703
		if (this.fileService.canHandleResource(resource)) {
B
Benjamin Pasero 已提交
704
			modelPromise = Promise.resolve(this._models.get(resource));
705
		} else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) {
706
			modelPromise = this.untitledEditorService.loadOrCreate({ resource });
707 708
		}

B
Benjamin Pasero 已提交
709
		return modelPromise.then(model => {
710 711 712

			// We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before)
			if (model) {
713
				return this.doSaveTextFileAs(model, resource, target, options);
714 715 716
			}

			// Otherwise we can only copy
B
Benjamin Pasero 已提交
717
			return this.fileService.copy(resource, target).then(() => true);
B
Benjamin Pasero 已提交
718 719 720 721 722 723
		}).then(result => {

			// Return early if the operation was not running
			if (!result) {
				return target;
			}
724 725 726 727 728 729 730 731 732 733

			// Revert the source
			return this.revert(resource).then(() => {

				// Done: return target
				return target;
			});
		});
	}

B
Benjamin Pasero 已提交
734
	private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise<boolean> {
J
Johannes Rieken 已提交
735
		let targetModelResolver: Promise<ITextFileEditorModel>;
B
Benjamin Pasero 已提交
736
		let targetExists: boolean = false;
737

738 739 740
		// Prefer an existing model if it is already loaded for the given target resource
		const targetModel = this.models.get(target);
		if (targetModel && targetModel.isResolved()) {
B
Benjamin Pasero 已提交
741
			targetModelResolver = Promise.resolve(targetModel);
B
Benjamin Pasero 已提交
742
			targetExists = true;
743
		}
744

745 746
		// Otherwise create the target file empty if it does not exist already and resolve it from there
		else {
747
			targetModelResolver = this.fileService.exists(target).then(exists => {
B
Benjamin Pasero 已提交
748 749 750 751 752 753 754
				targetExists = exists;

				// create target model adhoc if file does not exist yet
				if (!targetExists) {
					return this.fileService.updateContent(target, '');
				}

755
				return Promise.resolve(undefined);
B
Benjamin Pasero 已提交
756
			}).then(() => this.models.loadOrCreate(target));
757
		}
758

759
		return targetModelResolver.then(targetModel => {
760

B
Benjamin Pasero 已提交
761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
			// Confirm to overwrite if we have an untitled file with associated file where
			// the file actually exists on disk and we are instructed to save to that file
			// path. This can happen if the file was created after the untitled file was opened.
			// See https://github.com/Microsoft/vscode/issues/67946
			let confirmWrite: Promise<boolean>;
			if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, this.untitledToAssociatedFileResource(sourceModel.getResource()))) {
				confirmWrite = this.confirmOverwrite(target);
			} else {
				confirmWrite = Promise.resolve(true);
			}

			return confirmWrite.then(write => {
				if (!write) {
					return false;
				}

				// take over encoding and model value from source model
				targetModel.updatePreferredEncoding(sourceModel.getEncoding());
M
Matt Bierner 已提交
779 780 781 782 783 784
				if (targetModel.textEditorModel) {
					const snapshot = sourceModel.createSnapshot();
					if (snapshot) {
						this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(snapshot));
					}
				}
785

B
Benjamin Pasero 已提交
786 787 788
				// save model
				return targetModel.save(options).then(() => true);
			});
789
		}, error => {
790

791
			// binary model: delete the file and run the operation again
792
			if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {
793
				return this.fileService.del(target).then(() => this.doSaveTextFileAs(sourceModel, resource, target, options));
794 795
			}

B
Benjamin Pasero 已提交
796
			return Promise.reject(error);
797 798 799
		});
	}

M
Martin Aeschlimann 已提交
800
	private suggestFileName(untitledResource: URI): URI {
801
		const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource);
A
Alex Ross 已提交
802 803
		const remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
		const schemeFilter = remoteAuthority ? REMOTE_HOST_SCHEME : Schemas.file;
M
Martin Aeschlimann 已提交
804 805

		const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter);
806
		if (lastActiveFile) {
807 808
			const lastDir = dirname(lastActiveFile);
			return joinPath(lastDir, untitledFileName);
809 810
		}

M
Martin Aeschlimann 已提交
811
		const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
812
		if (lastActiveFolder) {
M
Martin Aeschlimann 已提交
813
			return joinPath(lastActiveFolder, untitledFileName);
814 815
		}

816
		return schemeFilter === Schemas.file ? URI.file(untitledFileName) : URI.from({ scheme: schemeFilter, authority: remoteAuthority, path: '/' + untitledFileName });
817
	}
E
Erich Gamma 已提交
818

J
Johannes Rieken 已提交
819
	revert(resource: URI, options?: IRevertOptions): Promise<boolean> {
820
		return this.revertAll([resource], options).then(result => result.results.length === 1 && !!result.results[0].success);
E
Erich Gamma 已提交
821 822
	}

J
Johannes Rieken 已提交
823
	revertAll(resources?: URI[], options?: IRevertOptions): Promise<ITextFileOperationResult> {
824 825

		// Revert files first
826
		return this.doRevertAllFiles(resources, options).then(operation => {
827 828 829 830 831 832 833 834 835

			// Revert untitled
			const reverted = this.untitledEditorService.revertAll(resources);
			reverted.forEach(res => operation.results.push({ source: res, success: true }));

			return operation;
		});
	}

J
Johannes Rieken 已提交
836
	private doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise<ITextFileOperationResult> {
837
		const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources);
E
Erich Gamma 已提交
838

B
Benjamin Pasero 已提交
839
		const mapResourceToResult = new ResourceMap<IResult>();
840
		fileModels.forEach(m => {
B
Benjamin Pasero 已提交
841
			mapResourceToResult.set(m.getResource(), {
E
Erich Gamma 已提交
842
				source: m.getResource()
B
Benjamin Pasero 已提交
843
			});
E
Erich Gamma 已提交
844 845
		});

B
Benjamin Pasero 已提交
846
		return Promise.all(fileModels.map(model => {
847
			return model.revert(options && options.soft).then(() => {
E
Erich Gamma 已提交
848
				if (!model.isDirty()) {
M
Matt Bierner 已提交
849 850 851 852
					const result = mapResourceToResult.get(model.getResource());
					if (result) {
						result.success = true;
					}
E
Erich Gamma 已提交
853
				}
854
			}, error => {
E
Erich Gamma 已提交
855

856
				// FileNotFound means the file got deleted meanwhile, so still record as successful revert
857
				if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) {
M
Matt Bierner 已提交
858 859 860 861
					const result = mapResourceToResult.get(model.getResource());
					if (result) {
						result.success = true;
					}
E
Erich Gamma 已提交
862 863 864 865
				}

				// Otherwise bubble up the error
				else {
B
Benjamin Pasero 已提交
866
					return Promise.reject(error);
E
Erich Gamma 已提交
867
				}
B
Benjamin Pasero 已提交
868

R
Rob Lourens 已提交
869
				return undefined;
E
Erich Gamma 已提交
870
			});
B
Benjamin Pasero 已提交
871
		})).then(r => ({ results: mapResourceToResult.values() }));
E
Erich Gamma 已提交
872 873
	}

J
Johannes Rieken 已提交
874
	create(resource: URI, contents?: string, options?: { overwrite?: boolean }): Promise<void> {
B
Benjamin Pasero 已提交
875 876
		const existingModel = this.models.get(resource);

B
Benjamin Pasero 已提交
877 878 879 880
		return this.fileService.createFile(resource, contents, options).then(() => {

			// If we had an existing model for the given resource, load
			// it again to make sure it is up to date with the contents
B
Benjamin Pasero 已提交
881 882 883 884
			// we just wrote into the underlying resource by calling
			// revert()
			if (existingModel && !existingModel.isDisposed()) {
				return existingModel.revert();
B
Benjamin Pasero 已提交
885 886
			}

R
Rob Lourens 已提交
887
			return undefined;
B
Benjamin Pasero 已提交
888 889 890
		});
	}

J
Johannes Rieken 已提交
891
	delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise<void> {
B
Benjamin Pasero 已提交
892 893
		const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource, !platform.isLinux /* ignorecase */));

894
		return this.revertAll(dirtyFiles, { soft: true }).then(() => this.fileService.del(resource, options));
B
Benjamin Pasero 已提交
895 896
	}

J
Johannes Rieken 已提交
897
	move(source: URI, target: URI, overwrite?: boolean): Promise<void> {
898
		const waitForPromises: Promise<unknown>[] = [];
B
Benjamin Pasero 已提交
899 900

		// Event
901 902 903
		this._onWillMove.fire({
			oldResource: source,
			newResource: target,
904
			waitUntil(promise: Promise<unknown>) {
R
Rob Lourens 已提交
905
				waitForPromises.push(promise.then(undefined, errors.onUnexpectedError));
906 907
			}
		});
B
Benjamin Pasero 已提交
908

909 910
		// prevent async waitUntil-calls
		Object.freeze(waitForPromises);
B
Benjamin Pasero 已提交
911

B
Benjamin Pasero 已提交
912
		return Promise.all(waitForPromises).then(() => {
B
Benjamin Pasero 已提交
913

914
			// Handle target models if existing (if target URI is a folder, this can be multiple)
915
			let handleTargetModelPromise: Promise<unknown> = Promise.resolve();
B
Benjamin Pasero 已提交
916
			const dirtyTargetModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */));
917 918 919
			if (dirtyTargetModels.length) {
				handleTargetModelPromise = this.revertAll(dirtyTargetModels.map(targetModel => targetModel.getResource()), { soft: true });
			}
B
Benjamin Pasero 已提交
920

921
			return handleTargetModelPromise.then(() => {
B
Benjamin Pasero 已提交
922

923
				// Handle dirty source models if existing (if source URI is a folder, this can be multiple)
924
				let handleDirtySourceModels: Promise<unknown>;
925 926 927
				const dirtySourceModels = this.getDirtyFileModels().filter(model => isEqualOrParent(model.getResource(), source, !platform.isLinux /* ignorecase */));
				const dirtyTargetModels: URI[] = [];
				if (dirtySourceModels.length) {
B
Benjamin Pasero 已提交
928
					handleDirtySourceModels = Promise.all(dirtySourceModels.map(sourceModel => {
929 930
						const sourceModelResource = sourceModel.getResource();
						let targetModelResource: URI;
B
Benjamin Pasero 已提交
931

932 933 934 935 936 937 938 939
						// If the source is the actual model, just use target as new resource
						if (isEqual(sourceModelResource, source, !platform.isLinux /* ignorecase */)) {
							targetModelResource = target;
						}

						// Otherwise a parent folder of the source is being moved, so we need
						// to compute the target resource based on that
						else {
940
							targetModelResource = sourceModelResource.with({ path: joinPath(target, sourceModelResource.path.substr(source.path.length + 1)).path });
941
						}
B
Benjamin Pasero 已提交
942

943 944
						// Remember as dirty target model to load after the operation
						dirtyTargetModels.push(targetModelResource);
B
Benjamin Pasero 已提交
945

946
						// Backup dirty source model to the target resource it will become later
M
Matt Bierner 已提交
947 948 949 950 951
						const snapshot = sourceModel.createSnapshot();
						if (snapshot) {
							return this.backupFileService.backupResource(targetModelResource, snapshot, sourceModel.getVersionId());
						}
						return Promise.resolve();
952 953
					}));
				} else {
B
Benjamin Pasero 已提交
954
					handleDirtySourceModels = Promise.resolve();
955 956 957
				}

				return handleDirtySourceModels.then(() => {
B
Benjamin Pasero 已提交
958

959 960
					// Soft revert the dirty source files if any
					return this.revertAll(dirtySourceModels.map(dirtySourceModel => dirtySourceModel.getResource()), { soft: true }).then(() => {
B
Benjamin Pasero 已提交
961

962
						// Rename to target
B
Benjamin Pasero 已提交
963
						return this.fileService.move(source, target, overwrite).then(() => {
B
Benjamin Pasero 已提交
964

965
							// Load models that were dirty before
R
Rob Lourens 已提交
966
							return Promise.all(dirtyTargetModels.map(dirtyTargetModel => this.models.loadOrCreate(dirtyTargetModel))).then(() => undefined);
967 968 969
						}, error => {

							// In case of an error, discard any dirty target backups that were made
B
Benjamin Pasero 已提交
970
							return Promise.all(dirtyTargetModels.map(dirtyTargetModel => this.backupFileService.discardResourceBackup(dirtyTargetModel)))
B
Benjamin Pasero 已提交
971
								.then(() => Promise.reject(error));
972
						});
B
Benjamin Pasero 已提交
973 974 975 976 977 978
					});
				});
			});
		});
	}

B
Benjamin Pasero 已提交
979
	getAutoSaveMode(): AutoSaveMode {
980 981 982 983
		if (this.configuredAutoSaveOnFocusChange) {
			return AutoSaveMode.ON_FOCUS_CHANGE;
		}

984 985 986 987
		if (this.configuredAutoSaveOnWindowChange) {
			return AutoSaveMode.ON_WINDOW_CHANGE;
		}

988
		if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) {
989
			return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY;
990 991 992
		}

		return AutoSaveMode.OFF;
993 994
	}

B
Benjamin Pasero 已提交
995
	getAutoSaveConfiguration(): IAutoSaveConfiguration {
996
		return {
R
Rob Lourens 已提交
997
			autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined,
998 999
			autoSaveFocusChange: this.configuredAutoSaveOnFocusChange,
			autoSaveApplicationChange: this.configuredAutoSaveOnWindowChange
B
Benjamin Pasero 已提交
1000
		};
1001 1002
	}

B
Benjamin Pasero 已提交
1003
	get isHotExitEnabled(): boolean {
1004
		return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF;
1005 1006
	}

B
Benjamin Pasero 已提交
1007
	dispose(): void {
E
Erich Gamma 已提交
1008 1009

		// Clear all caches
1010
		this._models.clear();
B
Benjamin Pasero 已提交
1011 1012

		super.dispose();
E
Erich Gamma 已提交
1013
	}
J
Johannes Rieken 已提交
1014
}
1015 1016

registerSingleton(ITextFileService, TextFileService);