Path: blob/main/src/vs/workbench/common/editor/diffEditorInput.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { localize } from '../../../nls.js';6import { AbstractSideBySideEditorInputSerializer, SideBySideEditorInput } from './sideBySideEditorInput.js';7import { EditorInput, IUntypedEditorOptions } from './editorInput.js';8import { EditorModel } from './editorModel.js';9import { TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity, IEditorDescriptor, IEditorPane, IResourceDiffEditorInput, IUntypedEditorInput, isResourceDiffEditorInput, IDiffEditorInput, IResourceSideBySideEditorInput, EditorInputCapabilities } from '../editor.js';10import { BaseTextEditorModel } from './textEditorModel.js';11import { DiffEditorModel } from './diffEditorModel.js';12import { TextDiffEditorModel } from './textDiffEditorModel.js';13import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';14import { IEditorService } from '../../services/editor/common/editorService.js';15import { shorten } from '../../../base/common/labels.js';16import { isResolvedEditorModel } from '../../../platform/editor/common/editor.js';1718interface IDiffEditorInputLabels {19readonly name: string;2021readonly shortDescription: string | undefined;22readonly mediumDescription: string | undefined;23readonly longDescription: string | undefined;2425readonly forceDescription: boolean;2627readonly shortTitle: string;28readonly mediumTitle: string;29readonly longTitle: string;30}3132/**33* The base editor input for the diff editor. It is made up of two editor inputs, the original version34* and the modified version.35*/36export class DiffEditorInput extends SideBySideEditorInput implements IDiffEditorInput {3738static override readonly ID: string = 'workbench.editors.diffEditorInput';3940override get typeId(): string {41return DiffEditorInput.ID;42}4344override get editorId(): string | undefined {45return this.modified.editorId === this.original.editorId ? this.modified.editorId : undefined;46}4748override get capabilities(): EditorInputCapabilities {49let capabilities = super.capabilities;5051// Force description capability depends on labels52if (this.labels.forceDescription) {53capabilities |= EditorInputCapabilities.ForceDescription;54}5556return capabilities;57}5859private cachedModel: DiffEditorModel | undefined = undefined;6061private readonly labels: IDiffEditorInputLabels;6263constructor(64preferredName: string | undefined,65preferredDescription: string | undefined,66readonly original: EditorInput,67readonly modified: EditorInput,68private readonly forceOpenAsBinary: boolean | undefined,69@IEditorService editorService: IEditorService70) {71super(preferredName, preferredDescription, original, modified, editorService);7273this.labels = this.computeLabels();74}7576private computeLabels(): IDiffEditorInputLabels {7778// Name79let name: string;80let forceDescription = false;81if (this.preferredName) {82name = this.preferredName;83} else {84const originalName = this.original.getName();85const modifiedName = this.modified.getName();8687name = localize('sideBySideLabels', "{0} ↔ {1}", originalName, modifiedName);8889// Enforce description when the names are identical90forceDescription = originalName === modifiedName;91}9293// Description94let shortDescription: string | undefined;95let mediumDescription: string | undefined;96let longDescription: string | undefined;97if (this.preferredDescription) {98shortDescription = this.preferredDescription;99mediumDescription = this.preferredDescription;100longDescription = this.preferredDescription;101} else {102shortDescription = this.computeLabel(this.original.getDescription(Verbosity.SHORT), this.modified.getDescription(Verbosity.SHORT));103longDescription = this.computeLabel(this.original.getDescription(Verbosity.LONG), this.modified.getDescription(Verbosity.LONG));104105// Medium Description: try to be verbose by computing106// a label that resembles the difference between the two107const originalMediumDescription = this.original.getDescription(Verbosity.MEDIUM);108const modifiedMediumDescription = this.modified.getDescription(Verbosity.MEDIUM);109if (110(typeof originalMediumDescription === 'string' && typeof modifiedMediumDescription === 'string') && // we can only `shorten` when both sides are strings...111(originalMediumDescription || modifiedMediumDescription) // ...however never when both sides are empty strings112) {113const [shortenedOriginalMediumDescription, shortenedModifiedMediumDescription] = shorten([originalMediumDescription, modifiedMediumDescription]);114mediumDescription = this.computeLabel(shortenedOriginalMediumDescription, shortenedModifiedMediumDescription);115}116}117118// Title119let shortTitle = this.computeLabel(this.original.getTitle(Verbosity.SHORT) ?? this.original.getName(), this.modified.getTitle(Verbosity.SHORT) ?? this.modified.getName(), ' ↔ ');120let mediumTitle = this.computeLabel(this.original.getTitle(Verbosity.MEDIUM) ?? this.original.getName(), this.modified.getTitle(Verbosity.MEDIUM) ?? this.modified.getName(), ' ↔ ');121let longTitle = this.computeLabel(this.original.getTitle(Verbosity.LONG) ?? this.original.getName(), this.modified.getTitle(Verbosity.LONG) ?? this.modified.getName(), ' ↔ ');122123const preferredTitle = this.getPreferredTitle();124if (preferredTitle) {125shortTitle = `${preferredTitle} (${shortTitle})`;126mediumTitle = `${preferredTitle} (${mediumTitle})`;127longTitle = `${preferredTitle} (${longTitle})`;128}129130return { name, shortDescription, mediumDescription, longDescription, forceDescription, shortTitle, mediumTitle, longTitle };131}132133private computeLabel(originalLabel: string, modifiedLabel: string, separator?: string): string;134private computeLabel(originalLabel: string | undefined, modifiedLabel: string | undefined, separator?: string): string | undefined;135private computeLabel(originalLabel: string | undefined, modifiedLabel: string | undefined, separator = ' - '): string | undefined {136if (!originalLabel || !modifiedLabel) {137return undefined;138}139140if (originalLabel === modifiedLabel) {141return modifiedLabel;142}143144return `${originalLabel}${separator}${modifiedLabel}`;145}146147override getName(): string {148return this.labels.name;149}150151override getDescription(verbosity = Verbosity.MEDIUM): string | undefined {152switch (verbosity) {153case Verbosity.SHORT:154return this.labels.shortDescription;155case Verbosity.LONG:156return this.labels.longDescription;157case Verbosity.MEDIUM:158default:159return this.labels.mediumDescription;160}161}162163override getTitle(verbosity?: Verbosity): string {164switch (verbosity) {165case Verbosity.SHORT:166return this.labels.shortTitle;167case Verbosity.LONG:168return this.labels.longTitle;169default:170case Verbosity.MEDIUM:171return this.labels.mediumTitle;172}173}174175override async resolve(): Promise<EditorModel> {176177// Create Model - we never reuse our cached model if refresh is true because we cannot178// decide for the inputs within if the cached model can be reused or not. There may be179// inputs that need to be loaded again and thus we always recreate the model and dispose180// the previous one - if any.181const resolvedModel = await this.createModel();182this.cachedModel?.dispose();183184this.cachedModel = resolvedModel;185186return this.cachedModel;187}188189override prefersEditorPane<T extends IEditorDescriptor<IEditorPane>>(editorPanes: T[]): T | undefined {190if (this.forceOpenAsBinary) {191return editorPanes.find(editorPane => editorPane.typeId === BINARY_DIFF_EDITOR_ID);192}193194return editorPanes.find(editorPane => editorPane.typeId === TEXT_DIFF_EDITOR_ID);195}196197private async createModel(): Promise<DiffEditorModel> {198199// Join resolve call over two inputs and build diff editor model200const [originalEditorModel, modifiedEditorModel] = await Promise.all([201this.original.resolve(),202this.modified.resolve()203]);204205// If both are text models, return textdiffeditor model206if (modifiedEditorModel instanceof BaseTextEditorModel && originalEditorModel instanceof BaseTextEditorModel) {207return new TextDiffEditorModel(originalEditorModel, modifiedEditorModel);208}209210// Otherwise return normal diff model211return new DiffEditorModel(isResolvedEditorModel(originalEditorModel) ? originalEditorModel : undefined, isResolvedEditorModel(modifiedEditorModel) ? modifiedEditorModel : undefined);212}213214override toUntyped(options?: IUntypedEditorOptions): (IResourceDiffEditorInput & IResourceSideBySideEditorInput) | undefined {215const untyped = super.toUntyped(options);216if (untyped) {217return {218...untyped,219modified: untyped.primary,220original: untyped.secondary221};222}223224return undefined;225}226227override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {228if (this === otherInput) {229return true;230}231232if (otherInput instanceof DiffEditorInput) {233return this.modified.matches(otherInput.modified) && this.original.matches(otherInput.original) && otherInput.forceOpenAsBinary === this.forceOpenAsBinary;234}235236if (isResourceDiffEditorInput(otherInput)) {237return this.modified.matches(otherInput.modified) && this.original.matches(otherInput.original);238}239240return false;241}242243override dispose(): void {244245// Free the diff editor model but do not propagate the dispose() call to the two inputs246// We never created the two inputs (original and modified) so we can not dispose247// them without sideeffects.248if (this.cachedModel) {249this.cachedModel.dispose();250this.cachedModel = undefined;251}252253super.dispose();254}255}256257export class DiffEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {258259protected createEditorInput(instantiationService: IInstantiationService, name: string | undefined, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {260return instantiationService.createInstance(DiffEditorInput, name, description, secondaryInput, primaryInput, undefined);261}262}263264265