@theia/scm 1.64.0-next.28 → 1.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/lib/browser/decorations/scm-decorations-service.d.ts.map +1 -1
  2. package/lib/browser/decorations/scm-decorations-service.js +19 -1
  3. package/lib/browser/decorations/scm-decorations-service.js.map +1 -1
  4. package/lib/browser/merge-editor/merge-editor-contribution.d.ts +34 -0
  5. package/lib/browser/merge-editor/merge-editor-contribution.d.ts.map +1 -0
  6. package/lib/browser/merge-editor/merge-editor-contribution.js +335 -0
  7. package/lib/browser/merge-editor/merge-editor-contribution.js.map +1 -0
  8. package/lib/browser/merge-editor/merge-editor-dev-contribution.d.ts +31 -0
  9. package/lib/browser/merge-editor/merge-editor-dev-contribution.d.ts.map +1 -0
  10. package/lib/browser/merge-editor/merge-editor-dev-contribution.js +151 -0
  11. package/lib/browser/merge-editor/merge-editor-dev-contribution.js.map +1 -0
  12. package/lib/browser/merge-editor/merge-editor-module.d.ts +24 -0
  13. package/lib/browser/merge-editor/merge-editor-module.d.ts.map +1 -0
  14. package/lib/browser/merge-editor/merge-editor-module.js +109 -0
  15. package/lib/browser/merge-editor/merge-editor-module.js.map +1 -0
  16. package/lib/browser/merge-editor/merge-editor.d.ts +122 -0
  17. package/lib/browser/merge-editor/merge-editor.d.ts.map +1 -0
  18. package/lib/browser/merge-editor/merge-editor.js +560 -0
  19. package/lib/browser/merge-editor/merge-editor.js.map +1 -0
  20. package/lib/browser/merge-editor/model/line-range.d.ts +37 -0
  21. package/lib/browser/merge-editor/model/line-range.d.ts.map +1 -0
  22. package/lib/browser/merge-editor/model/line-range.js +111 -0
  23. package/lib/browser/merge-editor/model/line-range.js.map +1 -0
  24. package/lib/browser/merge-editor/model/live-diff.d.ts +26 -0
  25. package/lib/browser/merge-editor/model/live-diff.d.ts.map +1 -0
  26. package/lib/browser/merge-editor/model/live-diff.js +85 -0
  27. package/lib/browser/merge-editor/model/live-diff.js.map +1 -0
  28. package/lib/browser/merge-editor/model/merge-editor-model.d.ts +116 -0
  29. package/lib/browser/merge-editor/model/merge-editor-model.d.ts.map +1 -0
  30. package/lib/browser/merge-editor/model/merge-editor-model.js +507 -0
  31. package/lib/browser/merge-editor/model/merge-editor-model.js.map +1 -0
  32. package/lib/browser/merge-editor/model/merge-range.d.ts +50 -0
  33. package/lib/browser/merge-editor/model/merge-range.d.ts.map +1 -0
  34. package/lib/browser/merge-editor/model/merge-range.js +215 -0
  35. package/lib/browser/merge-editor/model/merge-range.js.map +1 -0
  36. package/lib/browser/merge-editor/model/range-editing.d.ts +21 -0
  37. package/lib/browser/merge-editor/model/range-editing.d.ts.map +1 -0
  38. package/lib/browser/merge-editor/model/range-editing.js +68 -0
  39. package/lib/browser/merge-editor/model/range-editing.js.map +1 -0
  40. package/lib/browser/merge-editor/model/range-mapping.d.ts +106 -0
  41. package/lib/browser/merge-editor/model/range-mapping.d.ts.map +1 -0
  42. package/lib/browser/merge-editor/model/range-mapping.js +252 -0
  43. package/lib/browser/merge-editor/model/range-mapping.js.map +1 -0
  44. package/lib/browser/merge-editor/model/range-mapping.spec.d.ts +2 -0
  45. package/lib/browser/merge-editor/model/range-mapping.spec.d.ts.map +1 -0
  46. package/lib/browser/merge-editor/model/range-mapping.spec.js +48 -0
  47. package/lib/browser/merge-editor/model/range-mapping.spec.js.map +1 -0
  48. package/lib/browser/merge-editor/model/range-utils.d.ts +25 -0
  49. package/lib/browser/merge-editor/model/range-utils.d.ts.map +1 -0
  50. package/lib/browser/merge-editor/model/range-utils.js +118 -0
  51. package/lib/browser/merge-editor/model/range-utils.js.map +1 -0
  52. package/lib/browser/merge-editor/view/diff-spacers.d.ts +50 -0
  53. package/lib/browser/merge-editor/view/diff-spacers.d.ts.map +1 -0
  54. package/lib/browser/merge-editor/view/diff-spacers.js +133 -0
  55. package/lib/browser/merge-editor/view/diff-spacers.js.map +1 -0
  56. package/lib/browser/merge-editor/view/merge-editor-panes/index.d.ts +6 -0
  57. package/lib/browser/merge-editor/view/merge-editor-panes/index.d.ts.map +1 -0
  58. package/lib/browser/merge-editor/view/merge-editor-panes/index.js +24 -0
  59. package/lib/browser/merge-editor/view/merge-editor-panes/index.js.map +1 -0
  60. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.d.ts +12 -0
  61. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.d.ts.map +1 -0
  62. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.js +65 -0
  63. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.js.map +1 -0
  64. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.d.ts +30 -0
  65. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.d.ts.map +1 -0
  66. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.js +102 -0
  67. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.js.map +1 -0
  68. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.d.ts +49 -0
  69. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.d.ts.map +1 -0
  70. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.js +214 -0
  71. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.js.map +1 -0
  72. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.d.ts +16 -0
  73. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.d.ts.map +1 -0
  74. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.js +107 -0
  75. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.js.map +1 -0
  76. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.d.ts +27 -0
  77. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.d.ts.map +1 -0
  78. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.js +135 -0
  79. package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.js.map +1 -0
  80. package/lib/browser/merge-editor/view/merge-editor-scroll-sync.d.ts +20 -0
  81. package/lib/browser/merge-editor/view/merge-editor-scroll-sync.d.ts.map +1 -0
  82. package/lib/browser/merge-editor/view/merge-editor-scroll-sync.js +218 -0
  83. package/lib/browser/merge-editor/view/merge-editor-scroll-sync.js.map +1 -0
  84. package/lib/browser/merge-editor/view/merge-editor-view-zones.d.ts +57 -0
  85. package/lib/browser/merge-editor/view/merge-editor-view-zones.d.ts.map +1 -0
  86. package/lib/browser/merge-editor/view/merge-editor-view-zones.js +218 -0
  87. package/lib/browser/merge-editor/view/merge-editor-view-zones.js.map +1 -0
  88. package/lib/browser/merge-editor/view/merge-range-actions.d.ts +23 -0
  89. package/lib/browser/merge-editor/view/merge-range-actions.d.ts.map +1 -0
  90. package/lib/browser/merge-editor/view/merge-range-actions.js +142 -0
  91. package/lib/browser/merge-editor/view/merge-range-actions.js.map +1 -0
  92. package/lib/browser/scm-colors.d.ts +2 -0
  93. package/lib/browser/scm-colors.d.ts.map +1 -1
  94. package/lib/browser/scm-colors.js +2 -0
  95. package/lib/browser/scm-colors.js.map +1 -1
  96. package/lib/browser/scm-frontend-module.d.ts.map +1 -1
  97. package/lib/browser/scm-frontend-module.js +2 -0
  98. package/lib/browser/scm-frontend-module.js.map +1 -1
  99. package/package.json +7 -7
  100. package/src/browser/decorations/scm-decorations-service.ts +18 -1
  101. package/src/browser/merge-editor/merge-editor-contribution.ts +346 -0
  102. package/src/browser/merge-editor/merge-editor-dev-contribution.ts +154 -0
  103. package/src/browser/merge-editor/merge-editor-module.ts +134 -0
  104. package/src/browser/merge-editor/merge-editor.ts +643 -0
  105. package/src/browser/merge-editor/model/line-range.ts +128 -0
  106. package/src/browser/merge-editor/model/live-diff.ts +111 -0
  107. package/src/browser/merge-editor/model/merge-editor-model.ts +623 -0
  108. package/src/browser/merge-editor/model/merge-range.ts +268 -0
  109. package/src/browser/merge-editor/model/range-editing.ts +81 -0
  110. package/src/browser/merge-editor/model/range-mapping.spec.ts +52 -0
  111. package/src/browser/merge-editor/model/range-mapping.ts +396 -0
  112. package/src/browser/merge-editor/model/range-utils.ts +115 -0
  113. package/src/browser/merge-editor/view/diff-spacers.ts +160 -0
  114. package/src/browser/merge-editor/view/merge-editor-panes/index.ts +21 -0
  115. package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.ts +71 -0
  116. package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.tsx +106 -0
  117. package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.ts +246 -0
  118. package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.ts +115 -0
  119. package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.ts +139 -0
  120. package/src/browser/merge-editor/view/merge-editor-scroll-sync.ts +241 -0
  121. package/src/browser/merge-editor/view/merge-editor-view-zones.ts +264 -0
  122. package/src/browser/merge-editor/view/merge-range-actions.ts +159 -0
  123. package/src/browser/scm-colors.ts +2 -0
  124. package/src/browser/scm-frontend-module.ts +4 -0
  125. package/src/browser/style/merge-editor.css +221 -0
