debt - align editor model closer to file working copy

This commit is contained in:
Benjamin Pasero 2021-03-29 10:43:37 +02:00
parent 7031abadea
commit df6d78a169
No known key found for this signature in database
GPG key ID: C035C296C8A46619
32 changed files with 292 additions and 303 deletions

View file

@ -62,8 +62,8 @@ export class SimpleModel implements IResolvedTextEditorModel {
return this._onWillDispose.event;
}
public load(): Promise<SimpleModel> {
return Promise.resolve(this);
public resolve(): Promise<void> {
return Promise.resolve();
}
public get textEditorModel(): ITextModel {

View file

@ -14,9 +14,14 @@ export interface IEditorModel {
readonly onWillDispose: Event<void>;
/**
* Loads the model.
* Resolves the model.
*/
load(): Promise<IEditorModel>;
resolve(): Promise<void>;
/**
* Find out if the editor model was resolved or not.
*/
isResolved(): boolean;
/**
* Find out if this model has been disposed.

View file

@ -868,8 +868,8 @@ export interface ITextEditorModel extends IEditorModel {
/**
* The editor model is the heavyweight counterpart of editor input. Depending on the editor input, it
* connects to the disk to retrieve content and may allow for saving it back or reverting it. Editor models
* are typically cached for some while because they are expensive to construct.
* resolves from a file system retrieve content and may allow for saving it back or reverting it.
* Editor models are typically cached for some while because they are expensive to construct.
*/
export class EditorModel extends Disposable implements IEditorModel {
@ -877,19 +877,20 @@ export class EditorModel extends Disposable implements IEditorModel {
readonly onWillDispose = this._onWillDispose.event;
private disposed = false;
private resolved = false;
/**
* Causes this model to load returning a promise when loading is completed.
* Causes this model to resolve returning a promise when loading is completed.
*/
async load(): Promise<IEditorModel> {
return this;
async resolve(): Promise<void> {
this.resolved = true;
}
/**
* Returns whether this model was loaded or not.
*/
isResolved(): boolean {
return true;
return this.resolved;
}
/**

View file

@ -56,7 +56,7 @@ export class BinaryEditorModel extends EditorModel {
return this.etag;
}
async load(): Promise<BinaryEditorModel> {
async resolve(): Promise<void> {
// Make sure to resolve up to date stat for file resources
if (this.fileService.canHandleResource(this.resource)) {
@ -66,7 +66,5 @@ export class BinaryEditorModel extends EditorModel {
this.size = stat.size;
}
}
return this;
}
}

View file

@ -25,17 +25,15 @@ export class DiffEditorModel extends EditorModel {
this._modifiedModel = modifiedModel;
}
async load(): Promise<EditorModel> {
async resolve(): Promise<void> {
await Promise.all([
this._originalModel?.load(),
this._modifiedModel?.load()
this._originalModel?.resolve(),
this._modifiedModel?.resolve()
]);
return this;
}
isResolved(): boolean {
return this.originalModel instanceof EditorModel && this.originalModel.isResolved() && this.modifiedModel instanceof EditorModel && this.modifiedModel.isResolved();
return !!(this.originalModel?.isResolved() && this.modifiedModel?.isResolved());
}
dispose(): void {

View file

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { IDiffEditorModel } from 'vs/editor/common/editorCommon';
import { EditorModel } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel';
@ -32,12 +31,10 @@ export class TextDiffEditorModel extends DiffEditorModel {
this.updateTextDiffEditorModel();
}
async load(): Promise<EditorModel> {
await super.load();
async resolve(): Promise<void> {
await super.resolve();
this.updateTextDiffEditorModel();
return this;
}
private updateTextDiffEditorModel(): void {

View file

@ -78,7 +78,7 @@ suite('BackupRestorer', () => {
const resource = editor.resource;
if (isEqual(resource, untitledFile1)) {
const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource });
if (model.textEditorModel.getValue() !== 'untitled-1') {
if (model.textEditorModel?.getValue() !== 'untitled-1') {
const backupContents = await backupFileService.getBackupContents(untitledFile1);
assert.fail(`Unable to restore backup for resource ${untitledFile1.toString()}. Backup contents: ${backupContents}`);
}
@ -86,21 +86,23 @@ suite('BackupRestorer', () => {
counter++;
} else if (isEqual(resource, untitledFile2)) {
const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource });
if (model.textEditorModel.getValue() !== 'untitled-2') {
if (model.textEditorModel?.getValue() !== 'untitled-2') {
const backupContents = await backupFileService.getBackupContents(untitledFile2);
assert.fail(`Unable to restore backup for resource ${untitledFile2.toString()}. Backup contents: ${backupContents}`);
}
model.dispose();
counter++;
} else if (isEqual(resource, fooFile)) {
const model = await accessor.textFileService.files.get(fooFile!)?.load();
const model = accessor.textFileService.files.get(fooFile);
await model?.resolve();
if (model?.textEditorModel?.getValue() !== 'fooFile') {
const backupContents = await backupFileService.getBackupContents(fooFile);
assert.fail(`Unable to restore backup for resource ${fooFile.toString()}. Backup contents: ${backupContents}`);
}
counter++;
} else {
const model = await accessor.textFileService.files.get(barFile!)?.load();
const model = accessor.textFileService.files.get(barFile);
await model?.resolve();
if (model?.textEditorModel?.getValue() !== 'barFile') {
const backupContents = await backupFileService.getBackupContents(barFile);
assert.fail(`Unable to restore backup for resource ${barFile.toString()}. Backup contents: ${backupContents}`);

View file

@ -79,7 +79,7 @@ suite('BackupTracker (browser)', function () {
const untitledModel = await untitledEditor.resolve();
if (!untitled?.contents) {
untitledModel.textEditorModel.setValue('Super Good');
untitledModel.textEditorModel?.setValue('Super Good');
}
await backupFileService.joinBackupResource();

View file

@ -214,7 +214,7 @@ flakySuite('BackupTracker (native)', function () {
accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL);
accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } });
await model?.load();
await model?.resolve();
model?.textEditorModel?.setValue('foo');
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
@ -235,7 +235,7 @@ flakySuite('BackupTracker (native)', function () {
const model = accessor.textFileService.files.get(resource);
await model?.load();
await model?.resolve();
model?.textEditorModel?.setValue('foo');
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
@ -261,7 +261,7 @@ flakySuite('BackupTracker (native)', function () {
accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE);
accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } });
await model?.load();
await model?.resolve();
model?.textEditorModel?.setValue('foo');
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
const event = new BeforeShutdownEventImpl();
@ -285,7 +285,7 @@ flakySuite('BackupTracker (native)', function () {
accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE);
accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } });
await model?.load();
await model?.resolve();
model?.textEditorModel?.setValue('foo');
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);
const event = new BeforeShutdownEventImpl();
@ -425,7 +425,7 @@ flakySuite('BackupTracker (native)', function () {
// Set cancel to force a veto if hot exit does not trigger
accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL);
await model?.load();
await model?.resolve();
model?.textEditorModel?.setValue('foo');
assert.strictEqual(accessor.workingCopyService.dirtyCount, 1);

View file

@ -33,7 +33,7 @@ suite('Save Participants', function () {
test('insert final new line', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
await model.resolve();
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'insertFinalNewline': true });
const participant = new FinalNewLineParticipant(configService, undefined!);
@ -66,7 +66,7 @@ suite('Save Participants', function () {
test('trim final new lines', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
await model.resolve();
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'trimFinalNewlines': true });
const participant = new TrimFinalNewLinesParticipant(configService, undefined!);
@ -101,7 +101,7 @@ suite('Save Participants', function () {
test('trim final new lines bug#39750', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
await model.resolve();
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'trimFinalNewlines': true });
const participant = new TrimFinalNewLinesParticipant(configService, undefined!);
@ -128,7 +128,7 @@ suite('Save Participants', function () {
test('trim final new lines bug#46075', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
await model.resolve();
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'trimFinalNewlines': true });
const participant = new TrimFinalNewLinesParticipant(configService, undefined!);
@ -155,7 +155,7 @@ suite('Save Participants', function () {
test('trim whitespace', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8', undefined) as IResolvedTextFileEditorModel;
await model.load();
await model.resolve();
const configService = new TestConfigurationService();
configService.setUserConfiguration('files', { 'trimTrailingWhitespace': true });
const participant = new TrimWhitespaceParticipant(configService, undefined!);

View file

@ -9,7 +9,7 @@ import { EncodingMode, IFileEditorInput, Verbosity, GroupIdentifier, IMoveResult
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { ITextFileService, TextFileEditorModelState, TextFileLoadReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, TextFileEditorModelState, TextFileResolveReason, TextFileOperationError, TextFileOperationResult, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IReference, dispose, DisposableStore } from 'vs/base/common/lifecycle';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
@ -291,7 +291,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
encoding: this.preferredEncoding,
reload: { async: true }, // trigger a reload of the model if it exists already but do not wait to show the model
allowBinary: this.forceOpenAs === ForceOpenAs.Text,
reason: TextFileLoadReason.EDITOR
reason: TextFileResolveReason.EDITOR
});
// This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
@ -328,7 +328,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
}
private async doResolveAsBinary(): Promise<BinaryEditorModel> {
return this.instantiationService.createInstance(BinaryEditorModel, this.preferredResource, this.getName()).load();
const model = this.instantiationService.createInstance(BinaryEditorModel, this.preferredResource, this.getName());
await model.resolve();
return model;
}
isResolved(): boolean {

View file

@ -149,7 +149,7 @@ suite('Files - TextFileEditorTracker', () => {
assert.ok(!accessor.editorService.isOpen(untitledEditor));
model.textEditorModel.setValue('Super Good');
model.textEditorModel?.setValue('Super Good');
await awaitEditorOpening(accessor.editorService);
assert.ok(accessor.editorService.isOpen(untitledEditor));
@ -169,12 +169,12 @@ suite('Files - TextFileEditorTracker', () => {
accessor.hostService.setFocus(false);
accessor.hostService.setFocus(true);
await awaitModelLoadEvent(accessor.textFileService, resource);
await awaitModelResolveEvent(accessor.textFileService, resource);
});
function awaitModelLoadEvent(textFileService: ITextFileService, resource: URI): Promise<void> {
function awaitModelResolveEvent(textFileService: ITextFileService, resource: URI): Promise<void> {
return new Promise(resolve => {
const listener = textFileService.files.onDidLoad(e => {
const listener = textFileService.files.onDidResolve(e => {
if (isEqual(e.model.resource, resource)) {
listener.dispose();
resolve();

View file

@ -1033,7 +1033,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor {
return undefined;
}
return editorModel!.load();
return editorModel!.resolve();
})
.then(editorModel => {
if (token.isCancellationRequested) {

View file

@ -19,7 +19,7 @@ import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry';
import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ITextFileService, ITextFileSaveEvent, ITextFileLoadEvent } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, ITextFileSaveEvent, ITextFileResolveEvent } from 'vs/workbench/services/textfile/common/textfiles';
import { extname, basename, isEqual, isEqualOrParent } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
@ -124,14 +124,14 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
this._register(configurationTelemetry(telemetryService, configurationService));
// Files Telemetry
this._register(textFileService.files.onDidLoad(e => this.onTextFileModelLoaded(e)));
this._register(textFileService.files.onDidResolve(e => this.onTextFileModelResolved(e)));
this._register(textFileService.files.onDidSave(e => this.onTextFileModelSaved(e)));
// Lifecycle
this._register(lifecycleService.onShutdown(() => this.dispose()));
}
private onTextFileModelLoaded(e: ITextFileLoadEvent): void {
private onTextFileModelResolved(e: ITextFileResolveEvent): void {
const settingsType = this.getTypeIfSettings(e.model.resource);
if (settingsType) {
type SettingsReadClassification = {

View file

@ -479,10 +479,6 @@ class SimpleDiffEditorModel extends EditorModel {
super();
}
async load(): Promise<this> {
return this;
}
public dispose() {
super.dispose();
this._original.dispose();

View file

@ -140,7 +140,7 @@ export class KeybindingsEditorModel extends EditorModel {
return result;
}
resolve(actionLabels: Map<string, string>): Promise<EditorModel> {
async resolve(actionLabels = new Map<string, string>()): Promise<void> {
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
this._keybindingItemsSortedByPrecedence = [];
@ -158,7 +158,6 @@ export class KeybindingsEditorModel extends EditorModel {
this._keybindingItemsSortedByPrecedence.push(KeybindingsEditorModel.toKeybindingEntry(command, keybindingItem, workbenchActionsRegistry, actionLabels));
}
this._keybindingItems = this._keybindingItemsSortedByPrecedence.slice(0).sort((a, b) => KeybindingsEditorModel.compareKeybindingData(a, b));
return Promise.resolve(this);
}
private static getId(keybindingItem: IKeybindingItem): string {

View file

@ -326,7 +326,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
sourceModelEncoding = sourceModelWithEncodingSupport.getEncoding();
}
// Prefer an existing model if it is already loaded for the given target resource
// Prefer an existing model if it is already resolved for the given target resource
let targetExists: boolean = false;
let targetModel = this.files.get(target);
if (targetModel?.isResolved()) {
@ -346,7 +346,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
targetModel = await this.files.resolve(target, { encoding: sourceModelEncoding });
} catch (error) {
// if the target already exists and was not created by us, it is possible
// that we cannot load the target as text model if it is binary or too
// that we cannot resolve the target as text model if it is binary or too
// large. in that case we have to delete the target file first and then
// re-run the operation.
if (targetExists) {

View file

@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { assertIsDefined, withNullAsUndefined } from 'vs/base/common/types';
import { ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileLoadOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, TextFileEditorModelState, ITextFileEditorModel, ITextFileStreamContent, ITextFileResolveOptions, IResolvedTextFileEditorModel, ITextFileSaveOptions, TextFileResolveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { EncodingMode, IRevertOptions, SaveReason } from 'vs/workbench/common/editor';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { IBackupFileService, IResolvedBackup } from 'vs/workbench/services/backup/common/backup';
@ -43,8 +43,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
private readonly _onDidChangeContent = this._register(new Emitter<void>());
readonly onDidChangeContent = this._onDidChangeContent.event;
private readonly _onDidLoad = this._register(new Emitter<TextFileLoadReason>());
readonly onDidLoad = this._onDidLoad.event;
private readonly _onDidResolve = this._register(new Emitter<TextFileResolveReason>());
readonly onDidResolve = this._onDidResolve.event;
private readonly _onDidChangeDirty = this._register(new Emitter<void>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
@ -221,7 +221,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
const softUndo = options?.soft;
if (!softUndo) {
try {
await this.load({ forceReadFromFile: true });
await this.resolve({ forceReadFromFile: true });
} catch (error) {
// FileNotFound means the file got deleted meanwhile, so ignore it
@ -246,52 +246,52 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
//#endregion
//#region Load
//#region Resolve
async load(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
this.logService.trace('[text file model] load() - enter', this.resource.toString(true));
async resolve(options?: ITextFileResolveOptions): Promise<void> {
this.logService.trace('[text file model] resolve() - enter', this.resource.toString(true));
// Return early if we are disposed
if (this.isDisposed()) {
this.logService.trace('[text file model] load() - exit - without loading because model is disposed', this.resource.toString(true));
this.logService.trace('[text file model] resolve() - exit - without resolving because model is disposed', this.resource.toString(true));
return this;
return;
}
// Unless there are explicit contents provided, it is important that we do not
// load a model that is dirty or is in the process of saving to prevent data
// resolve a model that is dirty or is in the process of saving to prevent data
// loss.
if (!options?.contents && (this.dirty || this.saveSequentializer.hasPending())) {
this.logService.trace('[text file model] load() - exit - without loading because model is dirty or being saved', this.resource.toString(true));
this.logService.trace('[text file model] resolve() - exit - without resolving because model is dirty or being saved', this.resource.toString(true));
return this;
return;
}
return this.doLoad(options);
return this.doResolve(options);
}
private async doLoad(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
private async doResolve(options?: ITextFileResolveOptions): Promise<void> {
// First check if we have contents to use for the model
if (options?.contents) {
return this.loadFromBuffer(options.contents, options);
return this.resolveFromBuffer(options.contents, options);
}
// Second, check if we have a backup to load from (only for new models)
// Second, check if we have a backup to resolve from (only for new models)
const isNewModel = !this.isResolved();
if (isNewModel) {
const loadedFromBackup = await this.loadFromBackup(options);
if (loadedFromBackup) {
return loadedFromBackup;
const resolvedFromBackup = await this.resolveFromBackup(options);
if (resolvedFromBackup) {
return;
}
}
// Finally, load from file resource
return this.loadFromFile(options);
// Finally, resolve from file resource
return this.resolveFromFile(options);
}
private async loadFromBuffer(buffer: ITextBufferFactory, options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
this.logService.trace('[text file model] loadFromBuffer()', this.resource.toString(true));
private async resolveFromBuffer(buffer: ITextBufferFactory, options?: ITextFileResolveOptions): Promise<void> {
this.logService.trace('[text file model] resolveFromBuffer()', this.resource.toString(true));
// Try to resolve metdata from disk
let mtime: number;
@ -321,8 +321,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
const preferredEncoding = await this.textFileService.encoding.getPreferredWriteEncoding(this.resource, this.preferredEncoding);
// Load with buffer
this.loadFromContent({
// Resolve with buffer
this.resolveFromContent({
resource: this.resource,
name: this.name,
mtime,
@ -331,12 +331,10 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
etag,
value: buffer,
encoding: preferredEncoding.encoding
}, true /* dirty (loaded from buffer) */, options);
return this;
}, true /* dirty (resolved from buffer) */, options);
}
private async loadFromBackup(options?: ITextFileLoadOptions): Promise<TextFileEditorModel | undefined> {
private async resolveFromBackup(options?: ITextFileResolveOptions): Promise<boolean> {
// Resolve backup if any
const backup = await this.backupFileService.resolve<IBackupMetaData>(this.resource);
@ -350,25 +348,27 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// Abort if someone else managed to resolve the model by now
let isNewModel = !this.isResolved();
if (!isNewModel) {
this.logService.trace('[text file model] loadFromBackup() - exit - without loading because previously new model got created meanwhile', this.resource.toString(true));
this.logService.trace('[text file model] resolveFromBackup() - exit - without resolving because previously new model got created meanwhile', this.resource.toString(true));
return this; // imply that loading has happened in another operation
return true; // imply that resolving has happened in another operation
}
// Try to load from backup if we have any
// Try to resolve from backup if we have any
if (backup) {
return this.doLoadFromBackup(backup, encoding, options);
this.doResolveFromBackup(backup, encoding, options);
return true;
}
// Otherwise signal back that loading did not happen
return undefined;
// Otherwise signal back that resolving did not happen
return false;
}
private doLoadFromBackup(backup: IResolvedBackup<IBackupMetaData>, encoding: string, options?: ITextFileLoadOptions): TextFileEditorModel {
this.logService.trace('[text file model] doLoadFromBackup()', this.resource.toString(true));
private doResolveFromBackup(backup: IResolvedBackup<IBackupMetaData>, encoding: string, options?: ITextFileResolveOptions): void {
this.logService.trace('[text file model] doResolveFromBackup()', this.resource.toString(true));
// Load with backup
this.loadFromContent({
// Resolve with backup
this.resolveFromContent({
resource: this.resource,
name: this.name,
mtime: backup.meta ? backup.meta.mtime : Date.now(),
@ -377,18 +377,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
etag: backup.meta ? backup.meta.etag : ETAG_DISABLED, // etag disabled if unknown!
value: backup.value,
encoding
}, true /* dirty (loaded from backup) */, options);
}, true /* dirty (resolved from backup) */, options);
// Restore orphaned flag based on state
if (backup.meta && backup.meta.orphaned) {
this.setOrphaned(true);
}
return this;
}
private async loadFromFile(options?: ITextFileLoadOptions): Promise<TextFileEditorModel> {
this.logService.trace('[text file model] loadFromFile()', this.resource.toString(true));
private async resolveFromFile(options?: ITextFileResolveOptions): Promise<void> {
this.logService.trace('[text file model] resolveFromFile()', this.resource.toString(true));
const forceReadFromFile = options?.forceReadFromFile;
const allowBinary = this.isResolved() /* always allow if we resolved previously */ || options?.allowBinary;
@ -409,18 +407,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
try {
const content = await this.textFileService.readStream(this.resource, { acceptTextOnly: !allowBinary, etag, encoding: this.preferredEncoding });
// Clear orphaned state when loading was successful
// Clear orphaned state when resolving was successful
this.setOrphaned(false);
// Return early if the model content has changed
// meanwhile to prevent loosing any changes
if (currentVersionId !== this.versionId) {
this.logService.trace('[text file model] loadFromFile() - exit - without loading because model content changed', this.resource.toString(true));
this.logService.trace('[text file model] resolveFromFile() - exit - without resolving because model content changed', this.resource.toString(true));
return this;
return;
}
return this.loadFromContent(content, false /* not dirty (loaded from file) */, options);
return this.resolveFromContent(content, false /* not dirty (resolved from file) */, options);
} catch (error) {
const result = error.fileOperationResult;
@ -430,15 +428,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// NotModified status is expected and can be handled gracefully
// if we are resolved
if (this.isResolved() && result === FileOperationResult.FILE_NOT_MODIFIED_SINCE) {
return this;
return;
}
// Unless we are forced to read from the file, Ignore when a model has been resolved once
// and the file was deleted meanwhile. Since we already have the model loaded, we can return
// and the file was deleted meanwhile. Since we already have the model resolved, we can return
// to this state and update the orphaned flag to indicate that this model has no version on
// disk anymore.
if (this.isResolved() && result === FileOperationResult.FILE_NOT_FOUND && !forceReadFromFile) {
return this;
return;
}
// Otherwise bubble up the error
@ -446,14 +444,14 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
}
private loadFromContent(content: ITextFileStreamContent, dirty: boolean, options?: ITextFileLoadOptions): TextFileEditorModel {
this.logService.trace('[text file model] loadFromContent() - enter', this.resource.toString(true));
private resolveFromContent(content: ITextFileStreamContent, dirty: boolean, options?: ITextFileResolveOptions): void {
this.logService.trace('[text file model] resolveFromContent() - enter', this.resource.toString(true));
// Return early if we are disposed
if (this.isDisposed()) {
this.logService.trace('[text file model] loadFromContent() - exit - because model is disposed', this.resource.toString(true));
this.logService.trace('[text file model] resolveFromContent() - exit - because model is disposed', this.resource.toString(true));
return this;
return;
}
// Update our resolved disk stat model
@ -498,9 +496,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.setDirty(!!dirty);
// Emit as event
this._onDidLoad.fire(options?.reason ?? TextFileLoadReason.OTHER);
return this;
this._onDidResolve.fire(options?.reason ?? TextFileResolveReason.OTHER);
}
private doCreateTextModel(resource: URI, value: ITextBufferFactory): void {
@ -550,7 +546,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
// We mark check for a dirty-state change upon model content change, unless:
// - explicitly instructed to ignore it (e.g. from model.load())
// - explicitly instructed to ignore it (e.g. from model.resolve())
// - the model is readonly (in that case we never assume the change was done by the user)
if (!this.ignoreDirtyOnModelContentChange && !this.isReadonly()) {
@ -884,7 +880,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
// Subsequent resolve - make sure that we only assign it if the mtime is equal or has advanced.
// This prevents race conditions from loading and saving. If a save comes in late after a revert
// This prevents race conditions from resolving 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;
@ -950,7 +946,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
}
// Decode: Load with encoding
// Decode: Resolve with encoding
else {
if (this.isDirty()) {
this.notificationService.info(localize('saveFileFirst', "The file is dirty. Please save it first before reopening it with another encoding."));
@ -960,8 +956,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.updatePreferredEncoding(encoding);
// Load
this.load({
this.resolve({
forceReadFromFile: true // because encoding has changed
});
}

View file

@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { dispose, IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelLoadOrCreateOptions, ITextFileLoadEvent, ITextFileSaveEvent, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileEditorModel, ITextFileEditorModelManager, ITextFileEditorModelResolveOrCreateOptions, ITextFileResolveEvent, ITextFileSaveEvent, ITextFileSaveParticipant } from 'vs/workbench/services/textfile/common/textfiles';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResourceMap } from 'vs/base/common/map';
@ -32,8 +32,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
private readonly _onDidCreate = this._register(new Emitter<TextFileEditorModel>());
readonly onDidCreate = this._onDidCreate.event;
private readonly _onDidLoad = this._register(new Emitter<ITextFileLoadEvent>());
readonly onDidLoad = this._onDidLoad.event;
private readonly _onDidResolve = this._register(new Emitter<ITextFileResolveEvent>());
readonly onDidResolve = this._onDidResolve.event;
private readonly _onDidChangeDirty = this._register(new Emitter<TextFileEditorModel>());
readonly onDidChangeDirty = this._onDidChangeDirty.event;
@ -53,9 +53,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
private readonly mapResourceToModel = new ResourceMap<TextFileEditorModel>();
private readonly mapResourceToModelListeners = new ResourceMap<IDisposable>();
private readonly mapResourceToDisposeListener = new ResourceMap<IDisposable>();
private readonly mapResourceToPendingModelLoaders = new ResourceMap<Promise<TextFileEditorModel>>();
private readonly mapResourceToPendingModelResolvers = new ResourceMap<Promise<void>>();
private readonly modelLoadQueue = this._register(new ResourceQueue());
private readonly modelResolveQueue = this._register(new ResourceQueue());
saveErrorHandler = (() => {
const notificationService = this.notificationService;
@ -104,25 +104,25 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
continue; // require a resolved, saved model to continue
}
// Trigger a model load for any update or add event that impacts
// Trigger a model resolve for any update or add event that impacts
// the model. We also consider the added event because it could
// be that a file was added and updated right after.
if (e.contains(model.resource, FileChangeType.UPDATED, FileChangeType.ADDED)) {
this.queueModelLoad(model);
this.queueModelResolve(model);
}
}
}
private queueModelLoad(model: TextFileEditorModel): void {
private queueModelResolve(model: TextFileEditorModel): void {
// Load model to update (use a queue to prevent accumulation of loads
// when the load actually takes long. At most we only want the queue
// to have a size of 2 (1 running load and 1 queued load).
const queue = this.modelLoadQueue.queueFor(model.resource);
// Resolve model to update (use a queue to prevent accumulation of resolves
// when the resolve actually takes long. At most we only want the queue
// to have a size of 2 (1 running resolve and 1 queued resolve).
const queue = this.modelResolveQueue.queueFor(model.resource);
if (queue.size <= 1) {
queue.queue(async () => {
try {
await model.load();
await model.resolve();
} catch (error) {
onUnexpectedError(error);
}
@ -152,7 +152,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
}
}
// remember each source model to load again after move is done
// remember each source model to resolve again after move is done
// with optional content to restore if it was dirty
for (const sourceModel of sourceModels) {
const sourceModelResource = sourceModel.resource;
@ -219,7 +219,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
})());
break;
// Move/Copy: restore models that were loaded before the operation took place
// Move/Copy: restore models that were resolved before the operation took place
case FileOperation.MOVE:
case FileOperation.COPY:
e.waitUntil((async () => {
@ -255,17 +255,17 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
return this.mapResourceToModel.get(resource);
}
async resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise<TextFileEditorModel> {
async resolve(resource: URI, options?: ITextFileEditorModelResolveOrCreateOptions): Promise<TextFileEditorModel> {
// Await a pending model load first before proceeding
// to ensure that we never load a model more than once
// Await a pending model resolve first before proceeding
// to ensure that we never resolve a model more than once
// in parallel
const pendingResolve = this.joinPendingResolve(resource);
if (pendingResolve) {
await pendingResolve;
}
let modelPromise: Promise<TextFileEditorModel>;
let modelPromise: Promise<void>;
let model = this.get(resource);
let didCreateModel = false;
@ -274,7 +274,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Always reload if contents are provided
if (options?.contents) {
modelPromise = model.load(options);
modelPromise = model.resolve(options);
}
// Reload async or sync based on options
@ -282,19 +282,19 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// async reload: trigger a reload but return immediately
if (options.reload.async) {
modelPromise = Promise.resolve(model);
model.load(options);
modelPromise = Promise.resolve();
model.resolve(options);
}
// sync reload: do not return until model reloaded
else {
modelPromise = model.load(options);
modelPromise = model.resolve(options);
}
}
// Do not reload
else {
modelPromise = Promise.resolve(model);
modelPromise = Promise.resolve();
}
}
@ -303,13 +303,13 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
didCreateModel = true;
const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined, options ? options.mode : undefined);
modelPromise = model.load(options);
modelPromise = model.resolve(options);
this.registerModel(newModel);
}
// Store pending loads to avoid race conditions
this.mapResourceToPendingModelLoaders.set(resource, modelPromise);
// Store pending resolves to avoid race conditions
this.mapResourceToPendingModelResolvers.set(resource, modelPromise);
// Make known to manager (if not already known)
this.add(resource, model);
@ -326,23 +326,23 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
}
try {
const resolvedModel = await modelPromise;
await modelPromise;
// Remove from pending loads
this.mapResourceToPendingModelLoaders.delete(resource);
// Remove from pending resolves
this.mapResourceToPendingModelResolvers.delete(resource);
// Apply mode if provided
if (options?.mode) {
resolvedModel.setMode(options.mode);
model.setMode(options.mode);
}
// Model can be dirty if a backup was restored, so we make sure to
// have this event delivered if we created the model here
if (didCreateModel && resolvedModel.isDirty()) {
this._onDidChangeDirty.fire(resolvedModel);
if (didCreateModel && model.isDirty()) {
this._onDidChangeDirty.fire(model);
}
return resolvedModel;
return model;
} catch (error) {
// Free resources of this invalid model
@ -350,17 +350,17 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
model.dispose();
}
// Remove from pending loads
this.mapResourceToPendingModelLoaders.delete(resource);
// Remove from pending resolves
this.mapResourceToPendingModelResolvers.delete(resource);
throw error;
}
}
private joinPendingResolve(resource: URI): Promise<void> | undefined {
const pendingModelLoad = this.mapResourceToPendingModelLoaders.get(resource);
if (pendingModelLoad) {
return pendingModelLoad.then(undefined, error => {/* ignore any error here, it will bubble to the original requestor*/ });
const pendingModelResolve = this.mapResourceToPendingModelResolvers.get(resource);
if (pendingModelResolve) {
return pendingModelResolve.then(undefined, error => {/* ignore any error here, it will bubble to the original requestor*/ });
}
return undefined;
@ -370,7 +370,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// Install model listeners
const modelListeners = new DisposableStore();
modelListeners.add(model.onDidLoad(reason => this._onDidLoad.fire({ model, reason })));
modelListeners.add(model.onDidResolve(reason => this._onDidResolve.fire({ model, reason })));
modelListeners.add(model.onDidChangeDirty(() => this._onDidChangeDirty.fire(model)));
modelListeners.add(model.onDidSaveError(() => this._onDidSaveError.fire(model)));
modelListeners.add(model.onDidSave(reason => this._onDidSave.fire({ model, reason })));
@ -432,7 +432,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
// model caches
this.mapResourceToModel.clear();
this.mapResourceToPendingModelLoaders.clear();
this.mapResourceToPendingModelResolvers.clear();
// dispose the dispose listeners
this.mapResourceToDisposeListener.forEach(listener => listener.dispose());
@ -445,10 +445,10 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
canDispose(model: TextFileEditorModel): true | Promise<true> {
// quick return if model already disposed or not dirty and not loading
// quick return if model already disposed or not dirty and not resolving
if (
model.isDisposed() ||
(!this.mapResourceToPendingModelLoaders.has(model.resource) && !model.isDirty())
(!this.mapResourceToPendingModelResolvers.has(model.resource) && !model.isDirty())
) {
return true;
}
@ -459,7 +459,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE
private async doCanDispose(model: TextFileEditorModel): Promise<true> {
// if we have a pending model load, await it first and then try again
// if we have a pending model resolve, await it first and then try again
const pendingResolve = this.joinPendingResolve(model.resource);
if (pendingResolve) {
await pendingResolve;

View file

@ -214,7 +214,7 @@ export const enum TextFileEditorModelState {
ERROR
}
export const enum TextFileLoadReason {
export const enum TextFileResolveReason {
EDITOR = 1,
REFERENCE = 2,
OTHER = 3
@ -244,12 +244,12 @@ export interface ITextFileStreamContent extends IBaseTextFileContent {
value: ITextBufferFactory;
}
export interface ITextFileEditorModelLoadOrCreateOptions {
export interface ITextFileEditorModelResolveOrCreateOptions {
/**
* Context why the model is being loaded or created.
* Context why the model is being resolved or created.
*/
reason?: TextFileLoadReason;
reason?: TextFileResolveReason;
/**
* The language mode to use for the model text content.
@ -269,7 +269,7 @@ export interface ITextFileEditorModelLoadOrCreateOptions {
contents?: ITextBufferFactory;
/**
* If the model was already loaded before, allows to trigger
* If the model was already resolved before, allows to trigger
* a reload of it to fetch the latest contents:
* - async: resolve() will return immediately and trigger
* a reload that will run in the background.
@ -281,7 +281,7 @@ export interface ITextFileEditorModelLoadOrCreateOptions {
};
/**
* Allow to load a model even if we think it is a binary file.
* Allow to resolve a model even if we think it is a binary file.
*/
allowBinary?: boolean;
}
@ -291,9 +291,9 @@ export interface ITextFileSaveEvent {
reason: SaveReason;
}
export interface ITextFileLoadEvent {
export interface ITextFileResolveEvent {
model: ITextFileEditorModel;
reason: TextFileLoadReason;
reason: TextFileResolveReason;
}
export interface ITextFileSaveParticipant {
@ -313,7 +313,7 @@ export interface ITextFileSaveParticipant {
export interface ITextFileEditorModelManager {
readonly onDidCreate: Event<ITextFileEditorModel>;
readonly onDidLoad: Event<ITextFileLoadEvent>;
readonly onDidResolve: Event<ITextFileResolveEvent>;
readonly onDidChangeDirty: Event<ITextFileEditorModel>;
readonly onDidChangeEncoding: Event<ITextFileEditorModel>;
readonly onDidSaveError: Event<ITextFileEditorModel>;
@ -337,9 +337,9 @@ export interface ITextFileEditorModelManager {
get(resource: URI): ITextFileEditorModel | undefined;
/**
* Allows to load a text file model from disk.
* Allows to resolve a text file model from disk.
*/
resolve(resource: URI, options?: ITextFileEditorModelLoadOrCreateOptions): Promise<ITextFileEditorModel>;
resolve(resource: URI, options?: ITextFileEditorModelResolveOrCreateOptions): Promise<ITextFileEditorModel>;
/**
* Adds a participant for saving text file models.
@ -392,7 +392,7 @@ export interface ITextFileSaveAsOptions extends ITextFileSaveOptions {
suggestedTarget?: URI;
}
export interface ITextFileLoadOptions {
export interface ITextFileResolveOptions {
/**
* The contents to use for the model if known. If not
@ -407,14 +407,14 @@ export interface ITextFileLoadOptions {
forceReadFromFile?: boolean;
/**
* Allow to load a model even if we think it is a binary file.
* Allow to resolve a model even if we think it is a binary file.
*/
allowBinary?: boolean;
/**
* Context why the model is being loaded.
* Context why the model is being resolved.
*/
reason?: TextFileLoadReason;
reason?: TextFileResolveReason;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport, IModeSupport, IWorkingCopy {
@ -432,7 +432,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
save(options?: ITextFileSaveOptions): Promise<boolean>;
revert(options?: IRevertOptions): Promise<void>;
load(options?: ITextFileLoadOptions): Promise<ITextFileEditorModel>;
resolve(options?: ITextFileResolveOptions): Promise<void>;
isDirty(): this is IResolvedTextFileEditorModel;

View file

@ -43,12 +43,12 @@ suite('Files - TextFileEditorModel', () => {
test('basic events', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
let onDidLoadCounter = 0;
model.onDidLoad(() => onDidLoadCounter++);
let onDidResolveCounter = 0;
model.onDidResolve(() => onDidResolveCounter++);
await model.load();
await model.resolve();
assert.strictEqual(onDidLoadCounter, 1);
assert.strictEqual(onDidResolveCounter, 1);
let onDidChangeContentCounter = 0;
model.onDidChangeContent(() => onDidChangeContentCounter++);
@ -84,7 +84,7 @@ suite('Files - TextFileEditorModel', () => {
test('save', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
assert.strictEqual(accessor.workingCopyService.dirtyCount, 0);
@ -133,7 +133,7 @@ suite('Files - TextFileEditorModel', () => {
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();
await model.resolve();
let savedEvent = false;
model.onDidSave(() => savedEvent = true);
@ -157,7 +157,7 @@ suite('Files - TextFileEditorModel', () => {
test('save - touching with error turns model dirty', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
let saveErrorEvent = false;
model.onDidSaveError(() => saveErrorEvent = true);
@ -191,7 +191,7 @@ suite('Files - TextFileEditorModel', () => {
test('save error (generic)', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('bar'));
@ -221,7 +221,7 @@ suite('Files - TextFileEditorModel', () => {
test('save error (conflict)', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('bar'));
@ -274,7 +274,7 @@ suite('Files - TextFileEditorModel', () => {
model.setEncoding('utf16', EncodingMode.Decode);
await timeout(0);
assert.ok(model.isResolved()); // model got loaded due to decoding
assert.ok(model.isResolved()); // model got resolved due to decoding
model.dispose();
});
@ -286,7 +286,7 @@ suite('Files - TextFileEditorModel', () => {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', mode);
await model.load();
await model.resolve();
assert.strictEqual(model.textEditorModel!.getModeId(), mode);
@ -297,47 +297,47 @@ suite('Files - TextFileEditorModel', () => {
test('disposes when underlying model is destroyed', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
model.textEditorModel!.dispose();
assert.ok(model.isDisposed());
});
test('Load does not trigger save', async function () {
test('Resolve does not trigger save', async function () {
const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index.txt'), 'utf8', undefined);
assert.ok(model.hasState(TextFileEditorModelState.SAVED));
model.onDidSave(() => assert.fail());
model.onDidChangeDirty(() => assert.fail());
await model.load();
await model.resolve();
assert.ok(model.isResolved());
model.dispose();
assert.ok(!accessor.modelService.getModel(model.resource));
});
test('Load returns dirty model as long as model is dirty', async function () {
test('Resolve 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);
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
assert.ok(model.hasState(TextFileEditorModelState.DIRTY));
await model.load();
await model.resolve();
assert.ok(model.isDirty());
model.dispose();
});
test('Load with contents', async function () {
test('Resolve with contents', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load({ contents: createTextBufferFactory('Hello World') });
await model.resolve({ contents: createTextBufferFactory('Hello World') });
assert.strictEqual(model.textEditorModel?.getValue(), 'Hello World');
assert.strictEqual(model.isDirty(), true);
await model.load({ contents: createTextBufferFactory('Hello Changes') });
await model.resolve({ contents: createTextBufferFactory('Hello Changes') });
assert.strictEqual(model.textEditorModel?.getValue(), 'Hello Changes');
assert.strictEqual(model.isDirty(), true);
@ -365,7 +365,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
@ -398,7 +398,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
@ -419,7 +419,7 @@ suite('Files - TextFileEditorModel', () => {
test('Undo to saved state turns model non-dirty', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('Hello Text'));
assert.ok(model.isDirty());
@ -427,12 +427,12 @@ suite('Files - TextFileEditorModel', () => {
assert.ok(!model.isDirty());
});
test('Load and undo turns model dirty', async function () {
test('Resolve and undo turns model dirty', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
accessor.fileService.setContent('Hello Change');
await model.load();
await model.resolve();
await model.textEditorModel!.undo();
assert.ok(model.isDirty());
@ -448,7 +448,7 @@ suite('Files - TextFileEditorModel', () => {
model.setDirty(true);
assert.ok(!model.isDirty()); // needs to be resolved
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
@ -491,7 +491,7 @@ suite('Files - TextFileEditorModel', () => {
saveEvent = true;
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(!model.isDirty());
@ -509,25 +509,25 @@ 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);
await model.load();
await model.resolve();
const mtime = getLastModifiedTime(model);
accessor.textFileService.setReadStreamErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_MODIFIED_SINCE));
model = await model.load() as TextFileEditorModel;
await model.resolve();
assert.ok(model);
assert.strictEqual(getLastModifiedTime(model), mtime);
model.dispose();
});
test('Load error is handled gracefully if model already exists', async function () {
test('Resolve 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);
await model.load();
await model.resolve();
accessor.textFileService.setReadStreamErrorOnce(new FileOperationError('error', FileOperationResult.FILE_NOT_FOUND));
model = await model.load() as TextFileEditorModel;
await model.resolve();
assert.ok(model);
model.dispose();
});
@ -583,7 +583,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
assert.ok(model.isDirty());
@ -610,7 +610,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
await model.save({ skipSaveParticipants: true });
@ -640,7 +640,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
const now = Date.now();
@ -661,7 +661,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
await model.save();
@ -685,7 +685,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
const p1 = model.save();
@ -744,7 +744,7 @@ suite('Files - TextFileEditorModel', () => {
}
});
await model.load();
await model.resolve();
model.updateTextEditorModel(createTextBufferFactory('foo'));
savePromise = model.save();

View file

@ -186,16 +186,16 @@ suite('Files - TextFileEditorModelManager', () => {
const resource1 = toResource.call(this, '/path/index.txt');
const resource2 = toResource.call(this, '/path/other.txt');
let loadedCounter = 0;
let resolvedCounter = 0;
let gotDirtyCounter = 0;
let gotNonDirtyCounter = 0;
let revertedCounter = 0;
let savedCounter = 0;
let encodingCounter = 0;
manager.onDidLoad(({ model }) => {
manager.onDidResolve(({ model }) => {
if (model.resource.toString() === resource1.toString()) {
loadedCounter++;
resolvedCounter++;
}
});
@ -228,13 +228,13 @@ suite('Files - TextFileEditorModelManager', () => {
});
const model1 = await manager.resolve(resource1, { encoding: 'utf8' });
assert.strictEqual(loadedCounter, 1);
assert.strictEqual(resolvedCounter, 1);
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }], false));
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }], false));
const model2 = await manager.resolve(resource2, { encoding: 'utf8' });
assert.strictEqual(loadedCounter, 2);
assert.strictEqual(resolvedCounter, 2);
model1.updateTextEditorModel(createTextBufferFactory('changed'));
model1.updatePreferredEncoding('utf16');

View file

@ -31,7 +31,7 @@ suite('Files - TextFileService', () => {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
await model.resolve();
assert.ok(!accessor.textFileService.isDirty(model.resource));
model.textEditorModel!.setValue('foo');
@ -41,7 +41,7 @@ suite('Files - TextFileService', () => {
const untitled = await accessor.textFileService.untitled.resolve();
assert.ok(!accessor.textFileService.isDirty(untitled.resource));
untitled.textEditorModel.setValue('changed');
untitled.textEditorModel?.setValue('changed');
assert.ok(accessor.textFileService.isDirty(untitled.resource));
@ -53,7 +53,7 @@ suite('Files - TextFileService', () => {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
await model.resolve();
model.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
@ -66,7 +66,7 @@ suite('Files - TextFileService', () => {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
await model.resolve();
model.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
@ -80,7 +80,7 @@ suite('Files - TextFileService', () => {
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
accessor.fileDialogService.setPickFileToSave(model.resource);
await model.load();
await model.resolve();
model.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
@ -94,7 +94,7 @@ suite('Files - TextFileService', () => {
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
accessor.fileDialogService.setPickFileToSave(model.resource);
await model.load();
await model.resolve();
model!.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));
@ -106,7 +106,7 @@ suite('Files - TextFileService', () => {
model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
await model.resolve();
model!.textEditorModel!.setValue('foo');
assert.ok(accessor.textFileService.isDirty(model.resource));

View file

@ -54,7 +54,7 @@ suite('Files - NativeTextFileService', function () {
test('shutdown joins on pending saves', async function () {
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8', undefined);
await model.load();
await model.resolve();
let pendingSaveAwaited = false;
model.save().then(() => pendingSaveAwaited = true);

View file

@ -9,7 +9,7 @@ import { ITextModel } from 'vs/editor/common/model';
import { IDisposable, toDisposable, IReference, ReferenceCollection, Disposable } from 'vs/base/common/lifecycle';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
import { ITextFileService, TextFileLoadReason } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, TextFileResolveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { Schemas } from 'vs/base/common/network';
import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
@ -60,7 +60,7 @@ class ResourceModelCollection extends ReferenceCollection<Promise<ITextEditorMod
// File or remote file: go through text file service
if (this.fileService.canHandleResource(resource)) {
return this.textFileService.files.resolve(resource, { reason: TextFileLoadReason.REFERENCE });
return this.textFileService.files.resolve(resource, { reason: TextFileResolveReason.REFERENCE });
}
// Virtual documents

View file

@ -73,7 +73,7 @@ suite('Workbench - TextModelResolverService', () => {
const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textModel.resource, textModel);
await textModel.load();
await textModel.resolve();
const ref = await accessor.textModelResolverService.createModelReference(textModel.resource);
@ -97,14 +97,14 @@ suite('Workbench - TextModelResolverService', () => {
const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textModel.resource, textModel);
const loadedModel = await textModel.load();
await textModel.resolve();
loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty'));
textModel.updateTextEditorModel(createTextBufferFactory('make dirty'));
const ref = await accessor.textModelResolverService.createModelReference(textModel.resource);
let disposed = false;
Event.once(loadedModel.onWillDispose)(() => {
Event.once(textModel.onWillDispose)(() => {
disposed = true;
});
@ -112,7 +112,7 @@ suite('Workbench - TextModelResolverService', () => {
await timeout(0);
assert.strictEqual(disposed, false); // not disposed because model still dirty
loadedModel.revert();
textModel.revert();
await timeout(0);
assert.strictEqual(disposed, true); // now disposed because model got reverted
@ -122,14 +122,14 @@ suite('Workbench - TextModelResolverService', () => {
const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(textModel.resource, textModel);
const loadedModel = await textModel.load();
await textModel.resolve();
loadedModel.updateTextEditorModel(createTextBufferFactory('make dirty'));
textModel.updateTextEditorModel(createTextBufferFactory('make dirty'));
const ref1 = await accessor.textModelResolverService.createModelReference(textModel.resource);
let disposed = false;
Event.once(loadedModel.onWillDispose)(() => {
Event.once(textModel.onWillDispose)(() => {
disposed = true;
});
@ -139,7 +139,7 @@ suite('Workbench - TextModelResolverService', () => {
const ref2 = await accessor.textModelResolverService.createModelReference(textModel.resource);
loadedModel.revert();
textModel.revert();
await timeout(0);
assert.strictEqual(disposed, false); // not disposed because we got another ref meanwhile

View file

@ -8,7 +8,6 @@ import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/text
import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ILabelService } from 'vs/platform/label/common/label';
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IFileService } from 'vs/platform/files/common/files';
@ -22,7 +21,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp
static readonly ID: string = 'workbench.editors.untitledEditorInput';
private modelResolve: Promise<IUntitledTextEditorModel & IResolvedTextEditorModel> | undefined = undefined;
private modelResolve: Promise<void> | undefined = undefined;
constructor(
public readonly model: IUntitledTextEditorModel,
@ -110,16 +109,14 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp
return this.model.getMode();
}
resolve(): Promise<IUntitledTextEditorModel & IResolvedTextEditorModel> {
// Join a model resolve if we have had one before
if (this.modelResolve) {
return this.modelResolve;
async resolve(): Promise<IUntitledTextEditorModel> {
if (!this.modelResolve) {
this.modelResolve = this.model.resolve();
}
this.modelResolve = this.model.load();
await this.modelResolve;
return this.modelResolve;
return this.model;
}
matches(otherInput: unknown): boolean {

View file

@ -13,7 +13,7 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model';
import { createTextBufferFactory } from 'vs/editor/common/model/textModel';
import { IResolvedTextEditorModel, ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { IWorkingCopyService, IWorkingCopy, WorkingCopyCapabilities, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
@ -57,15 +57,15 @@ export interface IUntitledTextEditorModel extends ITextEditorModel, IModeSupport
setEncoding(encoding: string): void;
/**
* Load the untitled model.
* Resolves the untitled model.
*/
load(): Promise<IUntitledTextEditorModel & IResolvedTextEditorModel>;
resolve(): Promise<void>;
/**
* Updates the value of the untitled model optionally allowing to ignore dirty.
* The model must be resolved for this method to work.
*/
setValue(this: IResolvedTextEditorModel, value: string, ignoreDirty?: boolean): void;
setValue(value: string, ignoreDirty?: boolean): void;
}
export class UntitledTextEditorModel extends BaseTextEditorModel implements IUntitledTextEditorModel {
@ -272,7 +272,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
return { content: withNullAsUndefined(this.createSnapshot()) };
}
async load(): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
async resolve(): Promise<void> {
// Check for backups
const backup = await this.backupFileService.resolve(this.resource);
@ -321,8 +321,6 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
this._onDidChangeContent.fire();
}
}
return this as UntitledTextEditorModel & IResolvedTextEditorModel;
}
private onModelContentChanged(textEditorModel: ITextModel, e: IModelContentChangedEvent): void {

View file

@ -13,7 +13,6 @@ import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
export const IUntitledTextEditorService = createDecorator<IUntitledTextEditorService>('untitledTextEditorService');
@ -110,9 +109,9 @@ export interface IUntitledTextEditorModelManager {
* property is provided and the untitled editor exists, it will return that existing
* instance instead of creating a new one.
*/
resolve(options?: INewUntitledTextEditorOptions): Promise<IUntitledTextEditorModel & IResolvedTextEditorModel>;
resolve(options?: INewUntitledTextEditorWithAssociatedResourceOptions): Promise<IUntitledTextEditorModel & IResolvedTextEditorModel>;
resolve(options?: IExistingUntitledTextEditorOptions): Promise<IUntitledTextEditorModel & IResolvedTextEditorModel>;
resolve(options?: INewUntitledTextEditorOptions): Promise<IUntitledTextEditorModel>;
resolve(options?: INewUntitledTextEditorWithAssociatedResourceOptions): Promise<IUntitledTextEditorModel>;
resolve(options?: IExistingUntitledTextEditorOptions): Promise<IUntitledTextEditorModel>;
}
export interface IUntitledTextEditorService extends IUntitledTextEditorModelManager {
@ -153,8 +152,11 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
return this.get(resource)?.textEditorModel?.getValue();
}
resolve(options?: IInternalUntitledTextEditorOptions): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
return this.doCreateOrGet(options).load();
async resolve(options?: IInternalUntitledTextEditorOptions): Promise<UntitledTextEditorModel> {
const model = this.doCreateOrGet(options);
await model.resolve();
return model;
}
create(options?: IInternalUntitledTextEditorOptions): UntitledTextEditorModel {

View file

@ -62,7 +62,7 @@ suite('Untitled text editors', () => {
const resourcePromise = awaitDidChangeDirty(accessor.untitledTextEditorService);
model.textEditorModel.setValue('foo bar');
model.textEditorModel?.setValue('foo bar');
const resource = await resourcePromise;
@ -147,10 +147,10 @@ suite('Untitled text editors', () => {
// dirty
const model = await input.resolve();
model.textEditorModel.setValue('foo bar');
model.textEditorModel?.setValue('foo bar');
assert.ok(model.isDirty());
assert.ok(workingCopyService.isDirty(model.resource));
model.textEditorModel.setValue('');
model.textEditorModel?.setValue('');
assert.ok(!model.isDirty());
assert.ok(!workingCopyService.isDirty(model.resource));
input.dispose();
@ -196,9 +196,9 @@ suite('Untitled text editors', () => {
// dirty
const model = await input.resolve();
model.textEditorModel.setValue('foo bar');
model.textEditorModel?.setValue('foo bar');
assert.ok(model.isDirty());
model.textEditorModel.setValue('');
model.textEditorModel?.setValue('');
assert.ok(model.isDirty());
input.dispose();
model.dispose();
@ -345,7 +345,7 @@ suite('Untitled text editors', () => {
// label
const model = await input.resolve();
model.textEditorModel.setValue('Foo Bar');
model.textEditorModel?.setValue('Foo Bar');
assert.strictEqual(counter, 1);
input.dispose();
model.dispose();
@ -391,16 +391,16 @@ suite('Untitled text editors', () => {
const model = await input.resolve();
model.onDidChangeContent(() => counter++);
model.textEditorModel.setValue('foo');
model.textEditorModel?.setValue('foo');
assert.strictEqual(counter, 1, 'Dirty model should trigger event');
model.textEditorModel.setValue('bar');
model.textEditorModel?.setValue('bar');
assert.strictEqual(counter, 2, 'Content change when dirty should trigger event');
model.textEditorModel.setValue('');
model.textEditorModel?.setValue('');
assert.strictEqual(counter, 3, 'Manual revert should trigger event');
model.textEditorModel.setValue('foo');
model.textEditorModel?.setValue('foo');
assert.strictEqual(counter, 4, 'Dirty model should trigger event');
@ -417,7 +417,7 @@ suite('Untitled text editors', () => {
const model = await input.resolve();
model.onDidRevert(() => counter++);
model.textEditorModel.setValue('foo');
model.textEditorModel?.setValue('foo');
await model.revert();
@ -434,43 +434,43 @@ suite('Untitled text editors', () => {
let model = await input.resolve();
model.onDidChangeName(() => counter++);
model.textEditorModel.setValue('foo');
model.textEditorModel?.setValue('foo');
assert.strictEqual(input.getName(), 'foo');
assert.strictEqual(model.name, 'foo');
assert.strictEqual(counter, 1);
model.textEditorModel.setValue('bar');
model.textEditorModel?.setValue('bar');
assert.strictEqual(input.getName(), 'bar');
assert.strictEqual(model.name, 'bar');
assert.strictEqual(counter, 2);
model.textEditorModel.setValue('');
model.textEditorModel?.setValue('');
assert.strictEqual(input.getName(), 'Untitled-1');
assert.strictEqual(model.name, 'Untitled-1');
model.textEditorModel.setValue(' ');
model.textEditorModel?.setValue(' ');
assert.strictEqual(input.getName(), 'Untitled-1');
assert.strictEqual(model.name, 'Untitled-1');
model.textEditorModel.setValue('([]}'); // require actual words
model.textEditorModel?.setValue('([]}'); // require actual words
assert.strictEqual(input.getName(), 'Untitled-1');
assert.strictEqual(model.name, 'Untitled-1');
model.textEditorModel.setValue('([]}hello '); // require actual words
model.textEditorModel?.setValue('([]}hello '); // require actual words
assert.strictEqual(input.getName(), '([]}hello');
assert.strictEqual(model.name, '([]}hello');
model.textEditorModel.setValue('12345678901234567890123456789012345678901234567890'); // trimmed at 40chars max
model.textEditorModel?.setValue('12345678901234567890123456789012345678901234567890'); // trimmed at 40chars max
assert.strictEqual(input.getName(), '1234567890123456789012345678901234567890');
assert.strictEqual(model.name, '1234567890123456789012345678901234567890');
model.textEditorModel.setValue('123456789012345678901234567890123456789🌞'); // do not break grapehems (#111235)
model.textEditorModel?.setValue('123456789012345678901234567890123456789🌞'); // do not break grapehems (#111235)
assert.strictEqual(input.getName(), '123456789012345678901234567890123456789');
assert.strictEqual(model.name, '123456789012345678901234567890123456789');
assert.strictEqual(counter, 6);
model.textEditorModel.setValue('Hello\nWorld');
model.textEditorModel?.setValue('Hello\nWorld');
assert.strictEqual(counter, 7);
function createSingleEditOp(text: string, positionLineNumber: number, positionColumn: number, selectionLineNumber: number = positionLineNumber, selectionColumn: number = positionColumn): IIdentifiedSingleEditOperation {
@ -489,7 +489,7 @@ suite('Untitled text editors', () => {
};
}
model.textEditorModel.applyEdits([createSingleEditOp('hello', 2, 2)]);
model.textEditorModel?.applyEdits([createSingleEditOp('hello', 2, 2)]);
assert.strictEqual(counter, 7); // change was not on first line
input.dispose();
@ -513,10 +513,10 @@ suite('Untitled text editors', () => {
const model = await input.resolve();
model.onDidChangeDirty(() => counter++);
model.textEditorModel.setValue('foo');
model.textEditorModel?.setValue('foo');
assert.strictEqual(counter, 1, 'Dirty model should trigger event');
model.textEditorModel.setValue('bar');
model.textEditorModel?.setValue('bar');
assert.strictEqual(counter, 1, 'Another change does not fire event');

View file

@ -146,7 +146,7 @@ suite('WorkingCopyFileService', () => {
let dirty = accessor.workingCopyFileService.getDirty(model1.resource);
assert.strictEqual(dirty.length, 0);
await model1.load();
await model1.resolve();
model1.textEditorModel!.setValue('foo');
dirty = accessor.workingCopyFileService.getDirty(model1.resource);
@ -157,7 +157,7 @@ suite('WorkingCopyFileService', () => {
assert.strictEqual(dirty.length, 1);
assert.strictEqual(dirty[0], model1);
await model2.load();
await model2.resolve();
model2.textEditorModel!.setValue('bar');
dirty = accessor.workingCopyFileService.getDirty(toResource.call(this, '/path'));
@ -170,7 +170,7 @@ suite('WorkingCopyFileService', () => {
test('registerWorkingCopyProvider', async function () {
const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model1.resource, model1);
await model1.load();
await model1.resolve();
model1.textEditorModel!.setValue('foo');
const testWorkingCopy = new TestWorkingCopy(toResource.call(this, '/path/file-2.txt'), true);
@ -327,11 +327,11 @@ suite('WorkingCopyFileService', () => {
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(sourceModel.resource, sourceModel);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(targetModel.resource, targetModel);
await sourceModel.load();
await sourceModel.resolve();
sourceModel.textEditorModel!.setValue('foo' + i);
assert.ok(accessor.textFileService.isDirty(sourceModel.resource));
if (targetDirty) {
await targetModel.load();
await targetModel.resolve();
targetModel.textEditorModel!.setValue('bar' + i);
assert.ok(accessor.textFileService.isDirty(targetModel.resource));
}
@ -420,7 +420,7 @@ suite('WorkingCopyFileService', () => {
const model = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
await model.resolve();
model!.textEditorModel!.setValue('foo');
assert.ok(accessor.workingCopyService.isDirty(model.resource));
return model;
@ -480,7 +480,7 @@ suite('WorkingCopyFileService', () => {
const model = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8', undefined);
(<TestTextFileEditorModelManager>accessor.textFileService.files).add(model.resource, model);
await model.load();
await model.resolve();
model!.textEditorModel!.setValue('foo');
assert.ok(accessor.workingCopyService.isDirty(model.resource));

View file

@ -72,23 +72,21 @@ suite('Workbench editor model', () => {
counter++;
});
const resolvedModel = await model.load();
assert(resolvedModel === model);
assert.strictEqual(resolvedModel.isDisposed(), false);
await model.resolve();
assert.strictEqual(model.isDisposed(), false);
assert.strictEqual(model.isResolved(), true);
model.dispose();
assert.strictEqual(counter, 1);
assert.strictEqual(resolvedModel.isDisposed(), true);
assert.strictEqual(model.isDisposed(), true);
});
test('BaseTextEditorModel', async () => {
let modelService = stubModelService(instantiationService);
const model = new MyTextEditorModel(modelService, modeService);
const resolvedModel = await model.load() as MyTextEditorModel;
await model.resolve();
assert(resolvedModel === model);
resolvedModel.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain');
model.createTextEditorModel(createTextBufferFactory('foo'), null!, 'text/plain');
assert.strictEqual(model.isResolved(), true);
model.dispose();
});