Revert "Merge from vscode ada4bddb8edc69eea6ebaaa0e88c5f903cbd43d8 (#5529)" (#5553)

This reverts commit 5d44b6a6a7.
This commit is contained in:
Anthony Dresser
2019-05-20 17:07:32 -07:00
committed by GitHub
parent 1315b8e42a
commit c9a4f8f664
325 changed files with 3332 additions and 4501 deletions

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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
}
}
}

View File

@@ -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;