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

fix #102627

上级 0acc9961
......@@ -256,7 +256,7 @@ export class BreadcrumbsControl {
input = input.primary;
}
if (Registry.as<IEditorInputFactoryRegistry>(Extensions.EditorInputFactories).getFileEditorInputFactory().isFileEditorInput(input)) {
fileInfoUri = input.label;
fileInfoUri = input.preferredResource;
}
this.domNode.classList.toggle('hidden', false);
......
......@@ -167,7 +167,7 @@ export interface IFileEditorInputFactory {
/**
* Creates new new editor input capable of showing files.
*/
createFileEditorInput(resource: URI, label: URI | undefined, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
createFileEditorInput(resource: URI, preferredResource: URI | undefined, encoding: string | undefined, mode: string | undefined, instantiationService: IInstantiationService): IFileEditorInput;
/**
* Check if the provided object is a file editor input.
......@@ -649,20 +649,25 @@ export interface IModeSupport {
export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeSupport {
/**
* Gets the resource this file input is about.
* Gets the resource this file input is about. This will always be the
* canonical form of the resource, so it may differ from the original
* resource that was provided to create the input. Use `preferredResource`
* for the form as it was created.
*/
readonly resource: URI;
/**
* Gets the label of the editor. In most cases this will
* be identical to the resource.
* Gets the preferred resource of the editor. In most cases this will
* be identical to the resource. But in some cases the preferredResource
* may differ in path casing to the actual resource because we keep
* canonical forms of resources in-memory.
*/
readonly label: URI;
readonly preferredResource: URI;
/**
* Sets the preferred label to use for this file input.
* Sets the preferred resource to use for this file input.
*/
setLabel(label: URI): void;
setPreferredResource(preferredResource: URI): void;
/**
* Sets the preferred encoding to use for this file input.
......
......@@ -22,12 +22,12 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
private static readonly MEMOIZER = createMemoizer();
private _label: URI;
get label(): URI { return this._label; }
private _preferredResource: URI;
get preferredResource(): URI { return this._preferredResource; }
constructor(
public readonly resource: URI,
preferredLabel: URI | undefined,
preferredResource: URI | undefined,
@IEditorService protected readonly editorService: IEditorService,
@IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService,
@ITextFileService protected readonly textFileService: ITextFileService,
......@@ -37,7 +37,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
) {
super();
this._label = preferredLabel || resource;
this._preferredResource = preferredResource || resource;
this.registerListeners();
}
......@@ -51,7 +51,7 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
}
private onLabelEvent(scheme: string): void {
if (scheme === this._label.scheme) {
if (scheme === this._preferredResource.scheme) {
this.updateLabel();
}
}
......@@ -65,25 +65,21 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
this._onDidChangeLabel.fire();
}
setLabel(label: URI): void {
if (!extUri.isEqual(label, this._label)) {
this._label = label;
setPreferredResource(preferredResource: URI): void {
if (!extUri.isEqual(preferredResource, this._preferredResource)) {
this._preferredResource = preferredResource;
this.updateLabel();
}
}
getLabel(): URI {
return this._label;
}
getName(): string {
return this.basename;
}
@AbstractTextResourceEditorInput.MEMOIZER
private get basename(): string {
return this.labelService.getUriBasenameLabel(this._label);
return this.labelService.getUriBasenameLabel(this._preferredResource);
}
getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | undefined {
......@@ -100,17 +96,17 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
@AbstractTextResourceEditorInput.MEMOIZER
private get shortDescription(): string {
return this.labelService.getUriBasenameLabel(dirname(this._label));
return this.labelService.getUriBasenameLabel(dirname(this._preferredResource));
}
@AbstractTextResourceEditorInput.MEMOIZER
private get mediumDescription(): string {
return this.labelService.getUriLabel(dirname(this._label), { relative: true });
return this.labelService.getUriLabel(dirname(this._preferredResource), { relative: true });
}
@AbstractTextResourceEditorInput.MEMOIZER
private get longDescription(): string {
return this.labelService.getUriLabel(dirname(this._label));
return this.labelService.getUriLabel(dirname(this._preferredResource));
}
@AbstractTextResourceEditorInput.MEMOIZER
......@@ -120,12 +116,12 @@ export abstract class AbstractTextResourceEditorInput extends EditorInput {
@AbstractTextResourceEditorInput.MEMOIZER
private get mediumTitle(): string {
return this.labelService.getUriLabel(this._label, { relative: true });
return this.labelService.getUriLabel(this._preferredResource, { relative: true });
}
@AbstractTextResourceEditorInput.MEMOIZER
private get longTitle(): string {
return this.labelService.getUriLabel(this._label);
return this.labelService.getUriLabel(this._preferredResource);
}
getTitle(verbosity: Verbosity): string {
......
......@@ -102,8 +102,8 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
// Register default file input factory
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerFileEditorInputFactory({
createFileEditorInput: (resource, label, encoding, mode, instantiationService): IFileEditorInput => {
return instantiationService.createInstance(FileEditorInput, resource, label, encoding, mode);
createFileEditorInput: (resource, preferredResource, encoding, mode, instantiationService): IFileEditorInput => {
return instantiationService.createInstance(FileEditorInput, resource, preferredResource, encoding, mode);
},
isFileEditorInput: (obj): obj is IFileEditorInput => {
......@@ -113,7 +113,7 @@ Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactor
interface ISerializedFileEditorInput {
resourceJSON: UriComponents;
labelJSON?: UriComponents;
preferredResourceJSON?: UriComponents;
encoding?: string;
modeId?: string;
}
......@@ -128,10 +128,10 @@ class FileEditorInputFactory implements IEditorInputFactory {
serialize(editorInput: EditorInput): string {
const fileEditorInput = <FileEditorInput>editorInput;
const resource = fileEditorInput.resource;
const label = fileEditorInput.getLabel();
const preferredResource = fileEditorInput.preferredResource;
const serializedFileEditorInput: ISerializedFileEditorInput = {
resourceJSON: resource.toJSON(),
labelJSON: extUri.isEqual(resource, label) ? undefined : label, // only storing label if it differs from the resource
preferredResourceJSON: extUri.isEqual(resource, preferredResource) ? undefined : preferredResource, // only storing preferredResource if it differs from the resource
encoding: fileEditorInput.getEncoding(),
modeId: fileEditorInput.getPreferredMode() // only using the preferred user associated mode here if available to not store redundant data
};
......@@ -143,13 +143,18 @@ class FileEditorInputFactory implements IEditorInputFactory {
return instantiationService.invokeFunction<FileEditorInput>(accessor => {
const serializedFileEditorInput: ISerializedFileEditorInput = JSON.parse(serializedEditorInput);
const resource = URI.revive(serializedFileEditorInput.resourceJSON);
const label = URI.revive(serializedFileEditorInput.labelJSON);
const preferredResource = URI.revive(serializedFileEditorInput.preferredResourceJSON);
const encoding = serializedFileEditorInput.encoding;
const mode = serializedFileEditorInput.modeId;
const fileEditorInput = accessor.get(IEditorService).createEditorInput({ resource, encoding, mode, forceFile: true }) as FileEditorInput;
if (label) {
fileEditorInput.setLabel(label);
const fileEditorInput = accessor.get(IEditorService).createEditorInput({
resource: preferredResource || resource, // prefer the preferred resource when creating the input again (https://github.com/microsoft/vscode/issues/102627)
encoding,
mode,
forceFile: true
}) as FileEditorInput;
if (preferredResource) {
fileEditorInput.setPreferredResource(preferredResource);
}
return fileEditorInput;
......
......@@ -45,7 +45,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
constructor(
resource: URI,
preferredLabel: URI | undefined,
preferredResource: URI | undefined,
preferredEncoding: string | undefined,
preferredMode: string | undefined,
@IInstantiationService private readonly instantiationService: IInstantiationService,
......@@ -57,7 +57,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService
) {
super(resource, preferredLabel, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService);
super(resource, preferredResource, editorService, editorGroupService, textFileService, labelService, fileService, filesConfigurationService);
this.model = this.textFileService.files.get(resource);
......
......@@ -9,7 +9,7 @@ import { toResource } from 'vs/base/test/common/utils';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { workbenchInstantiationService, TestServiceAccessor, TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EncodingMode, Verbosity } from 'vs/workbench/common/editor';
import { EncodingMode, IEditorInputFactoryRegistry, Verbosity, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
import { TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
......@@ -18,6 +18,7 @@ import { ModesRegistry, PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRe
import { DisposableStore } from 'vs/base/common/lifecycle';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { Registry } from 'vs/platform/registry/common/platform';
suite('Files - FileEditorInput', () => {
let instantiationService: IInstantiationService;
......@@ -92,18 +93,32 @@ suite('Files - FileEditorInput', () => {
}
});
test('label', function () {
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined, undefined);
test('preferred resource', function () {
const resource = toResource.call(this, '/foo/bar/updatefile.js');
const preferredResource = toResource.call(this, '/foo/bar/UPDATEFILE.js');
const inputWithoutPreferredResource = instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined);
assert.equal(inputWithoutPreferredResource.resource.toString(), resource.toString());
assert.equal(inputWithoutPreferredResource.preferredResource.toString(), resource.toString());
const inputWithPreferredResource = instantiationService.createInstance(FileEditorInput, resource, preferredResource, undefined, undefined);
assert.equal(inputWithPreferredResource.resource.toString(), resource.toString());
assert.equal(inputWithPreferredResource.preferredResource.toString(), preferredResource.toString());
let didChangeLabel = false;
const listener = input.onDidChangeLabel(e => {
const listener = inputWithPreferredResource.onDidChangeLabel(e => {
didChangeLabel = true;
});
assert.equal(input.getName(), 'UPDATEFILE.js');
assert.equal(inputWithPreferredResource.getName(), 'UPDATEFILE.js');
input.setLabel(toResource.call(this, '/FOO/BAR/updateFILE.js'));
assert.equal(input.getName(), 'updateFILE.js');
const otherPreferredResource = toResource.call(this, '/FOO/BAR/updateFILE.js');
inputWithPreferredResource.setPreferredResource(otherPreferredResource);
assert.equal(inputWithPreferredResource.resource.toString(), resource.toString());
assert.equal(inputWithPreferredResource.preferredResource.toString(), otherPreferredResource.toString());
assert.equal(inputWithPreferredResource.getName(), 'updateFILE.js');
assert.equal(didChangeLabel, true);
listener.dispose();
......@@ -239,4 +254,37 @@ suite('Files - FileEditorInput', () => {
resolved.dispose();
});
test('file editor input factory', async function () {
instantiationService.invokeFunction(accessor => Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).start(accessor));
const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined, undefined, undefined);
const factory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getEditorInputFactory(input.getTypeId());
if (!factory) {
assert.fail('File Editor Input Factory missing');
}
assert.equal(factory.canSerialize(input), true);
const inputSerialized = factory.serialize(input);
if (!inputSerialized) {
assert.fail('Unexpected serialized file input');
}
const inputDeserialized = factory.deserialize(instantiationService, inputSerialized);
assert.equal(input.matches(inputDeserialized), true);
const preferredResource = toResource.call(this, '/foo/bar/UPDATEfile.js');
const inputWithPreferredResource = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), preferredResource, undefined, undefined);
const inputWithPreferredResourceSerialized = factory.serialize(inputWithPreferredResource);
if (!inputWithPreferredResourceSerialized) {
assert.fail('Unexpected serialized file input');
}
const inputWithPreferredResourceDeserialized = factory.deserialize(instantiationService, inputWithPreferredResourceSerialized) as FileEditorInput;
assert.equal(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.resource.toString());
assert.equal(inputWithPreferredResource.preferredResource.toString(), inputWithPreferredResourceDeserialized.preferredResource.toString());
});
});
......@@ -874,16 +874,21 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Derive the label from the path if not provided explicitly
const label = resourceEditorInput.label || basename(resourceEditorInput.resource);
// We keep track of the preferred resource this input is to be created
// with but it may be different from the canonical resource (see below)
const preferredResource = resourceEditorInput.resource;
// From this moment on, only operate on the canonical resource
// to ensure we reduce the chance of opening the same resource
// with different resource forms (e.g. path casing on Windows)
const canonicalResource = this.asCanonicalEditorResource(resourceEditorInput.resource);
const canonicalResource = this.asCanonicalEditorResource(preferredResource);
return this.createOrGetCached(canonicalResource, () => {
// File
if (resourceEditorInput.forceFile /* fix for https://github.com/Microsoft/vscode/issues/48275 */ || this.fileService.canHandleResource(canonicalResource)) {
return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, resourceEditorInput.resource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService);
if (resourceEditorInput.forceFile || this.fileService.canHandleResource(canonicalResource)) {
return this.fileEditorInputFactory.createFileEditorInput(canonicalResource, preferredResource, resourceEditorInput.encoding, resourceEditorInput.mode, this.instantiationService);
}
// Resource
......@@ -897,7 +902,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Files
else if (!(cachedInput instanceof ResourceEditorInput)) {
cachedInput.setLabel(resourceEditorInput.resource);
cachedInput.setPreferredResource(preferredResource);
if (resourceEditorInput.encoding) {
cachedInput.setPreferredEncoding(resourceEditorInput.encoding);
......
......@@ -150,14 +150,14 @@ class NonSerializableTestEditorInput extends EditorInput {
class TestFileEditorInput extends EditorInput implements IFileEditorInput {
readonly label = this.resource;
readonly preferredResource = this.resource;
constructor(public id: string, public resource: URI) {
super();
}
getTypeId() { return 'testFileEditorInputForGroups'; }
resolve(): Promise<IEditorModel> { return Promise.resolve(null!); }
setLabel(label: URI): void { }
setPreferredResource(resource: URI): void { }
setEncoding(encoding: string) { }
getEncoding() { return undefined; }
setPreferredEncoding(encoding: string) { }
......
......@@ -1123,7 +1123,7 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor<EditorInpu
export class TestFileEditorInput extends EditorInput implements IFileEditorInput {
readonly label = this.resource;
readonly preferredResource = this.resource;
gotDisposed = false;
gotSaved = false;
......@@ -1142,7 +1142,7 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput
getTypeId() { return this.typeId; }
resolve(): Promise<IEditorModel | null> { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); }
matches(other: EditorInput): boolean { return !!(other?.resource && this.resource.toString() === other.resource.toString() && other instanceof TestFileEditorInput && other.getTypeId() === this.typeId); }
setLabel(label: URI): void { }
setPreferredResource(resource: URI): void { }
setEncoding(encoding: string) { }
getEncoding() { return undefined; }
setPreferredEncoding(encoding: string) { }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册