Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/common/editor/diffEditorInput.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { localize } from '../../../nls.js';
7
import { AbstractSideBySideEditorInputSerializer, SideBySideEditorInput } from './sideBySideEditorInput.js';
8
import { EditorInput, IUntypedEditorOptions } from './editorInput.js';
9
import { EditorModel } from './editorModel.js';
10
import { TEXT_DIFF_EDITOR_ID, BINARY_DIFF_EDITOR_ID, Verbosity, IEditorDescriptor, IEditorPane, IResourceDiffEditorInput, IUntypedEditorInput, isResourceDiffEditorInput, IDiffEditorInput, IResourceSideBySideEditorInput, EditorInputCapabilities } from '../editor.js';
11
import { BaseTextEditorModel } from './textEditorModel.js';
12
import { DiffEditorModel } from './diffEditorModel.js';
13
import { TextDiffEditorModel } from './textDiffEditorModel.js';
14
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
15
import { IEditorService } from '../../services/editor/common/editorService.js';
16
import { shorten } from '../../../base/common/labels.js';
17
import { isResolvedEditorModel } from '../../../platform/editor/common/editor.js';
18
19
interface IDiffEditorInputLabels {
20
readonly name: string;
21
22
readonly shortDescription: string | undefined;
23
readonly mediumDescription: string | undefined;
24
readonly longDescription: string | undefined;
25
26
readonly forceDescription: boolean;
27
28
readonly shortTitle: string;
29
readonly mediumTitle: string;
30
readonly longTitle: string;
31
}
32
33
/**
34
* The base editor input for the diff editor. It is made up of two editor inputs, the original version
35
* and the modified version.
36
*/
37
export class DiffEditorInput extends SideBySideEditorInput implements IDiffEditorInput {
38
39
static override readonly ID: string = 'workbench.editors.diffEditorInput';
40
41
override get typeId(): string {
42
return DiffEditorInput.ID;
43
}
44
45
override get editorId(): string | undefined {
46
return this.modified.editorId === this.original.editorId ? this.modified.editorId : undefined;
47
}
48
49
override get capabilities(): EditorInputCapabilities {
50
let capabilities = super.capabilities;
51
52
// Force description capability depends on labels
53
if (this.labels.forceDescription) {
54
capabilities |= EditorInputCapabilities.ForceDescription;
55
}
56
57
return capabilities;
58
}
59
60
private cachedModel: DiffEditorModel | undefined = undefined;
61
62
private readonly labels: IDiffEditorInputLabels;
63
64
constructor(
65
preferredName: string | undefined,
66
preferredDescription: string | undefined,
67
readonly original: EditorInput,
68
readonly modified: EditorInput,
69
private readonly forceOpenAsBinary: boolean | undefined,
70
@IEditorService editorService: IEditorService
71
) {
72
super(preferredName, preferredDescription, original, modified, editorService);
73
74
this.labels = this.computeLabels();
75
}
76
77
private computeLabels(): IDiffEditorInputLabels {
78
79
// Name
80
let name: string;
81
let forceDescription = false;
82
if (this.preferredName) {
83
name = this.preferredName;
84
} else {
85
const originalName = this.original.getName();
86
const modifiedName = this.modified.getName();
87
88
name = localize('sideBySideLabels', "{0} ↔ {1}", originalName, modifiedName);
89
90
// Enforce description when the names are identical
91
forceDescription = originalName === modifiedName;
92
}
93
94
// Description
95
let shortDescription: string | undefined;
96
let mediumDescription: string | undefined;
97
let longDescription: string | undefined;
98
if (this.preferredDescription) {
99
shortDescription = this.preferredDescription;
100
mediumDescription = this.preferredDescription;
101
longDescription = this.preferredDescription;
102
} else {
103
shortDescription = this.computeLabel(this.original.getDescription(Verbosity.SHORT), this.modified.getDescription(Verbosity.SHORT));
104
longDescription = this.computeLabel(this.original.getDescription(Verbosity.LONG), this.modified.getDescription(Verbosity.LONG));
105
106
// Medium Description: try to be verbose by computing
107
// a label that resembles the difference between the two
108
const originalMediumDescription = this.original.getDescription(Verbosity.MEDIUM);
109
const modifiedMediumDescription = this.modified.getDescription(Verbosity.MEDIUM);
110
if (
111
(typeof originalMediumDescription === 'string' && typeof modifiedMediumDescription === 'string') && // we can only `shorten` when both sides are strings...
112
(originalMediumDescription || modifiedMediumDescription) // ...however never when both sides are empty strings
113
) {
114
const [shortenedOriginalMediumDescription, shortenedModifiedMediumDescription] = shorten([originalMediumDescription, modifiedMediumDescription]);
115
mediumDescription = this.computeLabel(shortenedOriginalMediumDescription, shortenedModifiedMediumDescription);
116
}
117
}
118
119
// Title
120
let shortTitle = this.computeLabel(this.original.getTitle(Verbosity.SHORT) ?? this.original.getName(), this.modified.getTitle(Verbosity.SHORT) ?? this.modified.getName(), ' ↔ ');
121
let mediumTitle = this.computeLabel(this.original.getTitle(Verbosity.MEDIUM) ?? this.original.getName(), this.modified.getTitle(Verbosity.MEDIUM) ?? this.modified.getName(), ' ↔ ');
122
let longTitle = this.computeLabel(this.original.getTitle(Verbosity.LONG) ?? this.original.getName(), this.modified.getTitle(Verbosity.LONG) ?? this.modified.getName(), ' ↔ ');
123
124
const preferredTitle = this.getPreferredTitle();
125
if (preferredTitle) {
126
shortTitle = `${preferredTitle} (${shortTitle})`;
127
mediumTitle = `${preferredTitle} (${mediumTitle})`;
128
longTitle = `${preferredTitle} (${longTitle})`;
129
}
130
131
return { name, shortDescription, mediumDescription, longDescription, forceDescription, shortTitle, mediumTitle, longTitle };
132
}
133
134
private computeLabel(originalLabel: string, modifiedLabel: string, separator?: string): string;
135
private computeLabel(originalLabel: string | undefined, modifiedLabel: string | undefined, separator?: string): string | undefined;
136
private computeLabel(originalLabel: string | undefined, modifiedLabel: string | undefined, separator = ' - '): string | undefined {
137
if (!originalLabel || !modifiedLabel) {
138
return undefined;
139
}
140
141
if (originalLabel === modifiedLabel) {
142
return modifiedLabel;
143
}
144
145
return `${originalLabel}${separator}${modifiedLabel}`;
146
}
147
148
override getName(): string {
149
return this.labels.name;
150
}
151
152
override getDescription(verbosity = Verbosity.MEDIUM): string | undefined {
153
switch (verbosity) {
154
case Verbosity.SHORT:
155
return this.labels.shortDescription;
156
case Verbosity.LONG:
157
return this.labels.longDescription;
158
case Verbosity.MEDIUM:
159
default:
160
return this.labels.mediumDescription;
161
}
162
}
163
164
override getTitle(verbosity?: Verbosity): string {
165
switch (verbosity) {
166
case Verbosity.SHORT:
167
return this.labels.shortTitle;
168
case Verbosity.LONG:
169
return this.labels.longTitle;
170
default:
171
case Verbosity.MEDIUM:
172
return this.labels.mediumTitle;
173
}
174
}
175
176
override async resolve(): Promise<EditorModel> {
177
178
// Create Model - we never reuse our cached model if refresh is true because we cannot
179
// decide for the inputs within if the cached model can be reused or not. There may be
180
// inputs that need to be loaded again and thus we always recreate the model and dispose
181
// the previous one - if any.
182
const resolvedModel = await this.createModel();
183
this.cachedModel?.dispose();
184
185
this.cachedModel = resolvedModel;
186
187
return this.cachedModel;
188
}
189
190
override prefersEditorPane<T extends IEditorDescriptor<IEditorPane>>(editorPanes: T[]): T | undefined {
191
if (this.forceOpenAsBinary) {
192
return editorPanes.find(editorPane => editorPane.typeId === BINARY_DIFF_EDITOR_ID);
193
}
194
195
return editorPanes.find(editorPane => editorPane.typeId === TEXT_DIFF_EDITOR_ID);
196
}
197
198
private async createModel(): Promise<DiffEditorModel> {
199
200
// Join resolve call over two inputs and build diff editor model
201
const [originalEditorModel, modifiedEditorModel] = await Promise.all([
202
this.original.resolve(),
203
this.modified.resolve()
204
]);
205
206
// If both are text models, return textdiffeditor model
207
if (modifiedEditorModel instanceof BaseTextEditorModel && originalEditorModel instanceof BaseTextEditorModel) {
208
return new TextDiffEditorModel(originalEditorModel, modifiedEditorModel);
209
}
210
211
// Otherwise return normal diff model
212
return new DiffEditorModel(isResolvedEditorModel(originalEditorModel) ? originalEditorModel : undefined, isResolvedEditorModel(modifiedEditorModel) ? modifiedEditorModel : undefined);
213
}
214
215
override toUntyped(options?: IUntypedEditorOptions): (IResourceDiffEditorInput & IResourceSideBySideEditorInput) | undefined {
216
const untyped = super.toUntyped(options);
217
if (untyped) {
218
return {
219
...untyped,
220
modified: untyped.primary,
221
original: untyped.secondary
222
};
223
}
224
225
return undefined;
226
}
227
228
override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {
229
if (this === otherInput) {
230
return true;
231
}
232
233
if (otherInput instanceof DiffEditorInput) {
234
return this.modified.matches(otherInput.modified) && this.original.matches(otherInput.original) && otherInput.forceOpenAsBinary === this.forceOpenAsBinary;
235
}
236
237
if (isResourceDiffEditorInput(otherInput)) {
238
return this.modified.matches(otherInput.modified) && this.original.matches(otherInput.original);
239
}
240
241
return false;
242
}
243
244
override dispose(): void {
245
246
// Free the diff editor model but do not propagate the dispose() call to the two inputs
247
// We never created the two inputs (original and modified) so we can not dispose
248
// them without sideeffects.
249
if (this.cachedModel) {
250
this.cachedModel.dispose();
251
this.cachedModel = undefined;
252
}
253
254
super.dispose();
255
}
256
}
257
258
export class DiffEditorInputSerializer extends AbstractSideBySideEditorInputSerializer {
259
260
protected createEditorInput(instantiationService: IInstantiationService, name: string | undefined, description: string | undefined, secondaryInput: EditorInput, primaryInput: EditorInput): EditorInput {
261
return instantiationService.createInstance(DiffEditorInput, name, description, secondaryInput, primaryInput, undefined);
262
}
263
}
264
265