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;
|
||||
|
||||
Reference in New Issue
Block a user