Merge VS Code 1.23.1 (#1520)

This commit is contained in:
Matt Irvine
2018-06-05 11:24:51 -07:00
committed by GitHub
parent e3baf5c443
commit 0c58f09e59
3651 changed files with 74249 additions and 48599 deletions

View File

@@ -5,21 +5,20 @@
'use strict';
import * as path from 'vs/base/common/paths';
import nls = require('vs/nls');
import Event, { Emitter } from 'vs/base/common/event';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { TPromise, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base';
import { onUnexpectedError } from 'vs/base/common/errors';
import { guessMimeTypes } from 'vs/base/common/mime';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import URI from 'vs/base/common/uri';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import paths = require('vs/base/common/paths');
import diagnostics = require('vs/base/common/diagnostics');
import types = require('vs/base/common/types');
import * as diagnostics from 'vs/base/common/diagnostics';
import * as types from 'vs/base/common/types';
import { IMode } from 'vs/editor/common/modes';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent, ILoadOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { EncodingMode } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
@@ -63,8 +62,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
private disposed: boolean;
private lastSaveAttemptTime: number;
private createTextEditorModelPromise: TPromise<TextFileEditorModel>;
private _onDidContentChange: Emitter<StateChange>;
private _onDidStateChange: Emitter<StateChange>;
private readonly _onDidContentChange: Emitter<StateChange>;
private readonly _onDidStateChange: Emitter<StateChange>;
private inConflictMode: boolean;
private inOrphanMode: boolean;
@@ -129,38 +128,51 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
private onFileChanges(e: FileChangesEvent): void {
let fileEventImpactsModel = false;
let newInOrphanModeGuess: boolean;
// Track ADD and DELETES for updates of this model to orphan-mode
const modelFileDeleted = e.contains(this.resource, FileChangeType.DELETED);
const modelFileAdded = e.contains(this.resource, FileChangeType.ADDED);
if (modelFileDeleted || modelFileAdded) {
const newInOrphanModeGuess = modelFileDeleted && !modelFileAdded;
if (this.inOrphanMode !== newInOrphanModeGuess) {
let checkOrphanedPromise: TPromise<boolean>;
if (newInOrphanModeGuess) {
// We have received reports of users seeing delete events even though the file still
// exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665).
// Since we do not want to mark the model as orphaned, we have to check if the
// file is really gone and not just a faulty file event.
checkOrphanedPromise = TPromise.timeout(100).then(() => {
if (this.disposed) {
return true;
}
return this.fileService.existsFile(this.resource).then(exists => !exists);
});
} else {
checkOrphanedPromise = TPromise.as(false);
}
checkOrphanedPromise.done(newInOrphanModeValidated => {
if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) {
this.setOrphaned(newInOrphanModeValidated);
}
});
// If we are currently orphaned, we check if the model file was added back
if (this.inOrphanMode) {
const modelFileAdded = e.contains(this.resource, FileChangeType.ADDED);
if (modelFileAdded) {
newInOrphanModeGuess = false;
fileEventImpactsModel = true;
}
}
// Otherwise we check if the model file was deleted
else {
const modelFileDeleted = e.contains(this.resource, FileChangeType.DELETED);
if (modelFileDeleted) {
newInOrphanModeGuess = true;
fileEventImpactsModel = true;
}
}
if (fileEventImpactsModel && this.inOrphanMode !== newInOrphanModeGuess) {
let checkOrphanedPromise: TPromise<boolean>;
if (newInOrphanModeGuess) {
// We have received reports of users seeing delete events even though the file still
// exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665).
// Since we do not want to mark the model as orphaned, we have to check if the
// file is really gone and not just a faulty file event.
checkOrphanedPromise = TPromise.timeout(100).then(() => {
if (this.disposed) {
return true;
}
return this.fileService.existsFile(this.resource).then(exists => !exists);
});
} else {
checkOrphanedPromise = TPromise.as(false);
}
checkOrphanedPromise.done(newInOrphanModeValidated => {
if (this.inOrphanMode !== newInOrphanModeValidated && !this.disposed) {
this.setOrphaned(newInOrphanModeValidated);
}
});
}
}
private setOrphaned(orphaned: boolean): void {
@@ -244,7 +256,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
if (soft) {
loadPromise = TPromise.as(this);
} else {
loadPromise = this.load(true /* force */);
loadPromise = this.load({ forceReadFromDisk: true });
}
return loadPromise.then(() => {
@@ -260,28 +272,28 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
});
}
public load(force?: boolean /* bypass any caches and really go to disk */): TPromise<TextFileEditorModel> {
public load(options?: ILoadOptions): TPromise<TextFileEditorModel> {
diag('load() - enter', this.resource, new Date());
// It is very important to not reload the model when the model is dirty. We only want to reload the model from the disk
// if no save is pending to avoid data loss. This might cause a save conflict in case the file has been modified on the disk
// meanwhile, but this is a very low risk.
if (this.dirty) {
diag('load() - exit - without loading because model is dirty', this.resource, new Date());
if (this.dirty || this.saveSequentializer.hasPendingSave()) {
diag('load() - exit - without loading because model is dirty or being saved', this.resource, new Date());
return TPromise.as(this);
}
// Only for new models we support to load from backup
if (!this.textEditorModel && !this.createTextEditorModelPromise) {
return this.loadWithBackup(force);
return this.loadWithBackup(options);
}
// Otherwise load from file resource
return this.loadFromFile(force);
return this.loadFromFile(options);
}
private loadWithBackup(force: boolean): TPromise<TextFileEditorModel> {
private loadWithBackup(options?: ILoadOptions): TPromise<TextFileEditorModel> {
return this.backupFileService.loadBackupResource(this.resource).then(backup => {
// Make sure meanwhile someone else did not suceed or start loading
@@ -293,34 +305,36 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
if (!!backup) {
const content: IRawTextContent = {
resource: this.resource,
name: paths.basename(this.resource.fsPath),
name: path.basename(this.resource.fsPath),
mtime: Date.now(),
etag: void 0,
value: createTextBufferFactory(''), /* will be filled later from backup */
encoding: this.fileService.getEncoding(this.resource, this.preferredEncoding)
encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding)
};
return this.loadWithContent(content, backup);
}
// Otherwise load from file
return this.loadFromFile(force);
return this.loadFromFile(options);
});
}
private loadFromFile(force: boolean): TPromise<TextFileEditorModel> {
private loadFromFile(options?: ILoadOptions): TPromise<TextFileEditorModel> {
const forceReadFromDisk = options && options.forceReadFromDisk;
const allowBinary = this.isResolved() /* always allow if we resolved previously */ || (options && options.allowBinary);
// Decide on etag
let etag: string;
if (force) {
etag = void 0; // bypass cache if force loading is true
if (forceReadFromDisk) {
etag = void 0; // reset ETag if we enforce to read from disk
} else if (this.lastResolvedDiskStat) {
etag = this.lastResolvedDiskStat.etag; // otherwise respect etag to support caching
}
// Resolve Content
return this.textFileService
.resolveTextContent(this.resource, { acceptTextOnly: true, etag, encoding: this.preferredEncoding })
.resolveTextContent(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding })
.then(content => this.handleLoadSuccess(content), error => this.handleLoadError(error));
}
@@ -370,10 +384,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
"fileGet" : {
"mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"path": { "classification": "CustomerContent", "purpose": "FeatureInsight" }
"path": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.resource.fsPath), path: this.hashService.createSHA1(this.resource.fsPath) });
this.telemetryService.publicLog('fileGet', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: path.extname(this.resource.fsPath), path: this.hashService.createSHA1(this.resource.fsPath) });
}
return model;
@@ -678,16 +692,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// mark the save participant as current pending save operation
return this.saveSequentializer.setPending(versionId, saveParticipantPromise.then(newVersionId => {
// Under certain conditions a save to the model will not cause the contents to the flushed on
// disk because we can assume that the contents are already on disk. Instead, we just touch the
// file to still trigger external file watchers for example.
// Under certain conditions we do a short-cut of flushing contents to disk when we can assume that
// the file has not changed and as such was not dirty before.
// The conditions are all of:
// - a forced, explicit save (Ctrl+S)
// - the model is not dirty (otherwise we know there are changed which needs to go to the file)
// - the model is not in orphan mode (because in that case we know the file does not exist on disk)
// - the model version did not change due to save participants running
if (options.force && !this.dirty && !this.inOrphanMode && options.reason === SaveReason.EXPLICIT && versionId === newVersionId) {
return this.doTouch();
return this.doTouch(newVersionId);
}
// update versionId with its new value (if pre-save changes happened)
@@ -725,7 +738,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
"ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('filePUT', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: paths.extname(this.lastResolvedDiskStat.resource.fsPath) });
this.telemetryService.publicLog('filePUT', { mimeType: guessMimeTypes(this.resource.fsPath).join(', '), ext: path.extname(this.lastResolvedDiskStat.resource.fsPath) });
}
// Update dirty state unless model has changed meanwhile
@@ -774,16 +787,20 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Check for workspace settings file
return this.contextService.getWorkspace().folders.some(folder => {
// {{SQL CARBON EDIT}}
return paths.isEqualOrParent(this.resource.fsPath, path.join(folder.uri.fsPath, '.sqlops'));
return path.isEqualOrParent(this.resource.fsPath, path.join(folder.uri.fsPath, '.sqlops'));
});
}
private doTouch(): TPromise<void> {
return this.fileService.touchFile(this.resource).then(stat => {
private doTouch(versionId: number): TPromise<void> {
return this.saveSequentializer.setPending(versionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, this.createSnapshot(), {
mtime: this.lastResolvedDiskStat.mtime,
encoding: this.getEncoding(),
etag: this.lastResolvedDiskStat.etag
}).then(stat => {
// Updated resolved stat with updated stat since touching it might have changed mtime
this.updateLastResolvedDiskStat(stat);
}, () => void 0 /* gracefully ignore errors if just touching */);
}, () => void 0 /* gracefully ignore errors if just touching */));
}
private setDirty(dirty: boolean): () => void {
@@ -829,8 +846,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
// Subsequent resolve - make sure that we only assign it if the mtime is equal or has advanced.
// This is essential a If-Modified-Since check on the client ot prevent race conditions from loading
// and saving. If a save comes in late after a revert was called, the mtime could be out of sync.
// This prevents race conditions from loading and saving. If a save comes in late after a revert
// was called, the mtime could be out of sync.
else if (this.lastResolvedDiskStat.mtime <= newVersionOnDiskStat.mtime) {
this.lastResolvedDiskStat = newVersionOnDiskStat;
}
@@ -923,7 +940,9 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.updatePreferredEncoding(encoding);
// Load
this.load(true /* force because encoding has changed */).done(null, onUnexpectedError);
this.load({
forceReadFromDisk: true // because encoding has changed
}).done(null, onUnexpectedError);
}
}
@@ -1083,10 +1102,10 @@ export class SaveSequentializer {
class DefaultSaveErrorHandler implements ISaveErrorHandler {
constructor( @INotificationService private notificationService: INotificationService) { }
constructor(@INotificationService private notificationService: INotificationService) { }
public onSaveError(error: any, model: TextFileEditorModel): void {
this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(model.getResource().fsPath), toErrorMessage(error, false)));
this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", path.basename(model.getResource().fsPath), toErrorMessage(error, false)));
}
}

View File

@@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import Event, { Emitter, debounceEvent } from 'vs/base/common/event';
import { Event, Emitter, debounceEvent } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeEvent, StateChange, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeEvent, StateChange, IModelLoadOrCreateOptions, ILoadOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResourceMap } from 'vs/base/common/map';
@@ -17,14 +17,14 @@ import { ResourceMap } from 'vs/base/common/map';
export class TextFileEditorModelManager implements ITextFileEditorModelManager {
private toUnbind: IDisposable[];
private _onModelDisposed: Emitter<URI>;
private _onModelContentChanged: Emitter<TextFileModelChangeEvent>;
private _onModelDirty: Emitter<TextFileModelChangeEvent>;
private _onModelSaveError: Emitter<TextFileModelChangeEvent>;
private _onModelSaved: Emitter<TextFileModelChangeEvent>;
private _onModelReverted: Emitter<TextFileModelChangeEvent>;
private _onModelEncodingChanged: Emitter<TextFileModelChangeEvent>;
private _onModelOrphanedChanged: Emitter<TextFileModelChangeEvent>;
private readonly _onModelDisposed: Emitter<URI>;
private readonly _onModelContentChanged: Emitter<TextFileModelChangeEvent>;
private readonly _onModelDirty: Emitter<TextFileModelChangeEvent>;
private readonly _onModelSaveError: Emitter<TextFileModelChangeEvent>;
private readonly _onModelSaved: Emitter<TextFileModelChangeEvent>;
private readonly _onModelReverted: Emitter<TextFileModelChangeEvent>;
private readonly _onModelEncodingChanged: Emitter<TextFileModelChangeEvent>;
private readonly _onModelOrphanedChanged: Emitter<TextFileModelChangeEvent>;
private _onModelsDirtyEvent: Event<TextFileModelChangeEvent[]>;
private _onModelsSaveError: Event<TextFileModelChangeEvent[]>;
@@ -167,22 +167,27 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
return pendingLoad;
}
let modelLoadOptions: ILoadOptions;
if (options && options.allowBinary) {
modelLoadOptions = { allowBinary: true };
}
let modelPromise: TPromise<ITextFileEditorModel>;
// Model exists
let model = this.get(resource);
if (model) {
if (!options || !options.reload) {
modelPromise = TPromise.as(model);
if (options && options.reload) {
modelPromise = model.load(modelLoadOptions);
} else {
modelPromise = model.load();
modelPromise = TPromise.as(model);
}
}
// Model does not exist
else {
model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : void 0);
modelPromise = model.load();
modelPromise = model.load(modelLoadOptions);
// Install state change listener
this.mapResourceToStateChangeListener.set(resource, model.onDidStateChange(state => {

View File

@@ -7,11 +7,11 @@
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import paths = require('vs/base/common/paths');
import errors = require('vs/base/common/errors');
import objects = require('vs/base/common/objects');
import Event, { Emitter } from 'vs/base/common/event';
import platform = require('vs/base/common/platform');
import * as paths from 'vs/base/common/paths';
import * as errors from 'vs/base/common/errors';
import * as objects from 'vs/base/common/objects';
import { Event, Emitter } from 'vs/base/common/event';
import * as platform from 'vs/base/common/platform';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles';
@@ -51,10 +51,10 @@ export abstract class TextFileService implements ITextFileService {
private toUnbind: IDisposable[];
private _models: TextFileEditorModelManager;
private _onFilesAssociationChange: Emitter<void>;
private readonly _onFilesAssociationChange: Emitter<void>;
private currentFilesAssociationConfig: { [key: string]: string; };
private _onAutoSaveConfigurationChange: Emitter<IAutoSaveConfiguration>;
private readonly _onAutoSaveConfigurationChange: Emitter<IAutoSaveConfiguration>;
private configuredAutoSaveDelay: number;
private configuredAutoSaveOnFocusChange: boolean;
private configuredAutoSaveOnWindowChange: boolean;
@@ -484,8 +484,8 @@ export abstract class TextFileService implements ITextFileService {
private doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): TPromise<ITextFileOperationResult> {
const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : void 0 /* Save All */)
.filter(model => {
if (model.hasState(ModelState.CONFLICT) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) {
return false; // if model is in save conflict, do not save unless save reason is explicit or not provided at all
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
}
return true;

View File

@@ -6,7 +6,7 @@
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import Event from 'vs/base/common/event';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEncodingSupport, ConfirmResult } from 'vs/workbench/common/editor';
import { IBaseStat, IResolveContentOptions, ITextSnapshot } from 'vs/platform/files/common/files';
@@ -140,8 +140,22 @@ export interface IRawTextContent extends IBaseStat {
}
export interface IModelLoadOrCreateOptions {
/**
* The encoding to use when resolving the model text content.
*/
encoding?: string;
/**
* Wether to reload the model if it already exists.
*/
reload?: boolean;
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
}
export interface ITextFileEditorModelManager {
@@ -179,6 +193,19 @@ export interface ISaveOptions {
writeElevated?: boolean;
}
export interface ILoadOptions {
/**
* Go to disk bypassing any cache of the model if any.
*/
forceReadFromDisk?: boolean;
/**
* Allow to load a model even if we think it is a binary file.
*/
allowBinary?: boolean;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
onDidContentChange: Event<StateChange>;
@@ -196,7 +223,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
save(options?: ISaveOptions): TPromise<void>;
load(): TPromise<ITextFileEditorModel>;
load(options?: ILoadOptions): TPromise<ITextFileEditorModel>;
revert(soft?: boolean): TPromise<void>;

View File

@@ -5,11 +5,11 @@
'use strict';
import nls = require('vs/nls');
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import paths = require('vs/base/common/paths');
import strings = require('vs/base/common/strings');
import { isWindows, isLinux } from 'vs/base/common/platform';
import * as paths from 'vs/base/common/paths';
import * as strings from 'vs/base/common/strings';
import { isWindows } from 'vs/base/common/platform';
import URI from 'vs/base/common/uri';
import { ConfirmResult } from 'vs/workbench/common/editor';
import { TextFileService as AbstractTextFileService } from 'vs/workbench/services/textfile/common/textFileService';
@@ -21,17 +21,16 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IModeService } from 'vs/editor/common/services/modeService';
import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
import product from 'vs/platform/node/product';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IModelService } from 'vs/editor/common/services/modelService';
import { getConfirmMessage } from 'vs/workbench/services/dialogs/electron-browser/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { getConfirmMessage, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { Severity } from 'vs/editor/common/standalone/standaloneBase';
export class TextFileService extends AbstractTextFileService {
@@ -50,7 +49,8 @@ export class TextFileService extends AbstractTextFileService {
@IBackupFileService backupFileService: IBackupFileService,
@IWindowsService windowsService: IWindowsService,
@IHistoryService historyService: IHistoryService,
@IContextKeyService contextKeyService: IContextKeyService
@IContextKeyService contextKeyService: IContextKeyService,
@IDialogService private dialogService: IDialogService
) {
super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, environmentService, backupFileService, windowsService, historyService, contextKeyService, modelService);
}
@@ -84,39 +84,22 @@ export class TextFileService extends AbstractTextFileService {
const message = resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", paths.basename(resourcesToConfirm[0].fsPath))
: getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm);
// Button order
// Windows: Save | Don't Save | Cancel
// Mac: Save | Cancel | Don't Save
// Linux: Don't Save | Cancel | Save
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")
];
const save = { label: resourcesToConfirm.length > 1 ? mnemonicButtonLabel(nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All")) : mnemonicButtonLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE };
const dontSave = { label: mnemonicButtonLabel(nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE };
const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL };
const buttons: { label: string; result: ConfirmResult; }[] = [];
if (isWindows) {
buttons.push(save, dontSave, cancel);
} else if (isLinux) {
buttons.push(dontSave, cancel, save);
} else {
buttons.push(save, cancel, dontSave);
}
const opts: Electron.MessageBoxOptions = {
title: product.nameLong,
message,
type: 'warning',
detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them."),
buttons: buttons.map(b => b.label),
noLink: true,
cancelId: buttons.indexOf(cancel)
};
if (isLinux) {
opts.defaultId = 2;
}
return this.windowService.showMessageBox(opts).then(result => buttons[result.button].result);
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;
}
});
}
public promptForPath(defaultPath: string): TPromise<string> {

View File

@@ -12,13 +12,14 @@ import { EncodingMode } from 'vs/workbench/common/editor';
import { TextFileEditorModel, SaveSequentializer } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { ITextFileService, ModelState, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { workbenchInstantiationService, TestTextFileService, createFileInput, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { onError, toResource } from 'vs/base/test/common/utils';
import { toResource } from 'vs/base/test/common/utils';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { FileOperationResult, FileOperationError, IFileService, snapshotToString } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/modelService';
import { timeout } from 'vs/base/common/async';
class ServiceAccessor {
constructor( @ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) {
constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) {
}
}
@@ -46,10 +47,10 @@ suite('Files - TextFileEditorModel', () => {
accessor.fileService.setContent(content);
});
test('Save', function (done) {
test('Save', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('bar');
assert.ok(getLastModifiedTime(model) <= Date.now());
@@ -59,10 +60,8 @@ suite('Files - TextFileEditorModel', () => {
model.dispose();
assert.ok(!accessor.modelService.getModel(model.getResource()));
done();
});
}, error => onError(error, done));
});
});
test('setEncoding - encode', function () {
@@ -88,19 +87,17 @@ suite('Files - TextFileEditorModel', () => {
model.dispose();
});
test('disposes when underlying model is destroyed', function (done) {
test('disposes when underlying model is destroyed', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.dispose();
assert.ok(model.isDisposed());
done();
}, error => onError(error, done));
});
});
test('Load does not trigger save', function (done) {
test('Load does not trigger save', function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8');
assert.ok(model.hasState(ModelState.SAVED));
@@ -108,21 +105,19 @@ suite('Files - TextFileEditorModel', () => {
assert.ok(e !== StateChange.DIRTY && e !== StateChange.SAVED);
});
model.load().done(() => {
return model.load().then(() => {
assert.ok(model.isResolved());
model.dispose();
assert.ok(!accessor.modelService.getModel(model.getResource()));
done();
}, error => onError(error, done));
});
});
test('Load returns dirty model as long as model is dirty', function (done) {
test('Load returns dirty model as long as model is dirty', function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.ok(model.isDirty());
@@ -131,13 +126,11 @@ suite('Files - TextFileEditorModel', () => {
assert.ok(model.isDirty());
model.dispose();
done();
});
}, error => onError(error, done));
});
});
test('Revert', function (done) {
test('Revert', function () {
let eventCounter = 0;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
@@ -148,7 +141,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.ok(model.isDirty());
@@ -159,13 +152,11 @@ suite('Files - TextFileEditorModel', () => {
assert.equal(eventCounter, 1);
model.dispose();
done();
});
}, error => onError(error, done));
});
});
test('Revert (soft)', function (done) {
test('Revert (soft)', function () {
let eventCounter = 0;
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
@@ -176,7 +167,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.ok(model.isDirty());
@@ -187,30 +178,26 @@ suite('Files - TextFileEditorModel', () => {
assert.equal(eventCounter, 1);
model.dispose();
done();
});
}, error => onError(error, done));
});
});
test('Load and undo turns model dirty', function (done) {
test('Load and undo turns model dirty', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
model.load().done(() => {
return model.load().then(() => {
accessor.fileService.setContent('Hello Change');
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.undo();
assert.ok(model.isDirty());
done();
});
}, error => onError(error, done));
});
});
test('File not modified error is handled gracefully', function (done) {
test('File not modified error is handled gracefully', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
model.load().done(() => {
return model.load().then(() => {
const mtime = getLastModifiedTime(model);
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_MODIFIED_SINCE));
@@ -218,32 +205,28 @@ suite('Files - TextFileEditorModel', () => {
assert.ok(model);
assert.equal(getLastModifiedTime(model), mtime);
model.dispose();
done();
});
}, error => onError(error, done));
});
});
test('Load error is handled gracefully if model already exists', function (done) {
test('Load error is handled gracefully if model already exists', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
model.load().done(() => {
return model.load().then(() => {
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND));
return model.load().then((model: TextFileEditorModel) => {
assert.ok(model);
model.dispose();
done();
});
}, error => onError(error, done));
});
});
test('save() and isDirty() - proper with check for mtimes', function (done) {
test('save() and isDirty() - proper with check for mtimes', function () {
const input1 = createFileInput(instantiationService, toResource.call(this, '/path/index_async2.txt'));
const input2 = createFileInput(instantiationService, toResource.call(this, '/path/index_async.txt'));
input1.resolve().done((model1: TextFileEditorModel) => {
return input1.resolve().then((model1: TextFileEditorModel) => {
return input2.resolve().then((model2: TextFileEditorModel) => {
model1.textEditorModel.setValue('foo');
@@ -259,7 +242,7 @@ suite('Files - TextFileEditorModel', () => {
model2.textEditorModel.setValue('foo');
assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
return TPromise.timeout(10).then(() => {
return timeout(10).then(() => {
accessor.textFileService.saveAll().then(() => {
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt')));
assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt')));
@@ -270,15 +253,13 @@ suite('Files - TextFileEditorModel', () => {
model1.dispose();
model2.dispose();
done();
});
});
});
}, error => onError(error, done));
});
});
test('Save Participant', function (done) {
test('Save Participant', function () {
let eventCounter = 0;
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
@@ -300,42 +281,39 @@ suite('Files - TextFileEditorModel', () => {
}
});
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
return model.save().then(() => {
model.dispose();
assert.equal(eventCounter, 2);
done();
});
}, error => onError(error, done));
});
});
test('Save Participant, async participant', function (done) {
test('Save Participant, async participant', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
TextFileEditorModel.setSaveParticipant({
participate: (model) => {
return TPromise.timeout(10);
return timeout(10);
}
});
return model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
const now = Date.now();
return model.save().then(() => {
assert.ok(Date.now() - now >= 10);
model.dispose();
done();
});
}, error => onError(error, done));
});
});
test('Save Participant, bad participant', function (done) {
test('Save Participant, bad participant', function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
TextFileEditorModel.setSaveParticipant({
@@ -347,17 +325,12 @@ suite('Files - TextFileEditorModel', () => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
return model.save().then(() => {
assert.ok(true);
model.dispose();
done();
}, err => {
assert.ok(false);
});
}, error => onError(error, done));
});
});
test('SaveSequentializer - pending basics', function (done) {
test('SaveSequentializer - pending basics', function () {
const sequentializer = new SaveSequentializer();
assert.ok(!sequentializer.hasPendingSave());
@@ -371,26 +344,24 @@ suite('Files - TextFileEditorModel', () => {
assert.ok(!sequentializer.pendingSave);
// pending removes itself after done (use timeout)
sequentializer.setPending(2, TPromise.timeout(1));
sequentializer.setPending(2, timeout(1));
assert.ok(sequentializer.hasPendingSave());
assert.ok(sequentializer.hasPendingSave(2));
assert.ok(!sequentializer.hasPendingSave(1));
assert.ok(sequentializer.pendingSave);
return TPromise.timeout(2).then(() => {
return timeout(2).then(() => {
assert.ok(!sequentializer.hasPendingSave());
assert.ok(!sequentializer.hasPendingSave(2));
assert.ok(!sequentializer.pendingSave);
done();
});
});
test('SaveSequentializer - pending and next (finishes instantly)', function (done) {
test('SaveSequentializer - pending and next (finishes instantly)', function () {
const sequentializer = new SaveSequentializer();
let pendingDone = false;
sequentializer.setPending(1, TPromise.timeout(1).then(() => { pendingDone = true; return null; }));
sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return null; }));
// next finishes instantly
let nextDone = false;
@@ -399,52 +370,46 @@ suite('Files - TextFileEditorModel', () => {
return res.done(() => {
assert.ok(pendingDone);
assert.ok(nextDone);
done();
});
});
test('SaveSequentializer - pending and next (finishes after timeout)', function (done) {
test('SaveSequentializer - pending and next (finishes after timeout)', function () {
const sequentializer = new SaveSequentializer();
let pendingDone = false;
sequentializer.setPending(1, TPromise.timeout(1).then(() => { pendingDone = true; return null; }));
sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return null; }));
// next finishes after timeout
let nextDone = false;
const res = sequentializer.setNext(() => TPromise.timeout(1).then(() => { nextDone = true; return null; }));
const res = sequentializer.setNext(() => timeout(1).then(() => { nextDone = true; return null; }));
return res.done(() => {
assert.ok(pendingDone);
assert.ok(nextDone);
done();
});
});
test('SaveSequentializer - pending and multiple next (last one wins)', function (done) {
test('SaveSequentializer - pending and multiple next (last one wins)', function () {
const sequentializer = new SaveSequentializer();
let pendingDone = false;
sequentializer.setPending(1, TPromise.timeout(1).then(() => { pendingDone = true; return null; }));
sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return null; }));
// next finishes after timeout
let firstDone = false;
let firstRes = sequentializer.setNext(() => TPromise.timeout(2).then(() => { firstDone = true; return null; }));
let firstRes = sequentializer.setNext(() => timeout(2).then(() => { firstDone = true; return null; }));
let secondDone = false;
let secondRes = sequentializer.setNext(() => TPromise.timeout(3).then(() => { secondDone = true; return null; }));
let secondRes = sequentializer.setNext(() => timeout(3).then(() => { secondDone = true; return null; }));
let thirdDone = false;
let thirdRes = sequentializer.setNext(() => TPromise.timeout(4).then(() => { thirdDone = true; return null; }));
let thirdRes = sequentializer.setNext(() => timeout(4).then(() => { thirdDone = true; return null; }));
return TPromise.join([firstRes, secondRes, thirdRes]).then(() => {
assert.ok(pendingDone);
assert.ok(!firstDone);
assert.ok(!secondDone);
assert.ok(thirdDone);
done();
});
});
});

View File

@@ -7,16 +7,15 @@
import * as assert from 'assert';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { join } from 'vs/base/common/paths';
import { workbenchInstantiationService, TestEditorGroupService, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { onError } from 'vs/base/test/common/utils';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/modelService';
import { timeout } from 'vs/base/common/async';
export class TestTextFileEditorModelManager extends TextFileEditorModelManager {
@@ -103,12 +102,12 @@ suite('Files - TextFileEditorModelManager', () => {
model3.dispose();
});
test('loadOrCreate', function (done) {
test('loadOrCreate', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource = URI.file('/test.html');
const encoding = 'utf8';
manager.loadOrCreate(resource, { encoding, reload: true }).done(model => {
return manager.loadOrCreate(resource, { encoding, reload: true }).then(model => {
assert.ok(model);
assert.equal(model.getEncoding(), encoding);
assert.equal(manager.get(resource), model);
@@ -123,11 +122,9 @@ suite('Files - TextFileEditorModelManager', () => {
assert.equal(manager.get(resource), model3);
model3.dispose();
done();
});
});
}, error => onError(error, done));
});
});
test('removed from cache when model disposed', function () {
@@ -150,7 +147,7 @@ suite('Files - TextFileEditorModelManager', () => {
model3.dispose();
});
test('events', function (done) {
test('events', function () {
TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0;
TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 0;
@@ -200,7 +197,7 @@ suite('Files - TextFileEditorModelManager', () => {
disposeCounter++;
});
manager.loadOrCreate(resource1, { encoding: 'utf8' }).done(model1 => {
return manager.loadOrCreate(resource1, { encoding: 'utf8' }).then(model1 => {
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }]));
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }]));
@@ -223,7 +220,7 @@ suite('Files - TextFileEditorModelManager', () => {
assert.equal(encodingCounter, 2);
// content change event if done async
TPromise.timeout(10).then(() => {
return timeout(10).then(() => {
assert.equal(contentCounter, 2);
model1.dispose();
@@ -231,17 +228,15 @@ suite('Files - TextFileEditorModelManager', () => {
assert.ok(!accessor.modelService.getModel(resource1));
assert.ok(!accessor.modelService.getModel(resource2));
done();
});
});
});
});
});
}, error => onError(error, done));
});
});
test('events debounced', function (done) {
test('events debounced', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource1 = toResource('/path/index.txt');
@@ -268,7 +263,7 @@ suite('Files - TextFileEditorModelManager', () => {
assert.equal(e[0].resource.toString(), resource1.toString());
});
manager.loadOrCreate(resource1, { encoding: 'utf8' }).done(model1 => {
return manager.loadOrCreate(resource1, { encoding: 'utf8' }).then(model1 => {
return manager.loadOrCreate(resource2, { encoding: 'utf8' }).then(model2 => {
model1.textEditorModel.setValue('changed');
model1.updatePreferredEncoding('utf16');
@@ -281,7 +276,7 @@ suite('Files - TextFileEditorModelManager', () => {
model2.dispose();
return model1.revert().then(() => { // should not trigger another event if disposed
return TPromise.timeout(20).then(() => {
return timeout(20).then(() => {
assert.equal(dirtyCounter, 2);
assert.equal(revertedCounter, 1);
assert.equal(savedCounter, 1);
@@ -291,38 +286,35 @@ suite('Files - TextFileEditorModelManager', () => {
assert.ok(!accessor.modelService.getModel(resource1));
assert.ok(!accessor.modelService.getModel(resource2));
done();
});
});
});
});
});
}, error => onError(error, done));
});
});
test('disposing model takes it out of the manager', function (done) {
test('disposing model takes it out of the manager', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource = toResource('/path/index_something.txt');
manager.loadOrCreate(resource, { encoding: 'utf8' }).done(model => {
return manager.loadOrCreate(resource, { encoding: 'utf8' }).then(model => {
model.dispose();
assert.ok(!manager.get(resource));
assert.ok(!accessor.modelService.getModel(model.getResource()));
manager.dispose();
done();
}, error => onError(error, done));
});
});
test('dispose prevents dirty model from getting disposed', function (done) {
test('dispose prevents dirty model from getting disposed', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource = toResource('/path/index_something.txt');
manager.loadOrCreate(resource, { encoding: 'utf8' }).done(model => {
return manager.loadOrCreate(resource, { encoding: 'utf8' }).then(model => {
model.textEditorModel.setValue('make dirty');
manager.disposeModel(model as TextFileEditorModel);
@@ -334,7 +326,6 @@ suite('Files - TextFileEditorModelManager', () => {
assert.ok(model.isDisposed());
manager.dispose();
done();
}, error => onError(error, done));
});
});
});

View File

@@ -9,7 +9,7 @@ import * as platform from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { ILifecycleService, ShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService } from 'vs/workbench/test/workbenchTestServices';
import { onError, toResource } from 'vs/base/test/common/utils';
import { toResource } from 'vs/base/test/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
@@ -77,14 +77,14 @@ suite('Files - TextFileService', () => {
}
});
test('confirm onWillShutdown - veto if user cancels', function (done) {
test('confirm onWillShutdown - veto if user cancels', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
service.setConfirmResult(ConfirmResult.CANCEL);
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.equal(service.getDirty().length, 1);
@@ -93,12 +93,10 @@ suite('Files - TextFileService', () => {
accessor.lifecycleService.fireWillShutdown(event);
assert.ok(event.value);
done();
}, error => onError(error, done));
});
});
test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', function (done) {
test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
@@ -106,7 +104,7 @@ suite('Files - TextFileService', () => {
service.setConfirmResult(ConfirmResult.DONT_SAVE);
service.onFilesConfigurationChange({ files: { hotExit: 'off' } });
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.equal(service.getDirty().length, 1);
@@ -119,19 +117,17 @@ suite('Files - TextFileService', () => {
assert.ok(service.cleanupBackupsBeforeShutdownCalled);
assert.ok(!veto);
done();
return void 0;
} else {
veto.then(veto => {
return veto.then(veto => {
assert.ok(service.cleanupBackupsBeforeShutdownCalled);
assert.ok(!veto);
done();
});
}
}, error => onError(error, done));
});
});
test('confirm onWillShutdown - save (hot.exit: off)', function (done) {
test('confirm onWillShutdown - save (hot.exit: off)', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
@@ -139,7 +135,7 @@ suite('Files - TextFileService', () => {
service.setConfirmResult(ConfirmResult.SAVE);
service.onFilesConfigurationChange({ files: { hotExit: 'off' } });
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.equal(service.getDirty().length, 1);
@@ -150,18 +146,16 @@ suite('Files - TextFileService', () => {
return (<TPromise<boolean>>event.value).then(veto => {
assert.ok(!veto);
assert.ok(!model.isDirty());
done();
});
}, error => onError(error, done));
});
});
test('isDirty/getDirty - files and untitled', function (done) {
test('isDirty/getDirty - files and untitled', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
model.load().done(() => {
return model.load().then(() => {
assert.ok(!service.isDirty(model.getResource()));
model.textEditorModel.setValue('foo');
@@ -178,19 +172,17 @@ suite('Files - TextFileService', () => {
assert.ok(service.isDirty(untitled.getResource()));
assert.equal(service.getDirty().length, 2);
assert.equal(service.getDirty([untitled.getResource()])[0].toString(), untitled.getResource().toString());
done();
});
}, error => onError(error, done));
});
});
test('save - file', function (done) {
test('save - file', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.ok(service.isDirty(model.getResource()));
@@ -198,19 +190,17 @@ suite('Files - TextFileService', () => {
return service.save(model.getResource()).then(res => {
assert.ok(res);
assert.ok(!service.isDirty(model.getResource()));
done();
});
}, error => onError(error, done));
});
});
test('saveAll - file', function (done) {
test('saveAll - file', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.ok(service.isDirty(model.getResource()));
@@ -220,20 +210,18 @@ suite('Files - TextFileService', () => {
assert.ok(!service.isDirty(model.getResource()));
assert.equal(res.results.length, 1);
assert.equal(res.results[0].source.toString(), model.getResource().toString());
done();
});
}, error => onError(error, done));
});
});
test('saveAs - file', function (done) {
test('saveAs - file', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
service.setPromptPath(model.getResource().fsPath);
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.ok(service.isDirty(model.getResource()));
@@ -241,20 +229,18 @@ suite('Files - TextFileService', () => {
return service.saveAs(model.getResource()).then(res => {
assert.equal(res.toString(), model.getResource().toString());
assert.ok(!service.isDirty(model.getResource()));
done();
});
}, error => onError(error, done));
});
});
test('revert - file', function (done) {
test('revert - file', function () {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
const service = accessor.textFileService;
service.setPromptPath(model.getResource().fsPath);
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.ok(service.isDirty(model.getResource()));
@@ -262,118 +248,116 @@ suite('Files - TextFileService', () => {
return service.revert(model.getResource()).then(res => {
assert.ok(res);
assert.ok(!service.isDirty(model.getResource()));
done();
});
}, error => onError(error, done));
});
});
// {{SQL CARBON EDIT}}
/*
suite('Hot Exit', () => {
suite('"onExit" setting', () => {
test('should hot exit on non-Mac (reason: CLOSE, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, true, !!platform.isMacintosh, done);
test('should hot exit on non-Mac (reason: CLOSE, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, true, !!platform.isMacintosh);
});
test('should hot exit on non-Mac (reason: CLOSE, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh, done);
test('should hot exit on non-Mac (reason: CLOSE, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh);
});
test('should NOT hot exit (reason: CLOSE, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, true, true, done);
test('should NOT hot exit (reason: CLOSE, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, true, true);
});
test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, false, true, done);
test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.CLOSE, true, false, true);
});
test('should hot exit (reason: QUIT, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, true, false, done);
test('should hot exit (reason: QUIT, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, true, false);
});
test('should hot exit (reason: QUIT, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, false, false, done);
test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, false, false, false);
});
test('should hot exit (reason: QUIT, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, true, false, done);
test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, true, false);
});
test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, false, false, done);
test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.QUIT, true, false, false);
});
test('should hot exit (reason: RELOAD, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, true, false, done);
test('should hot exit (reason: RELOAD, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, true, false);
});
test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, false, false, done);
test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, false, false, false);
});
test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, true, false, done);
test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, true, false);
});
test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, false, false, done);
test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.RELOAD, true, false, false);
});
test('should NOT hot exit (reason: LOAD, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, true, true, done);
test('should NOT hot exit (reason: LOAD, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, true, true);
});
test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, false, true, done);
test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, false, false, true);
});
test('should NOT hot exit (reason: LOAD, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, true, true, done);
test('should NOT hot exit (reason: LOAD, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, true, true);
});
test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, false, true, done);
test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT, ShutdownReason.LOAD, true, false, true);
});
});
suite('"onExitAndWindowClose" setting', () => {
test('should hot exit (reason: CLOSE, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, true, false, done);
test('should hot exit (reason: CLOSE, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, true, false);
});
test('should hot exit (reason: CLOSE, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh, done);
test('should hot exit (reason: CLOSE, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, false, false, !!platform.isMacintosh);
});
test('should hot exit (reason: CLOSE, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, true, false, done);
test('should hot exit (reason: CLOSE, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, true, false);
});
test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, false, true, done);
test('should NOT hot exit (reason: CLOSE, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.CLOSE, true, false, true);
});
test('should hot exit (reason: QUIT, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, true, false, done);
test('should hot exit (reason: QUIT, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, true, false);
});
test('should hot exit (reason: QUIT, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, false, false, done);
test('should hot exit (reason: QUIT, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, false, false, false);
});
test('should hot exit (reason: QUIT, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, true, false, done);
test('should hot exit (reason: QUIT, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, true, false);
});
test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, false, false, done);
test('should hot exit (reason: QUIT, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.QUIT, true, false, false);
});
test('should hot exit (reason: RELOAD, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, true, false, done);
test('should hot exit (reason: RELOAD, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, true, false);
});
test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, false, false, done);
test('should hot exit (reason: RELOAD, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, false, false, false);
});
test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, true, false, done);
test('should hot exit (reason: RELOAD, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, true, false);
});
test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, false, false, done);
test('should hot exit (reason: RELOAD, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.RELOAD, true, false, false);
});
test('should hot exit (reason: LOAD, windows: single, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, true, false, done);
test('should hot exit (reason: LOAD, windows: single, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, true, false);
});
test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, false, true, done);
test('should NOT hot exit (reason: LOAD, windows: single, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, false, false, true);
});
test('should hot exit (reason: LOAD, windows: multiple, workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, true, false, done);
test('should hot exit (reason: LOAD, windows: multiple, workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, true, false);
});
test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function (done) {
hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, false, true, done);
test('should NOT hot exit (reason: LOAD, windows: multiple, empty workspace)', function () {
return hotExitTest.call(this, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE, ShutdownReason.LOAD, true, false, true);
});
});
function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: true, shouldVeto: boolean, done: () => void): void {
function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: true, shouldVeto: boolean): TPromise<void> {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
@@ -391,7 +375,7 @@ suite('Files - TextFileService', () => {
// Set cancel to force a veto if hot exit does not trigger
service.setConfirmResult(ConfirmResult.CANCEL);
model.load().done(() => {
return model.load().then(() => {
model.textEditorModel.setValue('foo');
assert.equal(service.getDirty().length, 1);
@@ -404,10 +388,8 @@ suite('Files - TextFileService', () => {
// When hot exit is set, backups should never be cleaned since the confirm result is cancel
assert.ok(!service.cleanupBackupsBeforeShutdownCalled);
assert.equal(veto, shouldVeto);
done();
});
}, error => onError(error, done));
});
}
});
// {{SQL CARBON EDIT}}