mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
This reverts commit 5d44b6a6a7.
This commit is contained in:
@@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { guessMimeTypes } from 'vs/base/common/mime';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types';
|
||||
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, ITextFileStreamContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
@@ -18,12 +18,13 @@ import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'
|
||||
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
|
||||
import { IFileService, FileOperationError, FileOperationResult, CONTENT_CHANGE_EVENT_BUFFER_DELAY, FileChangesEvent, FileChangeType, IFileStatWithMetadata, ETAG_DISABLED } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { RunOnceScheduler, timeout } from 'vs/base/common/async';
|
||||
import { ITextBufferFactory } from 'vs/editor/common/model';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
@@ -32,13 +33,6 @@ import { isEqual, isEqualOrParent, extname, basename } from 'vs/base/common/reso
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export interface IBackupMetaData {
|
||||
mtime: number;
|
||||
size: number;
|
||||
etag: string;
|
||||
orphaned: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk.
|
||||
*/
|
||||
@@ -63,16 +57,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
private resource: URI;
|
||||
|
||||
private contentEncoding: string; // encoding as reported from disk
|
||||
private preferredEncoding: string; // encoding as chosen by the user
|
||||
|
||||
private preferredMode: string; // mode as chosen by the user
|
||||
private contentEncoding: string; // encoding as reported from disk
|
||||
private preferredEncoding: string; // encoding as chosen by the user
|
||||
|
||||
private versionId: number;
|
||||
private bufferSavedVersionId: number;
|
||||
private blockModelContentChange: boolean;
|
||||
|
||||
private lastResolvedFileStat: IFileStatWithMetadata;
|
||||
private createTextEditorModelPromise: Promise<TextFileEditorModel> | null;
|
||||
|
||||
private lastResolvedDiskStat: IFileStatWithMetadata;
|
||||
|
||||
private autoSaveAfterMillies?: number;
|
||||
private autoSaveAfterMilliesEnabled: boolean;
|
||||
@@ -94,7 +88,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
constructor(
|
||||
resource: URI,
|
||||
preferredEncoding: string,
|
||||
preferredMode: string,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IModelService modelService: IModelService,
|
||||
@@ -111,7 +104,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
this.resource = resource;
|
||||
this.preferredEncoding = preferredEncoding;
|
||||
this.preferredMode = preferredMode;
|
||||
this.inOrphanMode = false;
|
||||
this.dirty = false;
|
||||
this.versionId = 0;
|
||||
@@ -207,40 +199,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
private onFilesAssociationChange(): void {
|
||||
if (!this.isResolved()) {
|
||||
if (!this.textEditorModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLineText = this.getFirstLineText(this.textEditorModel);
|
||||
const languageSelection = this.getOrCreateMode(this.resource, this.modeService, this.preferredMode, firstLineText);
|
||||
const languageSelection = this.getOrCreateMode(this.modeService, undefined, firstLineText);
|
||||
|
||||
this.modelService.setMode(this.textEditorModel, languageSelection);
|
||||
}
|
||||
|
||||
setMode(mode: string): void {
|
||||
super.setMode(mode);
|
||||
|
||||
this.preferredMode = mode;
|
||||
}
|
||||
|
||||
async backup(target = this.resource): Promise<void> {
|
||||
if (this.isResolved()) {
|
||||
|
||||
// Only fill in model metadata if resource matches
|
||||
let meta: IBackupMetaData | undefined = undefined;
|
||||
if (isEqual(target, this.resource) && this.lastResolvedFileStat) {
|
||||
meta = {
|
||||
mtime: this.lastResolvedFileStat.mtime,
|
||||
size: this.lastResolvedFileStat.size,
|
||||
etag: this.lastResolvedFileStat.etag,
|
||||
orphaned: this.inOrphanMode
|
||||
};
|
||||
}
|
||||
|
||||
return this.backupFileService.backupResource<IBackupMetaData>(target, this.createSnapshot(), this.versionId, meta);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
getVersionId(): number {
|
||||
return this.versionId;
|
||||
}
|
||||
|
||||
async revert(soft?: boolean): Promise<void> {
|
||||
@@ -275,7 +245,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
}
|
||||
|
||||
async load(options?: ILoadOptions): Promise<ITextFileEditorModel> {
|
||||
load(options?: ILoadOptions): Promise<ITextFileEditorModel> {
|
||||
this.logService.trace('load() - enter', this.resource);
|
||||
|
||||
// It is very important to not reload the model when the model is dirty.
|
||||
@@ -284,57 +254,44 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
if (this.dirty || this.saveSequentializer.hasPendingSave()) {
|
||||
this.logService.trace('load() - exit - without loading because model is dirty or being saved', this.resource);
|
||||
|
||||
return this;
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
|
||||
// Only for new models we support to load from backup
|
||||
if (!this.isResolved()) {
|
||||
const backup = await this.backupFileService.loadBackupResource(this.resource);
|
||||
|
||||
if (this.isResolved()) {
|
||||
return this; // Make sure meanwhile someone else did not suceed in loading
|
||||
}
|
||||
|
||||
if (backup) {
|
||||
try {
|
||||
return await this.loadFromBackup(backup, options);
|
||||
} catch (error) {
|
||||
// ignore error and continue to load as file below
|
||||
}
|
||||
}
|
||||
if (!this.textEditorModel && !this.createTextEditorModelPromise) {
|
||||
return this.loadFromBackup(options);
|
||||
}
|
||||
|
||||
// Otherwise load from file resource
|
||||
return this.loadFromFile(options);
|
||||
}
|
||||
|
||||
private async loadFromBackup(backup: URI, options?: ILoadOptions): Promise<TextFileEditorModel> {
|
||||
private async loadFromBackup(options?: ILoadOptions): Promise<TextFileEditorModel> {
|
||||
const backup = await this.backupFileService.loadBackupResource(this.resource);
|
||||
|
||||
// Resolve actual backup contents
|
||||
const resolvedBackup = await this.backupFileService.resolveBackupContent<IBackupMetaData>(backup);
|
||||
|
||||
if (this.isResolved()) {
|
||||
return this; // Make sure meanwhile someone else did not suceed in loading
|
||||
// Make sure meanwhile someone else did not suceed or start loading
|
||||
if (this.createTextEditorModelPromise || this.textEditorModel) {
|
||||
return this.createTextEditorModelPromise || this;
|
||||
}
|
||||
|
||||
// Load with backup
|
||||
this.loadFromContent({
|
||||
resource: this.resource,
|
||||
name: basename(this.resource),
|
||||
mtime: resolvedBackup.meta ? resolvedBackup.meta.mtime : Date.now(),
|
||||
size: resolvedBackup.meta ? resolvedBackup.meta.size : 0,
|
||||
etag: resolvedBackup.meta ? resolvedBackup.meta.etag : ETAG_DISABLED, // etag disabled if unknown!
|
||||
value: resolvedBackup.value,
|
||||
encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding,
|
||||
isReadonly: false
|
||||
}, options, true /* from backup */);
|
||||
// If we have a backup, continue loading with it
|
||||
if (!!backup) {
|
||||
const content: ITextFileStreamContent = {
|
||||
resource: this.resource,
|
||||
name: basename(this.resource),
|
||||
mtime: Date.now(),
|
||||
size: 0,
|
||||
etag: ETAG_DISABLED, // always allow to save content restored from a backup (see https://github.com/Microsoft/vscode/issues/72343)
|
||||
value: createTextBufferFactory(''), // will be filled later from backup
|
||||
encoding: this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding).encoding,
|
||||
isReadonly: false
|
||||
};
|
||||
|
||||
// Restore orphaned flag based on state
|
||||
if (resolvedBackup.meta && resolvedBackup.meta.orphaned) {
|
||||
this.setOrphaned(true);
|
||||
return this.loadWithContent(content, options, backup);
|
||||
}
|
||||
|
||||
return this;
|
||||
// Otherwise load from file
|
||||
return this.loadFromFile(options);
|
||||
}
|
||||
|
||||
private async loadFromFile(options?: ILoadOptions): Promise<TextFileEditorModel> {
|
||||
@@ -345,8 +302,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
let etag: string | undefined;
|
||||
if (forceReadFromDisk) {
|
||||
etag = ETAG_DISABLED; // disable ETag if we enforce to read from disk
|
||||
} else if (this.lastResolvedFileStat) {
|
||||
etag = this.lastResolvedFileStat.etag; // otherwise respect etag to support caching
|
||||
} else if (this.lastResolvedDiskStat) {
|
||||
etag = this.lastResolvedDiskStat.etag; // otherwise respect etag to support caching
|
||||
}
|
||||
|
||||
// Ensure to track the versionId before doing a long running operation
|
||||
@@ -364,11 +321,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// Clear orphaned state when loading was successful
|
||||
this.setOrphaned(false);
|
||||
|
||||
if (currentVersionId !== this.versionId) {
|
||||
return this; // Make sure meanwhile someone else did not suceed loading
|
||||
// Guard against the model having changed in the meantime
|
||||
if (currentVersionId === this.versionId) {
|
||||
return this.loadWithContent(content, options);
|
||||
}
|
||||
|
||||
return this.loadFromContent(content, options);
|
||||
return this;
|
||||
} catch (error) {
|
||||
const result = error.fileOperationResult;
|
||||
|
||||
@@ -398,11 +356,37 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
}
|
||||
|
||||
private loadFromContent(content: ITextFileStreamContent, options?: ILoadOptions, fromBackup?: boolean): TextFileEditorModel {
|
||||
private async loadWithContent(content: ITextFileStreamContent, options?: ILoadOptions, backup?: URI): Promise<TextFileEditorModel> {
|
||||
const model = await this.doLoadWithContent(content, backup);
|
||||
|
||||
// Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype
|
||||
const settingsType = this.getTypeIfSettings();
|
||||
if (settingsType) {
|
||||
/* __GDPR__
|
||||
"settingsRead" : {
|
||||
"settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data
|
||||
} else {
|
||||
/* __GDPR__
|
||||
"fileGet" : {
|
||||
"${include}": [
|
||||
"${FileTelemetryData}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER));
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private doLoadWithContent(content: ITextFileStreamContent, backup?: URI): Promise<TextFileEditorModel> {
|
||||
this.logService.trace('load() - resolved content', this.resource);
|
||||
|
||||
// Update our resolved disk stat model
|
||||
this.updateLastResolvedFileStat({
|
||||
this.updateLastResolvedDiskStat({
|
||||
resource: this.resource,
|
||||
name: content.name,
|
||||
mtime: content.mtime,
|
||||
@@ -425,61 +409,21 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
// Update Existing Model
|
||||
if (this.isResolved()) {
|
||||
if (this.textEditorModel) {
|
||||
this.doUpdateTextModel(content.value);
|
||||
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
|
||||
// Join an existing request to create the editor model to avoid race conditions
|
||||
else if (this.createTextEditorModelPromise) {
|
||||
this.logService.trace('load() - join existing text editor model promise', this.resource);
|
||||
|
||||
return this.createTextEditorModelPromise;
|
||||
}
|
||||
|
||||
// Create New Model
|
||||
else {
|
||||
this.doCreateTextModel(content.resource, content.value, !!fromBackup);
|
||||
}
|
||||
|
||||
// Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype
|
||||
const settingsType = this.getTypeIfSettings();
|
||||
if (settingsType) {
|
||||
/* __GDPR__
|
||||
"settingsRead" : {
|
||||
"settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data
|
||||
} else {
|
||||
/* __GDPR__
|
||||
"fileGet" : {
|
||||
"${include}": [
|
||||
"${FileTelemetryData}"
|
||||
]
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private doCreateTextModel(resource: URI, value: ITextBufferFactory, fromBackup: boolean): void {
|
||||
this.logService.trace('load() - created text editor model', this.resource);
|
||||
|
||||
// Create model
|
||||
this.createTextEditorModel(value, resource, this.preferredMode);
|
||||
|
||||
// We restored a backup so we have to set the model as being dirty
|
||||
// We also want to trigger auto save if it is enabled to simulate the exact same behaviour
|
||||
// you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977)
|
||||
if (fromBackup) {
|
||||
this.makeDirty();
|
||||
if (this.autoSaveAfterMilliesEnabled) {
|
||||
this.doAutoSave(this.versionId);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we are not tracking a stale state
|
||||
else {
|
||||
this.setDirty(false);
|
||||
}
|
||||
|
||||
// Model Listeners
|
||||
this.installModelListeners();
|
||||
return this.doCreateTextModel(content.resource, content.value, backup);
|
||||
}
|
||||
|
||||
private doUpdateTextModel(value: ITextBufferFactory): void {
|
||||
@@ -491,7 +435,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// Update model value in a block that ignores model content change events
|
||||
this.blockModelContentChange = true;
|
||||
try {
|
||||
this.updateTextEditorModel(value, this.preferredMode);
|
||||
this.updateTextEditorModel(value);
|
||||
} finally {
|
||||
this.blockModelContentChange = false;
|
||||
}
|
||||
@@ -500,6 +444,44 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
this.updateSavedVersionId();
|
||||
}
|
||||
|
||||
private doCreateTextModel(resource: URI, value: ITextBufferFactory, backup: URI | undefined): Promise<TextFileEditorModel> {
|
||||
this.logService.trace('load() - created text editor model', this.resource);
|
||||
|
||||
this.createTextEditorModelPromise = this.doLoadBackup(backup).then(backupContent => {
|
||||
this.createTextEditorModelPromise = null;
|
||||
|
||||
// Create model
|
||||
const hasBackupContent = !!backupContent;
|
||||
this.createTextEditorModel(backupContent ? backupContent : value, resource);
|
||||
|
||||
// We restored a backup so we have to set the model as being dirty
|
||||
// We also want to trigger auto save if it is enabled to simulate the exact same behaviour
|
||||
// you would get if manually making the model dirty (fixes https://github.com/Microsoft/vscode/issues/16977)
|
||||
if (hasBackupContent) {
|
||||
this.makeDirty();
|
||||
if (this.autoSaveAfterMilliesEnabled) {
|
||||
this.doAutoSave(this.versionId);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we are not tracking a stale state
|
||||
else {
|
||||
this.setDirty(false);
|
||||
}
|
||||
|
||||
// Model Listeners
|
||||
this.installModelListeners();
|
||||
|
||||
return this;
|
||||
}, error => {
|
||||
this.createTextEditorModelPromise = null;
|
||||
|
||||
return Promise.reject<TextFileEditorModel>(error);
|
||||
});
|
||||
|
||||
return this.createTextEditorModelPromise;
|
||||
}
|
||||
|
||||
private installModelListeners(): void {
|
||||
|
||||
// See https://github.com/Microsoft/vscode/issues/30189
|
||||
@@ -507,11 +489,27 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// where `value` was captured in the content change listener closure scope.
|
||||
|
||||
// Content Change
|
||||
if (this.isResolved()) {
|
||||
if (this.textEditorModel) {
|
||||
this._register(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged()));
|
||||
}
|
||||
}
|
||||
|
||||
private async doLoadBackup(backup: URI | undefined): Promise<ITextBufferFactory | null> {
|
||||
if (!backup) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return withUndefinedAsNull(await this.backupFileService.resolveBackupContent(backup));
|
||||
} catch (error) {
|
||||
return null; // ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
protected getOrCreateMode(modeService: IModeService, preferredModeIds: string | undefined, firstLineText?: string): ILanguageSelection {
|
||||
return modeService.createByFilepathOrFirstLine(this.resource.fsPath, firstLineText);
|
||||
}
|
||||
|
||||
private onModelContentChanged(): void {
|
||||
this.logService.trace(`onModelContentChanged() - enter`, this.resource);
|
||||
|
||||
@@ -528,7 +526,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// In this case we clear the dirty flag and emit a SAVED event to indicate this state.
|
||||
// Note: we currently only do this check when auto-save is turned off because there you see
|
||||
// a dirty indicator that you want to get rid of when undoing to the saved version.
|
||||
if (!this.autoSaveAfterMilliesEnabled && this.isResolved() && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) {
|
||||
if (!this.autoSaveAfterMilliesEnabled && this.textEditorModel && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) {
|
||||
this.logService.trace('onModelContentChanged() - model content changed back to last saved version', this.resource);
|
||||
|
||||
// Clear flags
|
||||
@@ -659,7 +657,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
|
||||
// Push all edit operations to the undo stack so that the user has a chance to
|
||||
// Ctrl+Z back to the saved version. We only do this when auto-save is turned off
|
||||
if (!this.autoSaveAfterMilliesEnabled && this.isResolved()) {
|
||||
if (!this.autoSaveAfterMilliesEnabled && this.textEditorModel) {
|
||||
this.textEditorModel.pushStackElement();
|
||||
}
|
||||
|
||||
@@ -689,12 +687,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// saving contents to disk that are stale (see https://github.com/Microsoft/vscode/issues/50942).
|
||||
// To fix this issue, we will not store the contents to disk when we got disposed.
|
||||
if (this.disposed) {
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
}
|
||||
|
||||
// We require a resolved model from this point on, since we are about to write data to disk.
|
||||
if (!this.isResolved()) {
|
||||
return undefined; // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Under certain conditions we do a short-cut of flushing contents to disk when we can assume that
|
||||
@@ -720,12 +713,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// Save to Disk
|
||||
// mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering)
|
||||
this.logService.trace(`doSave(${versionId}) - before write()`, this.resource);
|
||||
return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(this.lastResolvedFileStat.resource, this.createSnapshot(), {
|
||||
const snapshot = this.createSnapshot();
|
||||
if (!snapshot) {
|
||||
throw new Error('Invalid snapshot');
|
||||
}
|
||||
return this.saveSequentializer.setPending(newVersionId, this.textFileService.write(this.lastResolvedDiskStat.resource, snapshot, {
|
||||
overwriteReadonly: options.overwriteReadonly,
|
||||
overwriteEncoding: options.overwriteEncoding,
|
||||
mtime: this.lastResolvedFileStat.mtime,
|
||||
mtime: this.lastResolvedDiskStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: this.lastResolvedFileStat.etag,
|
||||
etag: this.lastResolvedDiskStat.etag,
|
||||
writeElevated: options.writeElevated
|
||||
}).then(stat => {
|
||||
this.logService.trace(`doSave(${versionId}) - after write()`, this.resource);
|
||||
@@ -739,7 +736,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
// Updated resolved stat with updated stat
|
||||
this.updateLastResolvedFileStat(stat);
|
||||
this.updateLastResolvedDiskStat(stat);
|
||||
|
||||
// Cancel any content change event promises as they are no longer valid
|
||||
this.contentChangeEventScheduler.cancel();
|
||||
@@ -855,22 +852,19 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
private doTouch(versionId: number): Promise<void> {
|
||||
if (!this.isResolved()) {
|
||||
return Promise.resolve();
|
||||
const snapshot = this.createSnapshot();
|
||||
if (!snapshot) {
|
||||
throw new Error('invalid snapshot');
|
||||
}
|
||||
|
||||
return this.saveSequentializer.setPending(versionId, this.textFileService.write(this.lastResolvedFileStat.resource, this.createSnapshot(), {
|
||||
mtime: this.lastResolvedFileStat.mtime,
|
||||
return this.saveSequentializer.setPending(versionId, this.textFileService.write(this.lastResolvedDiskStat.resource, snapshot, {
|
||||
mtime: this.lastResolvedDiskStat.mtime,
|
||||
encoding: this.getEncoding(),
|
||||
etag: this.lastResolvedFileStat.etag
|
||||
etag: this.lastResolvedDiskStat.etag
|
||||
}).then(stat => {
|
||||
|
||||
// Updated resolved stat with updated stat since touching it might have changed mtime
|
||||
this.updateLastResolvedFileStat(stat);
|
||||
|
||||
// Emit File Saved Event
|
||||
this._onDidStateChange.fire(StateChange.SAVED);
|
||||
|
||||
this.updateLastResolvedDiskStat(stat);
|
||||
}, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */));
|
||||
}
|
||||
|
||||
@@ -904,23 +898,23 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
// in order to find out if the model changed back to a saved version (e.g.
|
||||
// when undoing long enough to reach to a version that is saved and then to
|
||||
// clear the dirty flag)
|
||||
if (this.isResolved()) {
|
||||
if (this.textEditorModel) {
|
||||
this.bufferSavedVersionId = this.textEditorModel.getAlternativeVersionId();
|
||||
}
|
||||
}
|
||||
|
||||
private updateLastResolvedFileStat(newFileStat: IFileStatWithMetadata): void {
|
||||
private updateLastResolvedDiskStat(newVersionOnDiskStat: IFileStatWithMetadata): void {
|
||||
|
||||
// First resolve - just take
|
||||
if (!this.lastResolvedFileStat) {
|
||||
this.lastResolvedFileStat = newFileStat;
|
||||
if (!this.lastResolvedDiskStat) {
|
||||
this.lastResolvedDiskStat = newVersionOnDiskStat;
|
||||
}
|
||||
|
||||
// Subsequent resolve - make sure that we only assign it if the mtime is equal or has advanced.
|
||||
// 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.lastResolvedFileStat.mtime <= newFileStat.mtime) {
|
||||
this.lastResolvedFileStat = newFileStat;
|
||||
else if (this.lastResolvedDiskStat.mtime <= newVersionOnDiskStat.mtime) {
|
||||
this.lastResolvedDiskStat = newVersionOnDiskStat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,6 +937,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return this.lastSaveAttemptTime;
|
||||
}
|
||||
|
||||
getETag(): string | null {
|
||||
return this.lastResolvedDiskStat ? this.lastResolvedDiskStat.etag || null : null;
|
||||
}
|
||||
|
||||
hasState(state: ModelState): boolean {
|
||||
switch (state) {
|
||||
case ModelState.CONFLICT:
|
||||
@@ -1024,12 +1022,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
return true;
|
||||
}
|
||||
|
||||
isResolved(): boolean { // {{SQL CARBON EDIT}} @anthonydresser strict-null-check
|
||||
return !!this.textEditorModel;
|
||||
isResolved(): boolean {
|
||||
return !isUndefinedOrNull(this.lastResolvedDiskStat);
|
||||
}
|
||||
|
||||
isReadonly(): boolean {
|
||||
return !!(this.lastResolvedFileStat && this.lastResolvedFileStat.isReadonly);
|
||||
return !!(this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly);
|
||||
}
|
||||
|
||||
isDisposed(): boolean {
|
||||
@@ -1041,7 +1039,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
}
|
||||
|
||||
getStat(): IFileStatWithMetadata {
|
||||
return this.lastResolvedFileStat;
|
||||
return this.lastResolvedDiskStat;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -1050,6 +1048,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
|
||||
this.inOrphanMode = false;
|
||||
this.inErrorMode = false;
|
||||
|
||||
this.createTextEditorModelPromise = null;
|
||||
|
||||
this.cancelPendingAutoSave();
|
||||
|
||||
super.dispose();
|
||||
|
||||
@@ -153,7 +153,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
|
||||
// Model does not exist
|
||||
else {
|
||||
const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined);
|
||||
const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined);
|
||||
modelPromise = model.load(options);
|
||||
|
||||
// Install state change listener
|
||||
@@ -204,11 +204,6 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
|
||||
// Remove from pending loads
|
||||
this.mapResourceToPendingModelLoaders.delete(resource);
|
||||
|
||||
// Apply mode if provided
|
||||
if (options && options.mode) {
|
||||
resolvedModel.setMode(options.mode);
|
||||
}
|
||||
|
||||
return resolvedModel;
|
||||
} catch (error) {
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import { trim } from 'vs/base/common/strings';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ITextSnapshot } from 'vs/editor/common/model';
|
||||
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
|
||||
/**
|
||||
* The workbench file service implementation implements the raw file service spec and adds additional methods on top.
|
||||
@@ -239,44 +238,59 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
|
||||
private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise<void> {
|
||||
|
||||
// Handle file resources first
|
||||
await Promise.all(dirtyFileModels.map(model => model.backup()));
|
||||
await Promise.all(dirtyFileModels.map(async model => {
|
||||
const snapshot = model.createSnapshot();
|
||||
if (snapshot) {
|
||||
await this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId());
|
||||
}
|
||||
}));
|
||||
|
||||
// Handle untitled resources
|
||||
await Promise.all(untitledResources
|
||||
const untitledModelPromises = untitledResources
|
||||
.filter(untitled => this.untitledEditorService.exists(untitled))
|
||||
.map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup()));
|
||||
.map(untitled => this.untitledEditorService.loadOrCreate({ resource: untitled }));
|
||||
|
||||
const untitledModels = await Promise.all(untitledModelPromises);
|
||||
|
||||
await Promise.all(untitledModels.map(async model => {
|
||||
const snapshot = model.createSnapshot();
|
||||
if (snapshot) {
|
||||
await this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private async confirmBeforeShutdown(): Promise<boolean> {
|
||||
const confirm = await this.confirmSave();
|
||||
private confirmBeforeShutdown(): boolean | Promise<boolean> {
|
||||
return this.confirmSave().then(confirm => {
|
||||
|
||||
// Save
|
||||
if (confirm === ConfirmResult.SAVE) {
|
||||
const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true });
|
||||
// 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
|
||||
}
|
||||
|
||||
if (result.results.some(r => !r.success)) {
|
||||
return true; // veto if some saves failed
|
||||
return this.noVeto({ cleanUpBackups: true });
|
||||
});
|
||||
}
|
||||
|
||||
return this.noVeto({ cleanUpBackups: true });
|
||||
}
|
||||
// Don't Save
|
||||
else if (confirm === ConfirmResult.DONT_SAVE) {
|
||||
|
||||
// Don't Save
|
||||
else if (confirm === ConfirmResult.DONT_SAVE) {
|
||||
// Make sure to revert untitled so that they do not restore
|
||||
// see https://github.com/Microsoft/vscode/issues/29572
|
||||
this.untitledEditorService.revertAll();
|
||||
|
||||
// Make sure to revert untitled so that they do not restore
|
||||
// see https://github.com/Microsoft/vscode/issues/29572
|
||||
this.untitledEditorService.revertAll();
|
||||
return this.noVeto({ cleanUpBackups: true });
|
||||
}
|
||||
|
||||
return this.noVeto({ cleanUpBackups: true });
|
||||
}
|
||||
// Cancel
|
||||
else if (confirm === ConfirmResult.CANCEL) {
|
||||
return true; // veto
|
||||
}
|
||||
|
||||
// Cancel
|
||||
else if (confirm === ConfirmResult.CANCEL) {
|
||||
return true; // veto
|
||||
}
|
||||
|
||||
return false;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise<boolean> {
|
||||
@@ -489,7 +503,10 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
|
||||
dirtyTargetModelUris.push(targetModelResource);
|
||||
|
||||
// Backup dirty source model to the target resource it will become later
|
||||
await sourceModel.backup(targetModelResource);
|
||||
const snapshot = sourceModel.createSnapshot();
|
||||
if (snapshot) {
|
||||
await this.backupFileService.backupResource(targetModelResource, snapshot, sourceModel.getVersionId());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -740,14 +757,14 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
|
||||
private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] {
|
||||
if (Array.isArray(arg1)) {
|
||||
const models: ITextFileEditorModel[] = [];
|
||||
arg1.forEach(resource => {
|
||||
(<URI[]>arg1).forEach(resource => {
|
||||
models.push(...this.getFileModels(resource));
|
||||
});
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
return this._models.getAll(arg1);
|
||||
return this._models.getAll(<URI>arg1);
|
||||
}
|
||||
|
||||
private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] {
|
||||
@@ -856,12 +873,17 @@ export abstract class TextFileService extends Disposable implements ITextFileSer
|
||||
|
||||
// take over encoding, mode and model value from source model
|
||||
targetModel.updatePreferredEncoding(sourceModel.getEncoding());
|
||||
if (sourceModel.isResolved() && targetModel.isResolved()) {
|
||||
this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot()));
|
||||
if (targetModel.textEditorModel) {
|
||||
const snapshot = sourceModel.createSnapshot();
|
||||
if (snapshot) {
|
||||
this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(snapshot));
|
||||
}
|
||||
|
||||
const mode = sourceModel.textEditorModel.getLanguageIdentifier();
|
||||
if (mode.language !== PLAINTEXT_MODE_ID) {
|
||||
targetModel.textEditorModel.setMode(mode); // only use if more specific than plain/text
|
||||
if (sourceModel.textEditorModel) {
|
||||
const language = sourceModel.textEditorModel.getLanguageIdentifier();
|
||||
if (language.id > 1) {
|
||||
targetModel.textEditorModel.setMode(language); // only use if more specific than plain/text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IEncodingSupport, ConfirmResult, IRevertOptions, IModeSupport } from 'vs/workbench/common/editor';
|
||||
import { IEncodingSupport, ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor';
|
||||
import { IBaseStatWithMetadata, IFileStatWithMetadata, IReadFileOptions, IWriteFileOptions, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
|
||||
@@ -367,11 +367,6 @@ export interface IModelLoadOrCreateOptions {
|
||||
*/
|
||||
reason?: LoadReason;
|
||||
|
||||
/**
|
||||
* The language mode to use for the model text content.
|
||||
*/
|
||||
mode?: string;
|
||||
|
||||
/**
|
||||
* The encoding to use when resolving the model text content.
|
||||
*/
|
||||
@@ -448,15 +443,19 @@ export interface ILoadOptions {
|
||||
reason?: LoadReason;
|
||||
}
|
||||
|
||||
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport {
|
||||
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
|
||||
|
||||
readonly onDidContentChange: Event<StateChange>;
|
||||
readonly onDidStateChange: Event<StateChange>;
|
||||
|
||||
getVersionId(): number;
|
||||
|
||||
getResource(): URI;
|
||||
|
||||
hasState(state: ModelState): boolean;
|
||||
|
||||
getETag(): string | null;
|
||||
|
||||
updatePreferredEncoding(encoding: string): void;
|
||||
|
||||
save(options?: ISaveOptions): Promise<void>;
|
||||
@@ -465,17 +464,16 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
|
||||
|
||||
revert(soft?: boolean): Promise<void>;
|
||||
|
||||
backup(target?: URI): Promise<void>;
|
||||
createSnapshot(): ITextSnapshot | null;
|
||||
|
||||
isDirty(): boolean;
|
||||
|
||||
isResolved(): this is IResolvedTextFileEditorModel;
|
||||
isResolved(): boolean;
|
||||
|
||||
isDisposed(): boolean;
|
||||
}
|
||||
|
||||
export interface IResolvedTextFileEditorModel extends ITextFileEditorModel {
|
||||
|
||||
readonly textEditorModel: ITextModel;
|
||||
|
||||
createSnapshot(): ITextSnapshot;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IFileStatWithMetadata, ICreateFileOptions, FileOperationError, FileOperationResult, IFileStreamContent, IFileService } from 'vs/platform/files/common/files';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { exists, stat, chmod, rimraf, MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/base/node/pfs';
|
||||
import { exists, stat, chmod, rimraf } from 'vs/base/node/pfs';
|
||||
import { join, dirname } from 'vs/base/common/path';
|
||||
import { isMacintosh, isLinux } from 'vs/base/common/platform';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
@@ -26,6 +26,7 @@ import { VSBufferReadable, VSBuffer, VSBufferReadableStream } from 'vs/base/comm
|
||||
import { Readable } from 'stream';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
|
||||
import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/fileConstants';
|
||||
import { ITextSnapshot } from 'vs/editor/common/model';
|
||||
|
||||
export class NodeTextFileService extends TextFileService {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiatio
|
||||
|
||||
export class TextResourcePropertiesService implements ITextResourcePropertiesService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<ITextResourcePropertiesService>;
|
||||
_serviceBrand: ServiceIdentifier<any>;
|
||||
|
||||
private remoteEnvironment: IRemoteAgentEnvironment | null = null;
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/commo
|
||||
import { FileOperationResult, FileOperationError, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
|
||||
|
||||
class ServiceAccessor {
|
||||
constructor(@ITextFileService public textFileService: TestTextFileService, @IModelService public modelService: IModelService, @IFileService public fileService: TestFileService) {
|
||||
@@ -45,53 +44,25 @@ suite('Files - TextFileEditorModel', () => {
|
||||
accessor.fileService.setContent(content);
|
||||
});
|
||||
|
||||
test('save', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
test('Save', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
await model.load();
|
||||
|
||||
model.textEditorModel!.setValue('bar');
|
||||
assert.ok(getLastModifiedTime(model) <= Date.now());
|
||||
|
||||
let savedEvent = false;
|
||||
model.onDidStateChange(e => {
|
||||
if (e === StateChange.SAVED) {
|
||||
savedEvent = true;
|
||||
}
|
||||
});
|
||||
|
||||
await model.save();
|
||||
|
||||
assert.ok(model.getLastSaveAttemptTime() <= Date.now());
|
||||
assert.ok(!model.isDirty());
|
||||
assert.ok(savedEvent);
|
||||
|
||||
model.dispose();
|
||||
assert.ok(!accessor.modelService.getModel(model.getResource()));
|
||||
});
|
||||
|
||||
test('save - touching also emits saved event', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
|
||||
await model.load();
|
||||
|
||||
let savedEvent = false;
|
||||
model.onDidStateChange(e => {
|
||||
if (e === StateChange.SAVED) {
|
||||
savedEvent = true;
|
||||
}
|
||||
});
|
||||
|
||||
await model.save({ force: true });
|
||||
|
||||
assert.ok(savedEvent);
|
||||
|
||||
model.dispose();
|
||||
assert.ok(!accessor.modelService.getModel(model.getResource()));
|
||||
});
|
||||
|
||||
test('setEncoding - encode', function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
model.setEncoding('utf8', EncodingMode.Encode); // no-op
|
||||
assert.equal(getLastModifiedTime(model), -1);
|
||||
@@ -104,7 +75,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
test('setEncoding - decode', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
model.setEncoding('utf16', EncodingMode.Decode);
|
||||
|
||||
@@ -113,24 +84,8 @@ suite('Files - TextFileEditorModel', () => {
|
||||
model.dispose();
|
||||
});
|
||||
|
||||
test('create with mode', async function () {
|
||||
const mode = 'text-file-model-test';
|
||||
ModesRegistry.registerLanguage({
|
||||
id: mode,
|
||||
});
|
||||
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', mode);
|
||||
|
||||
await model.load();
|
||||
|
||||
assert.equal(model.textEditorModel!.getModeId(), mode);
|
||||
|
||||
model.dispose();
|
||||
assert.ok(!accessor.modelService.getModel(model.getResource()));
|
||||
});
|
||||
|
||||
test('disposes when underlying model is destroyed', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
await model.load();
|
||||
|
||||
@@ -139,7 +94,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
test('Load does not trigger save', async function () {
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined);
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8');
|
||||
assert.ok(model.hasState(ModelState.SAVED));
|
||||
|
||||
model.onDidStateChange(e => {
|
||||
@@ -153,7 +108,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
test('Load returns dirty model as long as model is dirty', async function () {
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
await model.load();
|
||||
model.textEditorModel!.setValue('foo');
|
||||
@@ -168,7 +123,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
test('Revert', async function () {
|
||||
let eventCounter = 0;
|
||||
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
model.onDidStateChange(e => {
|
||||
if (e === StateChange.REVERTED) {
|
||||
@@ -190,7 +145,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
test('Revert (soft)', async function () {
|
||||
let eventCounter = 0;
|
||||
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
model.onDidStateChange(e => {
|
||||
if (e === StateChange.REVERTED) {
|
||||
@@ -210,7 +165,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
test('Load and undo turns model dirty', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
await model.load();
|
||||
accessor.fileService.setContent('Hello Change');
|
||||
|
||||
@@ -220,7 +175,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
test('File not modified error is handled gracefully', async function () {
|
||||
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
await model.load();
|
||||
|
||||
@@ -235,7 +190,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
test('Load error is handled gracefully if model already exists', async function () {
|
||||
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
let model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
await model.load();
|
||||
accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND));
|
||||
@@ -281,7 +236,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
test('Save Participant', async function () {
|
||||
let eventCounter = 0;
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
model.onDidStateChange(e => {
|
||||
if (e === StateChange.SAVED) {
|
||||
@@ -311,7 +266,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
|
||||
test('Save Participant, async participant', async function () {
|
||||
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
TextFileEditorModel.setSaveParticipant({
|
||||
participate: (model) => {
|
||||
@@ -329,7 +284,7 @@ suite('Files - TextFileEditorModel', () => {
|
||||
});
|
||||
|
||||
test('Save Participant, bad participant', async function () {
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
|
||||
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8');
|
||||
|
||||
TextFileEditorModel.setSaveParticipant({
|
||||
participate: (model) => {
|
||||
|
||||
@@ -13,7 +13,6 @@ import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/file
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { toResource } from 'vs/base/test/common/utils';
|
||||
import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
|
||||
|
||||
export class TestTextFileEditorModelManager extends TextFileEditorModelManager {
|
||||
|
||||
@@ -43,9 +42,9 @@ suite('Files - TextFileEditorModelManager', () => {
|
||||
test('add, remove, clear, get, getAll', function () {
|
||||
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
|
||||
|
||||
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined);
|
||||
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined);
|
||||
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined);
|
||||
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8');
|
||||
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8');
|
||||
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8');
|
||||
|
||||
manager.add(URI.file('/test.html'), model1);
|
||||
manager.add(URI.file('/some/other.html'), model2);
|
||||
@@ -118,9 +117,9 @@ suite('Files - TextFileEditorModelManager', () => {
|
||||
test('removed from cache when model disposed', function () {
|
||||
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
|
||||
|
||||
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8', undefined);
|
||||
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8', undefined);
|
||||
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8', undefined);
|
||||
const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8');
|
||||
const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8');
|
||||
const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8');
|
||||
|
||||
manager.add(URI.file('/test.html'), model1);
|
||||
manager.add(URI.file('/some/other.html'), model2);
|
||||
@@ -291,24 +290,4 @@ suite('Files - TextFileEditorModelManager', () => {
|
||||
assert.ok(model.isDisposed());
|
||||
manager.dispose();
|
||||
});
|
||||
|
||||
test('mode', async function () {
|
||||
const mode = 'text-file-model-manager-test';
|
||||
ModesRegistry.registerLanguage({
|
||||
id: mode,
|
||||
});
|
||||
|
||||
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
|
||||
|
||||
const resource = toResource.call(this, '/path/index_something.txt');
|
||||
|
||||
let model = await manager.loadOrCreate(resource, { mode });
|
||||
assert.equal(model.textEditorModel!.getModeId(), mode);
|
||||
|
||||
model = await manager.loadOrCreate(resource, { mode: 'text' });
|
||||
assert.equal(model.textEditorModel!.getModeId(), PLAINTEXT_MODE_ID);
|
||||
|
||||
manager.disposeModel((model as TextFileEditorModel));
|
||||
manager.dispose();
|
||||
});
|
||||
});
|
||||
@@ -65,8 +65,8 @@ suite('Files - TextFileService', () => {
|
||||
accessor.untitledEditorService.revertAll();
|
||||
});
|
||||
|
||||
test('confirm onWillShutdown - no veto', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
test('confirm onWillShutdown - no veto', function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const event = new BeforeShutdownEventImpl();
|
||||
@@ -76,12 +76,14 @@ suite('Files - TextFileService', () => {
|
||||
if (typeof veto === 'boolean') {
|
||||
assert.ok(!veto);
|
||||
} else {
|
||||
assert.ok(!(await veto));
|
||||
veto.then(veto => {
|
||||
assert.ok(!veto);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test('confirm onWillShutdown - veto if user cancels', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -97,7 +99,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('confirm onWillShutdown - no veto and backups cleaned up if user does not want to save (hot.exit: off)', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -123,7 +125,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('confirm onWillShutdown - save (hot.exit: off)', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -142,7 +144,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('isDirty/getDirty - files and untitled', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -169,7 +171,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('save - file', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -185,11 +187,11 @@ suite('Files - TextFileService', () => {
|
||||
|
||||
test('save - UNC path', async function () {
|
||||
const untitledUncUri = URI.from({ scheme: 'untitled', authority: 'server', path: '/share/path/file.txt' });
|
||||
model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, untitledUncUri, 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const mockedFileUri = untitledUncUri.with({ scheme: Schemas.file });
|
||||
const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8', undefined);
|
||||
const mockedEditorInput = instantiationService.createInstance(TextFileEditorModel, mockedFileUri, 'utf8');
|
||||
const loadOrCreateStub = sinon.stub(accessor.textFileService.models, 'loadOrCreate', () => Promise.resolve(mockedEditorInput));
|
||||
|
||||
sinon.stub(accessor.untitledEditorService, 'exists', () => true);
|
||||
@@ -209,7 +211,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('saveAll - file', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -226,7 +228,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('saveAs - file', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -242,7 +244,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('revert - file', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -258,7 +260,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('delete - dirty file', async function () {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
@@ -272,8 +274,8 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
test('move - dirty file', async function () {
|
||||
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8', undefined);
|
||||
let sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
let targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(sourceModel.getResource(), sourceModel);
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(targetModel.getResource(), targetModel);
|
||||
|
||||
@@ -395,7 +397,7 @@ suite('Files - TextFileService', () => {
|
||||
});
|
||||
|
||||
async function hotExitTest(this: any, setting: string, shutdownReason: ShutdownReason, multipleWindows: boolean, workspace: true, shouldVeto: boolean): Promise<void> {
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
|
||||
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8');
|
||||
(<TextFileEditorModelManager>accessor.textFileService.models).add(model.getResource(), model);
|
||||
|
||||
const service = accessor.textFileService;
|
||||
|
||||
Reference in New Issue
Block a user