提交 cdc24699 编写于 作者: B Benjamin Pasero

untitled - rewrite creation logic

上级 97005d89
......@@ -75,8 +75,8 @@ suite('workspace-namespace', () => {
});
});
test('openTextDocument, untitled is dirty', function () {
return vscode.workspace.openTextDocument(vscode.Uri.parse('untitled:' + join(vscode.workspace.workspaceFolders![0].uri.toString() || '', './newfile.txt'))).then(doc => {
test('openTextDocument, untitled is dirty', async function () {
return vscode.workspace.openTextDocument(vscode.workspace.workspaceFolders![0].uri.with({ scheme: 'untitled', path: posix.join(vscode.workspace.workspaceFolders![0].uri.path, 'newfile.txt') })).then(doc => {
assert.equal(doc.uri.scheme, 'untitled');
assert.ok(doc.isDirty);
});
......
......@@ -211,16 +211,15 @@ export class MainThreadDocuments implements MainThreadDocumentsShape {
// don't create a new file ontop of an existing file
return Promise.reject(new Error('file already exists'));
}, err => {
return this._doCreateUntitled(uri).then(resource => !!resource);
return this._doCreateUntitled(Boolean(uri.path) ? uri : undefined).then(resource => !!resource);
});
}
private _doCreateUntitled(resource?: URI, mode?: string, initialValue?: string): Promise<URI> {
private _doCreateUntitled(associatedResource?: URI, mode?: string, initialValue?: string): Promise<URI> {
return this._textFileService.untitled.resolve({
resource,
associatedResource,
mode,
initialValue,
useResourcePath: Boolean(resource && resource.path)
initialValue
}).then(model => {
const resource = model.resource;
......
......@@ -309,7 +309,7 @@ class DropOverlay extends Themable {
// Open as untitled file with the provided contents
const contents = VSBuffer.wrap(new Uint8Array(event.target.result)).toString();
const untitledEditor = this.textFileService.untitled.create({ resource: proposedFilePath, initialValue: contents });
const untitledEditor = this.textFileService.untitled.create({ associatedResource: proposedFilePath, initialValue: contents });
if (!targetGroup) {
targetGroup = ensureTargetGroup();
......
......@@ -127,12 +127,12 @@ suite('BackupRestorer', () => {
for (const editor of editorService.editors) {
const resource = editor.getResource();
if (isEqual(resource, untitledFile1)) {
const model = await accessor.textFileService.untitled.resolve({ resource });
const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource });
assert.equal(model.textEditorModel.getValue(), 'untitled-1');
model.dispose();
counter++;
} else if (isEqual(resource, untitledFile2)) {
const model = await accessor.textFileService.untitled.resolve({ resource });
const model = await accessor.textFileService.untitled.resolve({ untitledResource: resource });
assert.equal(model.textEditorModel.getValue(), 'untitled-2');
model.dispose();
counter++;
......
......@@ -611,7 +611,8 @@ export class EditorService extends Disposable implements EditorServiceImpl {
const untitledInput = input as IUntitledTextResourceInput;
if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) {
return this.untitledTextEditorService.create({
resource: untitledInput.resource,
untitledResource: untitledInput.resource?.scheme === Schemas.untitled ? untitledInput.resource : undefined,
associatedResource: untitledInput.resource?.scheme !== Schemas.untitled ? untitledInput.resource : undefined,
mode: untitledInput.mode,
initialValue: untitledInput.contents,
encoding: untitledInput.encoding
......
......@@ -141,7 +141,7 @@ export class TextModelResolverService implements ITextModelService {
// Untitled Schema: go through cached input
if (resource.scheme === network.Schemas.untitled) {
const model = await this.untitledTextEditorService.resolve({ resource });
const model = await this.untitledTextEditorService.resolve({ untitledResource: resource });
return new ImmortalReference(model);
}
......
......@@ -6,7 +6,7 @@
import { URI } from 'vs/base/common/uri';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { UntitledTextEditorInput } from 'vs/workbench/common/editor/untitledTextEditorInput';
import { IFilesConfiguration, IFileService } from 'vs/platform/files/common/files';
import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Event, Emitter } from 'vs/base/common/event';
import { ResourceMap } from 'vs/base/common/map';
......@@ -14,16 +14,40 @@ import { Schemas } from 'vs/base/common/network';
import { Disposable } from 'vs/base/common/lifecycle';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { UntitledTextEditorModel, IUntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
import type { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
export const IUntitledTextEditorService = createDecorator<IUntitledTextEditorService>('untitledTextEditorService');
export interface IUntitledTextEditorOptions {
resource?: URI;
mode?: string;
/**
* An optional resource to identify the untitled resource to create or return
* if already existing.
*/
untitledResource?: URI;
/**
* An optional resource to associate with the untitled file. When saving
* the untitled file, the associated resource will be used and the user
* is not being asked to provide a file path.
*/
associatedResource?: URI;
/**
* Initial value of the untitled file. An untitled file with initial
* value is dirty right from the beginning.
*/
initialValue?: string;
/**
* Preferred language mode to use when saving the untitled file.
*/
mode?: string;
/**
* Preferred encoding to use when saving the untitled file.
*/
encoding?: string;
useResourcePath?: boolean;
}
export interface IUntitledTextEditorModelManager {
......@@ -54,13 +78,16 @@ export interface IUntitledTextEditorModelManager {
get(resource: URI): UntitledTextEditorInput | undefined;
/**
* Creates a new untitled input with the provided options.
* Creates a new untitled input with the provided options. If the `untitledResource`
* property is provided and the untitled input exists, it will return that existing
* instance instead of creating a new one.
*/
create(options?: IUntitledTextEditorOptions): UntitledTextEditorInput;
/**
* Resolves an untitled editor model from the provided options. This can either
* return a new or existing model based on the options provided.
* Resolves an untitled editor model from the provided options. If the `untitledResource`
* property is provided and the untitled input exists, it will return that existing
* instance instead of creating a new one.
*/
resolve(options?: IUntitledTextEditorOptions): Promise<IUntitledTextEditorModel & IResolvedTextEditorModel>;
......@@ -93,8 +120,7 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFileService private readonly fileService: IFileService
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
}
......@@ -107,40 +133,61 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
return this.mapResourceToInput.get(resource);
}
resolve(options?: IUntitledTextEditorOptions): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
return this.doCreateOrGet(options).resolve();
}
create(options?: IUntitledTextEditorOptions): UntitledTextEditorInput {
return this.doCreateOrGet(options?.resource, options?.mode, options?.initialValue, options?.encoding, options?.useResourcePath);
return this.doCreateOrGet(options);
}
private doCreateOrGet(resource?: URI, mode?: string, initialValue?: string, encoding?: string, hasAssociatedFilePath: boolean = false): UntitledTextEditorInput {
if (resource) {
private doCreateOrGet(options: IUntitledTextEditorOptions = Object.create(null)): UntitledTextEditorInput {
const massagedOptions = this.massageOptions(options);
// Massage resource if it comes with known file based resource
if (this.fileService.canHandleResource(resource)) {
hasAssociatedFilePath = true;
resource = resource.with({ scheme: Schemas.untitled }); // ensure we have the right scheme
}
// Return existing instance if asked for it
if (massagedOptions.untitledResource && this.mapResourceToInput.has(massagedOptions.untitledResource)) {
return this.mapResourceToInput.get(massagedOptions.untitledResource)!;
}
// Create new instance otherwise
return this.doCreate(massagedOptions);
}
if (hasAssociatedFilePath) {
this.mapResourceToAssociatedFilePath.set(resource, true); // remember for future lookups
private massageOptions(options: IUntitledTextEditorOptions): IUntitledTextEditorOptions {
const massagedOptions: IUntitledTextEditorOptions = Object.create(null);
// Figure out associated and untitled resource
if (options.associatedResource) {
massagedOptions.untitledResource = options.associatedResource.with({ scheme: Schemas.untitled });
massagedOptions.associatedResource = options.associatedResource;
} else {
if (options.untitledResource?.scheme === Schemas.untitled) {
massagedOptions.untitledResource = options.untitledResource;
}
}
// Return existing instance if asked for it
if (resource && this.mapResourceToInput.has(resource)) {
return this.mapResourceToInput.get(resource)!;
// Language mode
if (options.mode) {
massagedOptions.mode = options.mode;
} else if (!massagedOptions.associatedResource) {
const configuration = this.configurationService.getValue<IFilesConfiguration>();
if (configuration.files?.defaultLanguage) {
massagedOptions.mode = configuration.files.defaultLanguage;
}
}
// Create new otherwise
return this.doCreate(resource, hasAssociatedFilePath, mode, initialValue, encoding);
// Take over encoding and initial value
massagedOptions.encoding = options.encoding;
massagedOptions.initialValue = options.initialValue;
return massagedOptions;
}
private doCreate(resource?: URI, hasAssociatedFilePath?: boolean, mode?: string, initialValue?: string, encoding?: string): UntitledTextEditorInput {
let untitledResource: URI;
if (resource) {
untitledResource = resource;
} else {
private doCreate(options: IUntitledTextEditorOptions): UntitledTextEditorInput {
// Create new taking a resource URI that is not already taken
// Create a new untitled resource if none is provided
let untitledResource = options.untitledResource;
if (!untitledResource) {
let counter = this.mapResourceToInput.size + 1;
do {
untitledResource = URI.from({ scheme: Schemas.untitled, path: `Untitled-${counter}` });
......@@ -148,25 +195,21 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
} while (this.mapResourceToInput.has(untitledResource));
}
// Look up default language from settings if any
if (!mode && !hasAssociatedFilePath) {
const configuration = this.configurationService.getValue<IFilesConfiguration>();
if (configuration.files?.defaultLanguage) {
mode = configuration.files.defaultLanguage;
}
}
const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!hasAssociatedFilePath, mode, initialValue, encoding);
// Create new input with provided options
const input = this.instantiationService.createInstance(UntitledTextEditorInput, untitledResource, !!options.associatedResource, options.mode, options.initialValue, options.encoding);
const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(untitledResource));
const encodingListener = input.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(untitledResource));
const disposeListener = input.onDispose(() => this._onDidDisposeModel.fire(untitledResource));
const dirtyListener = input.onDidChangeDirty(() => this._onDidChangeDirty.fire(input.getResource()));
const encodingListener = input.onDidModelChangeEncoding(() => this._onDidChangeEncoding.fire(input.getResource()));
const disposeListener = input.onDispose(() => this._onDidDisposeModel.fire(input.getResource()));
// Remove from cache on dispose
const onceDispose = Event.once(input.onDispose);
onceDispose(() => {
Event.once(input.onDispose)(() => {
// Registry
this.mapResourceToInput.delete(input.getResource());
this.mapResourceToAssociatedFilePath.delete(input.getResource());
// Listeners
dirtyListener.dispose();
encodingListener.dispose();
disposeListener.dispose();
......@@ -174,14 +217,13 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe
// Add to cache
this.mapResourceToInput.set(untitledResource, input);
if (options.associatedResource) {
this.mapResourceToAssociatedFilePath.set(untitledResource, true);
}
return input;
}
resolve(options?: IUntitledTextEditorOptions): Promise<UntitledTextEditorModel & IResolvedTextEditorModel> {
return this.doCreateOrGet(options?.resource, options?.mode, options?.initialValue, options?.encoding, options?.useResourcePath).resolve();
}
hasAssociatedFilePath(resource: URI): boolean {
return this.mapResourceToAssociatedFilePath.has(resource);
}
......
......@@ -46,7 +46,7 @@ suite('Workbench untitled text editors', () => {
const workingCopyService = accessor.workingCopyService;
const input1 = service.create();
assert.equal(input1, service.create({ resource: input1.getResource() }));
assert.equal(input1, service.create({ untitledResource: input1.getResource() }));
assert.equal(service.get(input1.getResource()), input1);
assert.ok(service.exists(input1.getResource()));
......@@ -66,7 +66,7 @@ suite('Workbench untitled text editors', () => {
// dirty
const model = await input2.resolve();
assert.equal(await service.resolve({ resource: input2.getResource() }), model);
assert.equal(await service.resolve({ untitledResource: input2.getResource() }), model);
assert.ok(!input2.isDirty());
......@@ -109,7 +109,7 @@ suite('Workbench untitled text editors', () => {
test('Untitled with associated resource is dirty', () => {
const service = accessor.untitledTextEditorService;
const file = URI.file(join('C:\\', '/foo/file.txt'));
const untitled = service.create({ resource: file });
const untitled = service.create({ associatedResource: file });
assert.ok(service.hasAssociatedFilePath(untitled.getResource()));
assert.equal(untitled.isDirty(), true);
......@@ -150,12 +150,12 @@ suite('Workbench untitled text editors', () => {
const input = service.create();
const model3 = await service.create({ resource: input.getResource() }).resolve();
const model3 = await service.create({ untitledResource: input.getResource() }).resolve();
assert.equal(model3.resource.toString(), input.getResource().toString());
const file = URI.file(join('C:\\', '/foo/file44.txt'));
const model4 = await service.create({ resource: file }).resolve();
const model4 = await service.create({ associatedResource: file }).resolve();
assert.ok(service.hasAssociatedFilePath(model4.resource));
assert.ok(model4.isDirty());
......@@ -177,7 +177,7 @@ suite('Workbench untitled text editors', () => {
test('Untitled with associated path remains dirty when content gets empty', async () => {
const service = accessor.untitledTextEditorService;
const file = URI.file(join('C:\\', '/foo/file.txt'));
const input = service.create({ resource: file });
const input = service.create({ associatedResource: file });
// dirty
const model = await input.resolve();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册