@@ -0,0 +1,396 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 1C-Soft LLC and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ /*---------------------------------------------------------------------------------------------
17
+ * Copyright (c) Microsoft Corporation. All rights reserved.
18
+ * Licensed under the MIT License. See License.txt in the project root for license information.
19
+ *--------------------------------------------------------------------------------------------*/
20
+ // copied and modified from https://github.com/microsoft/vscode/blob/1.96.3/src/vs/workbench/contrib/mergeEditor/browser/model/mapping.ts
21
+
22
+ import { ArrayUtils } from '@theia/core';
23
+ import { Position, Range, TextEditorDocument } from '@theia/editor/lib/browser/editor';
24
+ import { LineRange } from './line-range';
25
+ import { LineRangeEdit } from './range-editing';
26
+ import { PositionUtils, RangeUtils } from './range-utils';
27
+
28
+ /**
29
+ * Maps a line range in the original text document to a line range in the modified text document.
30
+ */
31
+ export class LineRangeMapping {
32
+
33
+ static join(mappings: readonly LineRangeMapping[]): LineRangeMapping | undefined {
34
+ return mappings.reduce<undefined | LineRangeMapping>((acc, cur) => acc ? acc.join(cur) : cur, undefined);
35
+ }
36
+
37
+ constructor(
38
+ readonly originalRange: LineRange,
39
+ readonly modifiedRange: LineRange
40
+ ) { }
41
+
42
+ toString(): string {
43
+ return `${this.originalRange.toString()} -> ${this.modifiedRange.toString()}`;
44
+ }
45
+
46
+ join(other: LineRangeMapping): LineRangeMapping {
47
+ return new LineRangeMapping(
48
+ this.originalRange.join(other.originalRange),
49
+ this.modifiedRange.join(other.modifiedRange)
50
+ );
51
+ }
52
+
53
+ addModifiedLineDelta(delta: number): LineRangeMapping {
54
+ return new LineRangeMapping(
55
+ this.originalRange,
56
+ this.modifiedRange.delta(delta)
57
+ );
58
+ }
59
+
60
+ addOriginalLineDelta(delta: number): LineRangeMapping {
61
+ return new LineRangeMapping(
62
+ this.originalRange.delta(delta),
63
+ this.modifiedRange
64
+ );
65
+ }
66
+
67
+ reverse(): LineRangeMapping {
68
+ return new LineRangeMapping(this.modifiedRange, this.originalRange);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Represents a total monotonous mapping of line ranges in one document to another document.
74
+ */
75
+ export class DocumentLineRangeMap {
76
+
77
+ static betweenModifiedSides(
78
+ side1Diff: readonly LineRangeMapping[],
79
+ side2Diff: readonly LineRangeMapping[]
80
+ ): DocumentLineRangeMap {
81
+ const alignments = MappingAlignment.computeAlignments(side1Diff, side2Diff);
82
+ const mappings = alignments.map(alignment => new LineRangeMapping(alignment.side1Range, alignment.side2Range));
83
+ return new DocumentLineRangeMap(mappings);
84
+ }
85
+
86
+ constructor(
87
+ /**
88
+ * The line range mappings that define this document mapping.
89
+ * The number of lines between two adjacent original ranges must equal the number of lines between their corresponding modified ranges.
90
+ */
91
+ readonly lineRangeMappings: readonly LineRangeMapping[]
92
+ ) {
93
+ if (!ArrayUtils.checkAdjacentItems(lineRangeMappings,
94
+ (m1, m2) => m1.originalRange.isBefore(m2.originalRange) && m1.modifiedRange.isBefore(m2.modifiedRange) &&
95
+ m2.originalRange.startLineNumber - m1.originalRange.endLineNumberExclusive === m2.modifiedRange.startLineNumber - m1.modifiedRange.endLineNumberExclusive
96
+ )) {
97
+ throw new Error('Illegal line range mappings');
98
+ }
99
+ }
100
+
101
+ /**
102
+ * @param lineNumber 0-based line number in the original text document
103
+ */
104
+ projectLine(lineNumber: number): LineRangeMapping {
105
+ const lastBefore = ArrayUtils.findLast(this.lineRangeMappings, m => m.originalRange.startLineNumber <= lineNumber);
106
+ if (!lastBefore) {
107
+ return new LineRangeMapping(
108
+ new LineRange(lineNumber, 1),
109
+ new LineRange(lineNumber, 1)
110
+ );
111
+ }
112
+
113
+ if (lastBefore.originalRange.containsLine(lineNumber)) {
114
+ return lastBefore;
115
+ }
116
+
117
+ return new LineRangeMapping(
118
+ new LineRange(lineNumber, 1),
119
+ new LineRange(lineNumber + lastBefore.modifiedRange.endLineNumberExclusive - lastBefore.originalRange.endLineNumberExclusive, 1)
120
+ );
121
+ }
122
+
123
+ reverse(): DocumentLineRangeMap {
124
+ return new DocumentLineRangeMap(
125
+ this.lineRangeMappings.map(m => m.reverse())
126
+ );
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Aligns mappings for two modified sides with a common base range.
132
+ */
133
+ export class MappingAlignment<T extends LineRangeMapping> {
134
+
135
+ static computeAlignments<T extends LineRangeMapping>(
136
+ side1Mappings: readonly T[],
137
+ side2Mappings: readonly T[]
138
+ ): MappingAlignment<T>[] {
139
+ const combinedMappings =
140
+ side1Mappings.map(mapping => ({ source: 0, mapping })).concat(
141
+ side2Mappings.map(mapping => ({ source: 1, mapping }))).sort(
142
+ (a, b) => LineRange.compareByStart(a.mapping.originalRange, b.mapping.originalRange));
143
+
144
+ const currentMappings = [new Array<T>(), new Array<T>()];
145
+ const currentDelta = [0, 0];
146
+
147
+ const alignments = new Array<MappingAlignment<T>>();
148
+
149
+ function pushAlignment(baseRange: LineRange): void {
150
+ const mapping1 = LineRangeMapping.join(currentMappings[0]) || new LineRangeMapping(baseRange, baseRange.delta(currentDelta[0]));
151
+ const mapping2 = LineRangeMapping.join(currentMappings[1]) || new LineRangeMapping(baseRange, baseRange.delta(currentDelta[1]));
152
+
153
+ function getAlignedModifiedRange(m: LineRangeMapping): LineRange {
154
+ const startDelta = baseRange.startLineNumber - m.originalRange.startLineNumber;
155
+ const endDelta = baseRange.endLineNumberExclusive - m.originalRange.endLineNumberExclusive;
156
+ return new LineRange(
157
+ m.modifiedRange.startLineNumber + startDelta,
158
+ m.modifiedRange.lineCount - startDelta + endDelta
159
+ );
160
+ }
161
+
162
+ alignments.push(
163
+ new MappingAlignment(
164
+ baseRange,
165
+ getAlignedModifiedRange(mapping1),
166
+ currentMappings[0],
167
+ getAlignedModifiedRange(mapping2),
168
+ currentMappings[1]
169
+ )
170
+ );
171
+ currentMappings[0] = [];
172
+ currentMappings[1] = [];
173
+ }
174
+
175
+ let currentBaseRange: LineRange | undefined;
176
+
177
+ for (const current of combinedMappings) {
178
+ const { originalRange, modifiedRange } = current.mapping;
179
+ if (currentBaseRange && !currentBaseRange.touches(originalRange)) {
180
+ pushAlignment(currentBaseRange);
181
+ currentBaseRange = undefined;
182
+ }
183
+ currentBaseRange = currentBaseRange ? currentBaseRange.join(originalRange) : originalRange;
184
+ currentMappings[current.source].push(current.mapping);
185
+ currentDelta[current.source] = modifiedRange.endLineNumberExclusive - originalRange.endLineNumberExclusive;
186
+ }
187
+ if (currentBaseRange) {
188
+ pushAlignment(currentBaseRange);
189
+ }
190
+
191
+ return alignments;
192
+ }
193
+
194
+ constructor(
195
+ readonly baseRange: LineRange,
196
+ readonly side1Range: LineRange,
197
+ readonly side1Mappings: readonly T[],
198
+ readonly side2Range: LineRange,
199
+ readonly side2Mappings: readonly T[]
200
+ ) { }
201
+
202
+ toString(): string {
203
+ return `${this.side1Range} <- ${this.baseRange} -> ${this.side2Range}`;
204
+ }
205
+ }
206
+
207
+ /**
208
+ * A line range mapping with inner range mappings.
209
+ */
210
+ export class DetailedLineRangeMapping extends LineRangeMapping {
211
+
212
+ static override join(mappings: readonly DetailedLineRangeMapping[]): DetailedLineRangeMapping | undefined {
213
+ return mappings.reduce<undefined | DetailedLineRangeMapping>((acc, cur) => acc ? acc.join(cur) : cur, undefined);
214
+ }
215
+
216
+ readonly rangeMappings: readonly RangeMapping[];
217
+
218
+ constructor(
219
+ originalRange: LineRange,
220
+ readonly originalDocument: TextEditorDocument,
221
+ modifiedRange: LineRange,
222
+ readonly modifiedDocument: TextEditorDocument,
223
+ rangeMappings?: readonly RangeMapping[]
224
+ ) {
225
+ super(originalRange, modifiedRange);
226
+
227
+ this.rangeMappings = rangeMappings || [new RangeMapping(originalRange.toRange(), modifiedRange.toRange())];
228
+ }
229
+
230
+ override join(other: DetailedLineRangeMapping): DetailedLineRangeMapping {
231
+ return new DetailedLineRangeMapping(
232
+ this.originalRange.join(other.originalRange),
233
+ this.originalDocument,
234
+ this.modifiedRange.join(other.modifiedRange),
235
+ this.modifiedDocument
236
+ );
237
+ }
238
+
239
+ override addModifiedLineDelta(delta: number): DetailedLineRangeMapping {
240
+ return new DetailedLineRangeMapping(
241
+ this.originalRange,
242
+ this.originalDocument,
243
+ this.modifiedRange.delta(delta),
244
+ this.modifiedDocument,
245
+ this.rangeMappings.map(m => m.addModifiedLineDelta(delta))
246
+ );
247
+ }
248
+
249
+ override addOriginalLineDelta(delta: number): DetailedLineRangeMapping {
250
+ return new DetailedLineRangeMapping(
251
+ this.originalRange.delta(delta),
252
+ this.originalDocument,
253
+ this.modifiedRange,
254
+ this.modifiedDocument,
255
+ this.rangeMappings.map(m => m.addOriginalLineDelta(delta))
256
+ );
257
+ }
258
+
259
+ override reverse(): DetailedLineRangeMapping {
260
+ return new DetailedLineRangeMapping(
261
+ this.modifiedRange,
262
+ this.modifiedDocument,
263
+ this.originalRange,
264
+ this.originalDocument,
265
+ this.rangeMappings.map(m => m.reverse())
266
+ );
267
+ }
268
+
269
+ getLineEdit(): LineRangeEdit {
270
+ return new LineRangeEdit(this.originalRange, this.getModifiedLines());
271
+ }
272
+
273
+ getReverseLineEdit(): LineRangeEdit {
274
+ return new LineRangeEdit(this.modifiedRange, this.getOriginalLines());
275
+ }
276
+
277
+ getModifiedLines(): string[] {
278
+ return this.modifiedRange.getLines(this.modifiedDocument);
279
+ }
280
+
281
+ getOriginalLines(): string[] {
282
+ return this.originalRange.getLines(this.originalDocument);
283
+ }
284
+ }
285
+
286
+ /**
287
+ * Maps a range in the original text document to a range in the modified text document.
288
+ */
289
+ export class RangeMapping {
290
+
291
+ constructor(
292
+ readonly originalRange: Readonly<Range>,
293
+ readonly modifiedRange: Readonly<Range>
294
+ ) { }
295
+
296
+ toString(): string {
297
+ function rangeToString(range: Range): string {
298
+ return `[${range.start.line}:${range.start.character}, ${range.end.line}:${range.end.character})`;
299
+ }
300
+
301
+ return `${rangeToString(this.originalRange)} -> ${rangeToString(this.modifiedRange)}`;
302
+ }
303
+
304
+ addModifiedLineDelta(deltaLines: number): RangeMapping {
305
+ return new RangeMapping(
306
+ this.originalRange,
307
+ Range.create(
308
+ this.modifiedRange.start.line + deltaLines,
309
+ this.modifiedRange.start.character,
310
+ this.modifiedRange.end.line + deltaLines,
311
+ this.modifiedRange.end.character
312
+ )
313
+ );
314
+ }
315
+
316
+ addOriginalLineDelta(deltaLines: number): RangeMapping {
317
+ return new RangeMapping(
318
+ Range.create(
319
+ this.originalRange.start.line + deltaLines,
320
+ this.originalRange.start.character,
321
+ this.originalRange.end.line + deltaLines,
322
+ this.originalRange.end.character
323
+ ),
324
+ this.modifiedRange
325
+ );
326
+ }
327
+
328
+ reverse(): RangeMapping {
329
+ return new RangeMapping(this.modifiedRange, this.originalRange);
330
+ }
331
+ }
332
+
333
+ /**
334
+ * Represents a total monotonous mapping of ranges in one document to another document.
335
+ */
336
+ export class DocumentRangeMap {
337
+
338
+ constructor(
339
+ /**
340
+ * The range mappings that define this document mapping.
341
+ */
342
+ readonly rangeMappings: readonly RangeMapping[]
343
+ ) {
344
+ if (!ArrayUtils.checkAdjacentItems(
345
+ rangeMappings,
346
+ (m1, m2) =>
347
+ RangeUtils.isBeforeOrTouching(m1.originalRange, m2.originalRange) &&
348
+ RangeUtils.isBeforeOrTouching(m1.modifiedRange, m2.modifiedRange)
349
+ )) {
350
+ throw new Error('Illegal range mappings');
351
+ }
352
+ }
353
+
354
+ /**
355
+ * @param position position in the original text document
356
+ */
357
+ projectPosition(position: Position): RangeMapping {
358
+ const lastBefore = ArrayUtils.findLast(this.rangeMappings, m => PositionUtils.isBeforeOrEqual(m.originalRange.start, position));
359
+ if (!lastBefore) {
360
+ return new RangeMapping(
361
+ Range.create(position, position),
362
+ Range.create(position, position)
363
+ );
364
+ }
365
+
366
+ if (RangeUtils.containsPosition(lastBefore.originalRange, position)) {
367
+ return lastBefore;
368
+ }
369
+
370
+ const relativePosition = PositionUtils.relativize(lastBefore.originalRange.end, position);
371
+ const modifiedRangePosition = PositionUtils.resolve(lastBefore.modifiedRange.end, relativePosition);
372
+
373
+ return new RangeMapping(
374
+ Range.create(position, position),
375
+ Range.create(modifiedRangePosition, modifiedRangePosition)
376
+ );
377
+ }
378
+
379
+ /**
380
+ * @param range range in the original text document
381
+ */
382
+ projectRange(range: Range): RangeMapping {
383
+ const start = this.projectPosition(range.start);
384
+ const end = this.projectPosition(range.end);
385
+ return new RangeMapping(
386
+ RangeUtils.union(start.originalRange, end.originalRange),
387
+ RangeUtils.union(start.modifiedRange, end.modifiedRange)
388
+ );
389
+ }
390
+
391
+ reverse(): DocumentRangeMap {
392
+ return new DocumentRangeMap(
393
+ this.rangeMappings.map(m => m.reverse())
394
+ );
395
+ }
396
+ }
@@ -0,0 +1,115 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 1C-Soft LLC and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+ /*---------------------------------------------------------------------------------------------
17
+ * Copyright (c) Microsoft Corporation. All rights reserved.
18
+ * Licensed under the MIT License. See License.txt in the project root for license information.
19
+ *--------------------------------------------------------------------------------------------*/
20
+ // copied and modified from https://github.com/microsoft/vscode/blob/1.96.3/src/vs/workbench/contrib/mergeEditor/browser/model/rangeUtils.ts,
21
+ // https://github.com/microsoft/vscode/blob/1.96.3/src/vs/editor/common/core/range.ts,
22
+ // https://github.com/microsoft/vscode/blob/1.96.3/src/vs/editor/common/core/position.ts,
23
+ // https://github.com/microsoft/vscode/blob/1.96.3/src/vs/editor/common/core/textLength.ts
24
+
25
+ import { Position, Range } from '@theia/core/shared/vscode-languageserver-protocol';
26
+
27
+ export namespace RangeUtils {
28
+
29
+ export function isEmpty(range: Range): boolean {
30
+ return range.start.line === range.end.line && range.start.character === range.end.character;
31
+ }
32
+
33
+ export function containsPosition(range: Range, position: Position): boolean {
34
+ if (position.line < range.start.line || position.line > range.end.line) {
35
+ return false;
36
+ }
37
+ if (position.line === range.start.line && position.character < range.start.character) {
38
+ return false;
39
+ }
40
+ if (position.line === range.end.line && position.character >= range.end.character) {
41
+ return false;
42
+ }
43
+ return true;
44
+ }
45
+
46
+ export function isBeforeOrTouching(range: Range, other: Range): boolean {
47
+ return (
48
+ range.end.line < other.start.line ||
49
+ (range.end.line === other.start.line &&
50
+ range.end.character <= other.start.character)
51
+ );
52
+ }
53
+
54
+ export function union(range: Range, other: Range): Range {
55
+ const start = PositionUtils.isBeforeOrEqual(range.start, other.start) ? range.start : other.start;
56
+ const end = PositionUtils.isBeforeOrEqual(range.end, other.end) ? other.end : range.end;
57
+ return { start, end };
58
+ }
59
+
60
+ /**
61
+ * A function that compares ranges, useful for sorting ranges.
62
+ * It will first compare ranges on the start position and then on the end position.
63
+ */
64
+ export function compareUsingStarts(range: Range, other: Range): number {
65
+ if (range.start.line === other.start.line) {
66
+ if (range.start.character === other.start.character) {
67
+ if (range.end.line === other.end.line) {
68
+ return range.end.character - other.end.character;
69
+ }
70
+ return range.end.line - other.end.line;
71
+ }
72
+ return range.start.character - other.start.character;
73
+ }
74
+ return range.start.line - other.start.line;
75
+ }
76
+ }
77
+
78
+ export namespace PositionUtils {
79
+
80
+ export function isBeforeOrEqual(position: Position, other: Position): boolean {
81
+ return compare(position, other) <= 0;
82
+ }
83
+
84
+ export function compare(position: Position, other: Position): number {
85
+ if (position.line === other.line) {
86
+ return position.character - other.character;
87
+ }
88
+ return position.line - other.line;
89
+ }
90
+
91
+ /**
92
+ * Given two positions, computes the relative position of the greater position against the lesser position.
93
+ */
94
+ export function relativize(position: Position, other: Position): Position {
95
+ if (compare(position, other) > 0) {
96
+ [position, other] = [other, position];
97
+ }
98
+ if (position.line === other.line) {
99
+ return Position.create(0, other.character - position.character);
100
+ } else {
101
+ return Position.create(other.line - position.line, other.character);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Resolves the given relative position against the given position and returns the resulting position.
107
+ */
108
+ export function resolve(position: Position, relativePosition: Position): Position {
109
+ if (relativePosition.line === 0) {
110
+ return Position.create(position.line, position.character + relativePosition.character);
111
+ } else {
112
+ return Position.create(position.line + relativePosition.line, relativePosition.character);
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,160 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 1C-Soft LLC and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { injectable } from '@theia/core/shared/inversify';
18
+ import { ArrayUtils } from '@theia/core';
19
+ import { LineRangeMapping } from '../model/range-mapping';
20
+
21
+ export interface DiffSpacers {
22
+ /**
23
+ * An array representing spacers in the original side of the diff.
24
+ * Indices are line numbers in the original document, and values are the height in lines of the spacer directly above the given line.
25
+ * If a value is missing for a line number, the corresponding spacer is assumed to have zero height.
26
+ */
27
+ originalSpacers: number[];
28
+ /**
29
+ * An array representing spacers in the modified side of the diff.
30
+ * Indices are line numbers in the modified document, and values are the height in lines of the spacer directly above the given line.
31
+ * If a value is missing for a line number, the corresponding spacer is assumed to have zero height.
32
+ */
33
+ modifiedSpacers: number[];
34
+ /**
35
+ * An array respresenting a mapping of line numbers for the diff.
36
+ * Indices are line numbers in the original document, and values are the corresponding line numbers in the modified document.
37
+ * If a value is missing for a line number, it is assumed that the line was deleted.
38
+ */
39
+ lineMapping: number[];
40
+ }
41
+
42
+ export type ModifiedSideSpacers = Omit<DiffSpacers, 'originalSpacers'>;
43
+
44
+ export interface CombinedMultiDiffSpacers {
45
+ originalSpacers: number[];
46
+ modifiedSides: ModifiedSideSpacers[];
47
+ }
48
+
49
+ @injectable()
50
+ export class DiffSpacerService {
51
+
52
+ computeDiffSpacers(changes: readonly LineRangeMapping[], originalLineCount: number): DiffSpacers {
53
+ const lineMapping: number[] = [];
54
+ const originalSpacers: number[] = [];
55
+ const modifiedSpacers: number[] = [];
56
+ let originalLine = 0;
57
+ let deltaSum = 0;
58
+ for (const { originalRange, modifiedRange } of changes) {
59
+ while (originalLine < originalRange.startLineNumber + Math.min(originalRange.lineCount, modifiedRange.lineCount)) {
60
+ lineMapping[originalLine] = originalLine + deltaSum;
61
+ originalLine++;
62
+ }
63
+ const delta = modifiedRange.lineCount - originalRange.lineCount;
64
+ deltaSum += delta;
65
+ if (delta > 0) {
66
+ originalSpacers[originalLine] = delta;
67
+ }
68
+ if (delta < 0) {
69
+ modifiedSpacers[modifiedRange.endLineNumberExclusive] = -delta;
70
+ originalLine += -delta;
71
+ }
72
+ }
73
+ while (originalLine <= originalLineCount) {
74
+ lineMapping[originalLine] = originalLine + deltaSum;
75
+ originalLine++;
76
+ }
77
+ return { originalSpacers, modifiedSpacers, lineMapping };
78
+ }
79
+
80
+ /**
81
+ * Combines multiple {@link DiffSpacers} objects into a {@link CombinedMultiDiffSpacers} object with the appropriately adjusted spacers.
82
+ * The given {@link DiffSpacers} objects are not modified.
83
+ *
84
+ * It is assumed that all of the given {@link DiffSpacers} objects have been computed from diffs against the same original side.
85
+ */
86
+ combineMultiDiffSpacers(multiDiffSpacers: DiffSpacers[]): CombinedMultiDiffSpacers {
87
+ if (multiDiffSpacers.length < 2) {
88
+ throw new Error('At least two items are required');
89
+ }
90
+ this.checkLineMappingsHaveEqualLength(multiDiffSpacers);
91
+ const originalSpacers: number[] = [];
92
+ const modifiedSides: ModifiedSideSpacers[] = [];
93
+ for (const { modifiedSpacers, lineMapping } of multiDiffSpacers) {
94
+ const modifiedSpacersCopy = modifiedSpacers.concat(); // note: copying by concat() preserves empty slots of the sparse array
95
+ modifiedSides.push({ modifiedSpacers: modifiedSpacersCopy, lineMapping });
96
+ }
97
+ const originalLineCount = modifiedSides[0].lineMapping.length;
98
+ for (let originalLine = 0; originalLine < originalLineCount; originalLine++) {
99
+ const max = Math.max(...multiDiffSpacers.map(diffSpacers => diffSpacers.originalSpacers[originalLine] ?? 0));
100
+ if (max > 0) {
101
+ originalSpacers[originalLine] = max;
102
+ for (let i = 0; i < multiDiffSpacers.length; i++) {
103
+ const delta = max - (multiDiffSpacers[i].originalSpacers[originalLine] ?? 0);
104
+ if (delta > 0) {
105
+ const { modifiedSpacers, lineMapping } = modifiedSides[i];
106
+ const modifiedLine = this.projectLine(originalLine, lineMapping);
107
+ modifiedSpacers[modifiedLine] = (modifiedSpacers[modifiedLine] ?? 0) + delta;
108
+ }
109
+ }
110
+ }
111
+ }
112
+ return { originalSpacers, modifiedSides };
113
+ }
114
+
115
+ /**
116
+ * Given a {@link CombinedMultiDiffSpacers} object, excludes the original side, returning the modified sides with the appropriately adjusted spacers.
117
+ * The given {@link CombinedMultiDiffSpacers} object is not modified.
118
+ */
119
+ excludeOriginalSide({ modifiedSides }: CombinedMultiDiffSpacers): { modifiedSides: { modifiedSpacers: number[] }[] } {
120
+ if (modifiedSides.length < 2) {
121
+ throw new Error('At least two modified sides are required');
122
+ }
123
+ this.checkLineMappingsHaveEqualLength(modifiedSides);
124
+ const modifiedSidesCopy: { modifiedSpacers: number[] }[] = [];
125
+ for (const { modifiedSpacers } of modifiedSides) {
126
+ const modifiedSpacersCopy = modifiedSpacers.concat(); // note: copying by concat() preserves empty slots of the sparse array
127
+ modifiedSidesCopy.push({ modifiedSpacers: modifiedSpacersCopy });
128
+ }
129
+ // When the original side is excluded, the adjoining spacers in the modified sides can be deflated by removing their intersecting parts.
130
+ const originalLineCount = modifiedSides[0].lineMapping.length;
131
+ for (let originalLine = 0; originalLine < originalLineCount; originalLine++) {
132
+ if (modifiedSides.every(({ lineMapping }) => lineMapping[originalLine] === undefined)) {
133
+ modifiedSides.forEach(({ lineMapping }, index) => {
134
+ const modifiedLine = this.projectLine(originalLine, lineMapping);
135
+ const { modifiedSpacers } = modifiedSidesCopy[index];
136
+ modifiedSpacers[modifiedLine]--;
137
+ });
138
+ }
139
+ }
140
+ return { modifiedSides: modifiedSidesCopy };
141
+ }
142
+
143
+ protected checkLineMappingsHaveEqualLength(items: { lineMapping: number[] }[]): void {
144
+ if (!ArrayUtils.checkAdjacentItems(items, (item1, item2) => item1.lineMapping.length === item2.lineMapping.length)) {
145
+ throw new Error('Line mappings must have equal length');
146
+ }
147
+ }
148
+
149
+ protected projectLine(originalLine: number, lineMapping: number[]): number {
150
+ let modifiedLine: number | undefined;
151
+ const originalLineCount = lineMapping.length;
152
+ while (originalLine < originalLineCount) {
153
+ modifiedLine = lineMapping[originalLine++];
154
+ if (modifiedLine !== undefined) {
155
+ return modifiedLine;
156
+ }
157
+ }
158
+ throw new Error('Assertion failed');
159
+ }
160
+ }
@@ -0,0 +1,21 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2025 1C-Soft LLC and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ export * from './merge-editor-pane';
18
+ export * from './merge-editor-pane-header';
19
+ export * from './merge-editor-base-pane';
20
+ export * from './merge-editor-side-pane';
21
+ export * from './merge-editor-result-pane';