diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 28437a50bab..413c53b8155 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -16,7 +16,7 @@ import { isEqual, isEqualOrParent, toLocalResource } from 'vs/base/common/resour import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { FileOperation, FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; +import { FileOperation, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUndoRedoService, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; @@ -445,16 +445,12 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom private readonly _onDidChangeContent: Emitter = this._register(new Emitter()); readonly onDidChangeContent: Event = this._onDidChangeContent.event; - readonly onDidChangeEditable = Event.None; + readonly onDidChangeReadonly = Event.None; //#endregion - public isEditable(): boolean { - return this._editable; - } - - public isOnReadonlyFileSystem(): boolean { - return this.fileService.hasCapability(this.editorResource, FileSystemProviderCapabilities.Readonly); + public isReadonly(): boolean { + return !this._editable; } public get viewType() { diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 66e0948e66e..c0564f712ef 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -16,9 +16,6 @@ import { dirname, isEqual } from 'vs/base/common/resources'; */ export abstract class AbstractResourceEditorInput extends EditorInput implements IEditorInputWithPreferredResource { - private _preferredResource: URI; - get preferredResource(): URI { return this._preferredResource; } - override get capabilities(): EditorInputCapabilities { let capabilities = EditorInputCapabilities.None; @@ -33,6 +30,9 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements return capabilities; } + private _preferredResource: URI; + get preferredResource(): URI { return this._preferredResource; } + constructor( readonly resource: URI, preferredResource: URI | undefined, @@ -48,7 +48,7 @@ export abstract class AbstractResourceEditorInput extends EditorInput implements private registerListeners(): void { - // Clear label memoizer on certain events that have impact + // Clear our labels on certain label related events this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme))); this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme))); this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme))); diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index 6eb10a4f271..2eafa406afd 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -105,7 +105,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi override getTelemetryDescriptor(): { [key: string]: unknown } { const descriptor = this.primary.getTelemetryDescriptor(); - return Object.assign(descriptor, super.getTelemetryDescriptor()); + return { ...descriptor, ...super.getTelemetryDescriptor() }; } override isDirty(): boolean { @@ -129,7 +129,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi } override matches(otherInput: unknown): boolean { - if (otherInput === this) { + if (super.matches(otherInput)) { return true; } diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index ac8d06b4c4f..487c19fe391 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -202,7 +202,7 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp } override matches(otherInput: unknown): boolean { - if (otherInput === this) { + if (super.matches(otherInput)) { return true; } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index ed23e79dbac..9c390babf04 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { VSBuffer } from 'vs/base/common/buffer'; -import { memoize } from 'vs/base/common/decorators'; import { IReference } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; -import { isEqual } from 'vs/base/common/resources'; +import { dirname, isEqual } from 'vs/base/common/resources'; import { assertIsDefined } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; @@ -79,6 +79,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { @IFileDialogService private readonly fileDialogService: IFileDialogService, @IEditorService private readonly editorService: IEditorService, @IUndoRedoService private readonly undoRedoService: IUndoRedoService, + @IFileService private readonly fileService: IFileService ) { super(id, viewType, '', webview, webviewWorkbenchService); this._editorResource = resource; @@ -86,6 +87,36 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._defaultDirtyState = options.startsDirty; this._backupId = options.backupId; this._untitledDocumentData = options.untitledDocumentData; + + this.registerListeners(); + } + + private registerListeners(): void { + + // Clear our labels on certain label related events + this._register(this.labelService.onDidChangeFormatters(e => this.onLabelEvent(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderRegistrations(e => this.onLabelEvent(e.scheme))); + this._register(this.fileService.onDidChangeFileSystemProviderCapabilities(e => this.onLabelEvent(e.scheme))); + } + + private onLabelEvent(scheme: string): void { + if (scheme === this.resource.scheme) { + this.updateLabel(); + } + } + + private updateLabel(): void { + + // Clear any cached labels from before + this._shortDescription = undefined; + this._mediumDescription = undefined; + this._longDescription = undefined; + this._shortTitle = undefined; + this._mediumTitle = undefined; + this._longTitle = undefined; + + // Trigger recompute of label + this._onDidChangeLabel.fire(); } public override get typeId(): string { @@ -99,8 +130,14 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { capabilities |= EditorInputCapabilities.Singleton; } - if (this._modelRef && !this._modelRef.object.isEditable()) { - capabilities |= EditorInputCapabilities.Readonly; + if (this._modelRef) { + if (this._modelRef.object.isReadonly()) { + capabilities |= EditorInputCapabilities.Readonly; + } + } else { + if (this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly)) { + capabilities |= EditorInputCapabilities.Readonly; + } } if (this.resource.scheme === Schemas.untitled) { @@ -115,56 +152,101 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return this.decorateLabel(name); } - override matches(other: IEditorInput): boolean { + override getDescription(verbosity = Verbosity.MEDIUM): string | undefined { + switch (verbosity) { + case Verbosity.SHORT: + return this.shortDescription; + case Verbosity.LONG: + return this.longDescription; + case Verbosity.MEDIUM: + default: + return this.mediumDescription; + } + } + + private _shortDescription: string | undefined = undefined; + private get shortDescription(): string { + if (typeof this._shortDescription !== 'string') { + this._shortDescription = this.labelService.getUriBasenameLabel(dirname(this.resource)); + } + + return this._shortDescription; + } + + private _mediumDescription: string | undefined = undefined; + private get mediumDescription(): string { + if (typeof this._mediumDescription !== 'string') { + this._mediumDescription = this.labelService.getUriLabel(dirname(this.resource), { relative: true }); + } + + return this._mediumDescription; + } + + private _longDescription: string | undefined = undefined; + private get longDescription(): string { + if (typeof this._longDescription !== 'string') { + this._longDescription = this.labelService.getUriLabel(dirname(this.resource)); + } + + return this._longDescription; + } + + private _shortTitle: string | undefined = undefined; + private get shortTitle(): string { + if (typeof this._shortTitle !== 'string') { + this._shortTitle = this.getName(); + } + + return this._shortTitle; + } + + private _mediumTitle: string | undefined = undefined; + private get mediumTitle(): string { + if (typeof this._mediumTitle !== 'string') { + this._mediumTitle = this.labelService.getUriLabel(this.resource, { relative: true }); + } + + return this._mediumTitle; + } + + private _longTitle: string | undefined = undefined; + private get longTitle(): string { + if (typeof this._longTitle !== 'string') { + this._longTitle = this.labelService.getUriLabel(this.resource); + } + + return this._longTitle; + } + + override getTitle(verbosity?: Verbosity): string { + switch (verbosity) { + case Verbosity.SHORT: + return this.decorateLabel(this.shortTitle); + case Verbosity.LONG: + return this.decorateLabel(this.longTitle); + default: + case Verbosity.MEDIUM: + return this.decorateLabel(this.mediumTitle); + } + } + + private decorateLabel(label: string): string { + const readonly = this.hasCapability(EditorInputCapabilities.Readonly); + const orphaned = !!this._modelRef?.object.isOrphaned(); + + return decorateFileEditorLabel(label, { orphaned, readonly }); + } + + public override matches(other: IEditorInput): boolean { return this === other || (other instanceof CustomEditorInput && this.viewType === other.viewType && isEqual(this.resource, other.resource)); } - override copy(): IEditorInput { + public override copy(): IEditorInput { return CustomEditorInput.create(this.instantiationService, this.resource, this.viewType, this.group, this.webview.options); } - @memoize - private get shortTitle(): string { - return this.getName(); - } - - @memoize - private get mediumTitle(): string { - return this.labelService.getUriLabel(this.resource, { relative: true }); - } - - @memoize - private get longTitle(): string { - return this.labelService.getUriLabel(this.resource); - } - - public override getTitle(verbosity?: Verbosity): string { - switch (verbosity) { - case Verbosity.SHORT: - return this.decorateLabel(this.shortTitle); - default: - case Verbosity.MEDIUM: - return this.decorateLabel(this.mediumTitle); - case Verbosity.LONG: - return this.decorateLabel(this.longTitle); - } - } - - private decorateLabel(label: string): string { - const orphaned = !!this._modelRef?.object.isOrphaned(); - - const readonly = this._modelRef - ? this._modelRef.object.isEditable() && this._modelRef.object.isOnReadonlyFileSystem() - : false; - - return decorateFileEditorLabel(label, { - orphaned, - readonly - }); - } - public override isDirty(): boolean { if (!this._modelRef) { return !!this._defaultDirtyState; @@ -226,7 +308,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { this._modelRef = this._register(assertIsDefined(await this.customEditorService.models.tryRetain(this.resource, this.viewType))); this._register(this._modelRef.object.onDidChangeDirty(() => this._onDidChangeDirty.fire())); this._register(this._modelRef.object.onDidChangeOrphaned(() => this._onDidChangeLabel.fire())); - this._register(this._modelRef.object.onDidChangeEditable(() => this._onDidChangeCapabilities.fire())); + this._register(this._modelRef.object.onDidChangeReadonly(() => this._onDidChangeCapabilities.fire())); // If we're loading untitled file data we should ensure it's dirty if (this._untitledDocumentData) { this._defaultDirtyState = true; @@ -239,7 +321,7 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return null; } - override rename(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { + public override rename(group: GroupIdentifier, newResource: URI): { editor: IEditorInput } | undefined { // See if we can keep using the same custom editor provider const editorInfo = this.customEditorService.getCustomEditor(this.viewType); if (editorInfo?.matches(newResource)) { @@ -293,18 +375,18 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return other; } - get backupId(): string | undefined { + public get backupId(): string | undefined { if (this._modelRef) { return this._modelRef.object.backupId; } return this._backupId; } - get untitledDocumentData(): VSBuffer | undefined { + public get untitledDocumentData(): VSBuffer | undefined { return this._untitledDocumentData; } - override asResourceEditorInput(groupId: GroupIdentifier): IResourceEditorInput { + public override asResourceEditorInput(groupId: GroupIdentifier): IResourceEditorInput { return { resource: this.resource, options: { diff --git a/src/vs/workbench/contrib/customEditor/common/customEditor.ts b/src/vs/workbench/contrib/customEditor/common/customEditor.ts index 2b385de28bb..dbd675070a9 100644 --- a/src/vs/workbench/contrib/customEditor/common/customEditor.ts +++ b/src/vs/workbench/contrib/customEditor/common/customEditor.ts @@ -57,9 +57,8 @@ export interface ICustomEditorModel extends IDisposable { readonly resource: URI; readonly backupId: string | undefined; - isEditable(): boolean; - readonly onDidChangeEditable: Event; - isOnReadonlyFileSystem(): boolean; + isReadonly(): boolean; + readonly onDidChangeReadonly: Event; isOrphaned(): boolean; readonly onDidChangeOrphaned: Event; diff --git a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts index 2f03131d716..32c0e0189db 100644 --- a/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts +++ b/src/vs/workbench/contrib/customEditor/common/customTextEditorModel.ts @@ -8,7 +8,6 @@ import { Disposable, IReference } from 'vs/base/common/lifecycle'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; -import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; import { ICustomEditorModel } from 'vs/workbench/contrib/customEditor/common/customEditor'; @@ -33,15 +32,14 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo private readonly _onDidChangeOrphaned = this._register(new Emitter()); public readonly onDidChangeOrphaned = this._onDidChangeOrphaned.event; - private readonly _onDidChangeEditable = this._register(new Emitter()); - public readonly onDidChangeEditable = this._onDidChangeEditable.event; + private readonly _onDidChangeReadonly = this._register(new Emitter()); + public readonly onDidChangeReadonly = this._onDidChangeReadonly.event; constructor( public readonly viewType: string, private readonly _resource: URI, private readonly _model: IReference, - @ITextFileService private readonly textFileService: ITextFileService, - @IFileService private readonly _fileService: IFileService, + @ITextFileService private readonly textFileService: ITextFileService ) { super(); @@ -50,7 +48,7 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo this._textFileModel = this.textFileService.files.get(_resource); if (this._textFileModel) { this._register(this._textFileModel.onDidChangeOrphaned(() => this._onDidChangeOrphaned.fire())); - this._register(this._textFileModel.onDidChangeReadonly(() => this._onDidChangeEditable.fire())); + this._register(this._textFileModel.onDidChangeReadonly(() => this._onDidChangeReadonly.fire())); } this._register(this.textFileService.files.onDidChangeDirty(e => { @@ -65,12 +63,8 @@ export class CustomTextEditorModel extends Disposable implements ICustomEditorMo return this._resource; } - public isEditable(): boolean { - return !this._model.object.isReadonly(); - } - - public isOnReadonlyFileSystem(): boolean { - return this._fileService.hasCapability(this._resource, FileSystemProviderCapabilities.Readonly); + public isReadonly(): boolean { + return this._model.object.isReadonly(); } public get backupId() { diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index 9ef036848ee..d6fb7f6da6b 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -403,7 +403,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements } override matches(otherInput: unknown): boolean { - if (otherInput === this) { + if (super.matches(otherInput)) { return true; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts index fee828c2e73..b8a16e1cf8b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookDiffEditorInput.ts @@ -16,6 +16,7 @@ import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebo import { IReference } from 'vs/base/common/lifecycle'; import { INotebookDiffEditorModel, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Schemas } from 'vs/base/common/network'; +import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; interface NotebookEditorInputOptions { startDirty?: boolean; @@ -70,7 +71,8 @@ export class NotebookDiffEditorInput extends EditorInput { public readonly options: NotebookEditorInputOptions, @INotebookService private readonly _notebookService: INotebookService, @INotebookEditorModelResolverService private readonly _notebookModelResolverService: INotebookEditorModelResolverService, - @IFileDialogService private readonly _fileDialogService: IFileDialogService + @IFileDialogService private readonly _fileDialogService: IFileDialogService, + @IFileService private readonly _fileService: IFileService ) { super(); this._defaultDirtyState = !!options.startDirty; @@ -87,6 +89,16 @@ export class NotebookDiffEditorInput extends EditorInput { capabilities |= EditorInputCapabilities.Untitled; } + if (this._modifiedTextModel) { + if (this._modifiedTextModel.object.isReadonly()) { + capabilities |= EditorInputCapabilities.Readonly; + } + } else { + if (this._fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly)) { + capabilities |= EditorInputCapabilities.Readonly; + } + } + return capabilities; } @@ -214,7 +226,7 @@ ${patterns} } override matches(otherInput: unknown): boolean { - if (this === otherInput) { + if (super.matches(otherInput)) { return true; } if (otherInput instanceof NotebookDiffEditorInput) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index ed4efbd3d89..7810d7e0546 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -210,7 +210,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } override matches(otherInput: unknown): boolean { - if (this === otherInput) { + if (super.matches(otherInput)) { return true; } if (otherInput instanceof NotebookEditorInput) { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts index e5c62d1ac65..908e0244241 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts @@ -214,7 +214,7 @@ export class SearchEditorInput extends EditorInput { } override matches(other: unknown) { - if (this === other) { return true; } + if (super.matches(other)) { return true; } if (other instanceof SearchEditorInput) { return !!(other.modelUri.fragment && other.modelUri.fragment === this.modelUri.fragment) || !!(other.backingUri && isEqual(other.backingUri, this.backingUri)); diff --git a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts index de39f3dee71..30f595f01d2 100644 --- a/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput.ts @@ -131,10 +131,7 @@ export class WalkThroughInput extends EditorInput { } if (otherInput instanceof WalkThroughInput) { - let otherResourceEditorInput = otherInput; - - // Compare by properties - return isEqual(otherResourceEditorInput.options.resource, this.options.resource); + return isEqual(otherInput.options.resource, this.options.resource); } return false; diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 6334cb6efd0..bb3d872419c 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -135,7 +135,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp } override matches(otherInput: unknown): boolean { - if (otherInput === this) { + if (super.matches(otherInput)) { return true; }