@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.
- package/lib/browser/decorations/scm-decorations-service.d.ts.map +1 -1
- package/lib/browser/decorations/scm-decorations-service.js +19 -1
- package/lib/browser/decorations/scm-decorations-service.js.map +1 -1
- package/lib/browser/merge-editor/merge-editor-contribution.d.ts +34 -0
- package/lib/browser/merge-editor/merge-editor-contribution.d.ts.map +1 -0
- package/lib/browser/merge-editor/merge-editor-contribution.js +335 -0
- package/lib/browser/merge-editor/merge-editor-contribution.js.map +1 -0
- package/lib/browser/merge-editor/merge-editor-dev-contribution.d.ts +31 -0
- package/lib/browser/merge-editor/merge-editor-dev-contribution.d.ts.map +1 -0
- package/lib/browser/merge-editor/merge-editor-dev-contribution.js +151 -0
- package/lib/browser/merge-editor/merge-editor-dev-contribution.js.map +1 -0
- package/lib/browser/merge-editor/merge-editor-module.d.ts +24 -0
- package/lib/browser/merge-editor/merge-editor-module.d.ts.map +1 -0
- package/lib/browser/merge-editor/merge-editor-module.js +109 -0
- package/lib/browser/merge-editor/merge-editor-module.js.map +1 -0
- package/lib/browser/merge-editor/merge-editor.d.ts +122 -0
- package/lib/browser/merge-editor/merge-editor.d.ts.map +1 -0
- package/lib/browser/merge-editor/merge-editor.js +560 -0
- package/lib/browser/merge-editor/merge-editor.js.map +1 -0
- package/lib/browser/merge-editor/model/line-range.d.ts +37 -0
- package/lib/browser/merge-editor/model/line-range.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/line-range.js +111 -0
- package/lib/browser/merge-editor/model/line-range.js.map +1 -0
- package/lib/browser/merge-editor/model/live-diff.d.ts +26 -0
- package/lib/browser/merge-editor/model/live-diff.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/live-diff.js +85 -0
- package/lib/browser/merge-editor/model/live-diff.js.map +1 -0
- package/lib/browser/merge-editor/model/merge-editor-model.d.ts +116 -0
- package/lib/browser/merge-editor/model/merge-editor-model.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/merge-editor-model.js +507 -0
- package/lib/browser/merge-editor/model/merge-editor-model.js.map +1 -0
- package/lib/browser/merge-editor/model/merge-range.d.ts +50 -0
- package/lib/browser/merge-editor/model/merge-range.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/merge-range.js +215 -0
- package/lib/browser/merge-editor/model/merge-range.js.map +1 -0
- package/lib/browser/merge-editor/model/range-editing.d.ts +21 -0
- package/lib/browser/merge-editor/model/range-editing.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/range-editing.js +68 -0
- package/lib/browser/merge-editor/model/range-editing.js.map +1 -0
- package/lib/browser/merge-editor/model/range-mapping.d.ts +106 -0
- package/lib/browser/merge-editor/model/range-mapping.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/range-mapping.js +252 -0
- package/lib/browser/merge-editor/model/range-mapping.js.map +1 -0
- package/lib/browser/merge-editor/model/range-mapping.spec.d.ts +2 -0
- package/lib/browser/merge-editor/model/range-mapping.spec.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/range-mapping.spec.js +48 -0
- package/lib/browser/merge-editor/model/range-mapping.spec.js.map +1 -0
- package/lib/browser/merge-editor/model/range-utils.d.ts +25 -0
- package/lib/browser/merge-editor/model/range-utils.d.ts.map +1 -0
- package/lib/browser/merge-editor/model/range-utils.js +118 -0
- package/lib/browser/merge-editor/model/range-utils.js.map +1 -0
- package/lib/browser/merge-editor/view/diff-spacers.d.ts +50 -0
- package/lib/browser/merge-editor/view/diff-spacers.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/diff-spacers.js +133 -0
- package/lib/browser/merge-editor/view/diff-spacers.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/index.d.ts +6 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/index.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/index.js +24 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/index.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.d.ts +12 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.js +65 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.d.ts +30 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.js +102 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.d.ts +49 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.js +214 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.d.ts +16 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.js +107 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.d.ts +27 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.js +135 -0
- package/lib/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-scroll-sync.d.ts +20 -0
- package/lib/browser/merge-editor/view/merge-editor-scroll-sync.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-scroll-sync.js +218 -0
- package/lib/browser/merge-editor/view/merge-editor-scroll-sync.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-view-zones.d.ts +57 -0
- package/lib/browser/merge-editor/view/merge-editor-view-zones.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-editor-view-zones.js +218 -0
- package/lib/browser/merge-editor/view/merge-editor-view-zones.js.map +1 -0
- package/lib/browser/merge-editor/view/merge-range-actions.d.ts +23 -0
- package/lib/browser/merge-editor/view/merge-range-actions.d.ts.map +1 -0
- package/lib/browser/merge-editor/view/merge-range-actions.js +142 -0
- package/lib/browser/merge-editor/view/merge-range-actions.js.map +1 -0
- package/lib/browser/scm-colors.d.ts +2 -0
- package/lib/browser/scm-colors.d.ts.map +1 -1
- package/lib/browser/scm-colors.js +2 -0
- package/lib/browser/scm-colors.js.map +1 -1
- package/lib/browser/scm-frontend-module.d.ts.map +1 -1
- package/lib/browser/scm-frontend-module.js +2 -0
- package/lib/browser/scm-frontend-module.js.map +1 -1
- package/package.json +7 -7
- package/src/browser/decorations/scm-decorations-service.ts +18 -1
- package/src/browser/merge-editor/merge-editor-contribution.ts +346 -0
- package/src/browser/merge-editor/merge-editor-dev-contribution.ts +154 -0
- package/src/browser/merge-editor/merge-editor-module.ts +134 -0
- package/src/browser/merge-editor/merge-editor.ts +643 -0
- package/src/browser/merge-editor/model/line-range.ts +128 -0
- package/src/browser/merge-editor/model/live-diff.ts +111 -0
- package/src/browser/merge-editor/model/merge-editor-model.ts +623 -0
- package/src/browser/merge-editor/model/merge-range.ts +268 -0
- package/src/browser/merge-editor/model/range-editing.ts +81 -0
- package/src/browser/merge-editor/model/range-mapping.spec.ts +52 -0
- package/src/browser/merge-editor/model/range-mapping.ts +396 -0
- package/src/browser/merge-editor/model/range-utils.ts +115 -0
- package/src/browser/merge-editor/view/diff-spacers.ts +160 -0
- package/src/browser/merge-editor/view/merge-editor-panes/index.ts +21 -0
- package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-base-pane.ts +71 -0
- package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-pane-header.tsx +106 -0
- package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-pane.ts +246 -0
- package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-result-pane.ts +115 -0
- package/src/browser/merge-editor/view/merge-editor-panes/merge-editor-side-pane.ts +139 -0
- package/src/browser/merge-editor/view/merge-editor-scroll-sync.ts +241 -0
- package/src/browser/merge-editor/view/merge-editor-view-zones.ts +264 -0
- package/src/browser/merge-editor/view/merge-range-actions.ts +159 -0
- package/src/browser/scm-colors.ts +2 -0
- package/src/browser/scm-frontend-module.ts +4 -0
- package/src/browser/style/merge-editor.css +221 -0
|
@@ -0,0 +1,643 @@
|
|
|
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 { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
|
|
18
|
+
import { ArrayUtils, Disposable, DisposableCollection, nls, URI } from '@theia/core';
|
|
19
|
+
import {
|
|
20
|
+
ApplicationShell, BaseWidget, FocusTracker, LabelProvider, Message, Navigatable, NavigatableWidgetOpenHandler, PanelLayout,
|
|
21
|
+
Saveable, SaveableSource, SplitPanel, StatefulWidget, StorageService, Widget, WidgetOpenerOptions
|
|
22
|
+
} from '@theia/core/lib/browser';
|
|
23
|
+
import { Autorun, DerivedObservable, Observable, ObservableUtils, SettableObservable } from '@theia/core/lib/common/observable';
|
|
24
|
+
import { Range } from '@theia/editor/lib/browser';
|
|
25
|
+
import { MergeRange } from './model/merge-range';
|
|
26
|
+
import { MergeEditorModel } from './model/merge-editor-model';
|
|
27
|
+
import { MergeEditorBasePane, MergeEditorPane, MergeEditorResultPane, MergeEditorSide1Pane, MergeEditorSide2Pane, MergeEditorSidePane } from './view/merge-editor-panes';
|
|
28
|
+
import { MergeEditorViewZone, MergeEditorViewZoneComputer } from './view/merge-editor-view-zones';
|
|
29
|
+
import { MergeEditorScrollSync } from './view/merge-editor-scroll-sync';
|
|
30
|
+
|
|
31
|
+
export interface MergeUris {
|
|
32
|
+
baseUri: URI;
|
|
33
|
+
side1Uri: URI;
|
|
34
|
+
side2Uri: URI;
|
|
35
|
+
resultUri: URI;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export namespace MergeEditorUri {
|
|
39
|
+
|
|
40
|
+
const SCHEME = 'merge-editor';
|
|
41
|
+
|
|
42
|
+
export function isMergeEditorUri(uri: URI): boolean {
|
|
43
|
+
return uri.scheme === SCHEME;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function encode({ baseUri, side1Uri, side2Uri, resultUri }: MergeUris): URI {
|
|
47
|
+
return new URI().withScheme(SCHEME).withQuery(JSON.stringify([baseUri.toString(), side1Uri.toString(), side2Uri.toString(), resultUri.toString()]));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function decode(uri: URI): MergeUris {
|
|
51
|
+
if (uri.scheme !== SCHEME) {
|
|
52
|
+
throw new Error(`The URI must have scheme ${SCHEME}. The URI was: ${uri}`);
|
|
53
|
+
}
|
|
54
|
+
const mergeUris = JSON.parse(uri.query);
|
|
55
|
+
if (!Array.isArray(mergeUris) || !mergeUris.every(mergeUri => typeof mergeUri === 'string')) {
|
|
56
|
+
throw new Error(`The URI ${uri} is not a valid URI for scheme ${SCHEME}`);
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
baseUri: new URI(mergeUris[0]),
|
|
60
|
+
side1Uri: new URI(mergeUris[1]),
|
|
61
|
+
side2Uri: new URI(mergeUris[2]),
|
|
62
|
+
resultUri: new URI(mergeUris[3])
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type MergeEditorLayoutKind = 'mixed' | 'columns';
|
|
68
|
+
|
|
69
|
+
export interface MergeEditorLayoutMode {
|
|
70
|
+
readonly kind: MergeEditorLayoutKind;
|
|
71
|
+
readonly showBase: boolean;
|
|
72
|
+
readonly showBaseAtTop: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export namespace MergeEditorLayoutMode {
|
|
76
|
+
export const DEFAULT: MergeEditorLayoutMode = { kind: 'mixed', showBase: true, showBaseAtTop: false };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface MergeEditorSideWidgetState {
|
|
80
|
+
title?: string;
|
|
81
|
+
description?: string;
|
|
82
|
+
detail?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface MergeEditorWidgetState {
|
|
86
|
+
layoutMode?: MergeEditorLayoutMode;
|
|
87
|
+
side1State?: MergeEditorSideWidgetState;
|
|
88
|
+
side2State?: MergeEditorSideWidgetState;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@injectable()
|
|
92
|
+
export class MergeEditorSettings {
|
|
93
|
+
|
|
94
|
+
protected static LAYOUT_MODE = 'mergeEditor/layoutMode';
|
|
95
|
+
|
|
96
|
+
@inject(StorageService)
|
|
97
|
+
protected readonly storageService: StorageService;
|
|
98
|
+
|
|
99
|
+
layoutMode = MergeEditorLayoutMode.DEFAULT;
|
|
100
|
+
|
|
101
|
+
async load(): Promise<void> {
|
|
102
|
+
await Promise.allSettled([
|
|
103
|
+
this.storageService.getData(MergeEditorSettings.LAYOUT_MODE, this.layoutMode).then(
|
|
104
|
+
layoutMode => this.layoutMode = layoutMode
|
|
105
|
+
),
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async save(): Promise<void> {
|
|
110
|
+
await Promise.allSettled([
|
|
111
|
+
this.storageService.setData(MergeEditorSettings.LAYOUT_MODE, this.layoutMode),
|
|
112
|
+
]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
@injectable()
|
|
117
|
+
export class MergeEditor extends BaseWidget implements StatefulWidget, SaveableSource, Navigatable, ApplicationShell.TrackableWidgetProvider {
|
|
118
|
+
|
|
119
|
+
@inject(MergeEditorModel)
|
|
120
|
+
readonly model: MergeEditorModel;
|
|
121
|
+
|
|
122
|
+
@inject(MergeEditorBasePane)
|
|
123
|
+
readonly basePane: MergeEditorBasePane;
|
|
124
|
+
|
|
125
|
+
@inject(MergeEditorSide1Pane)
|
|
126
|
+
readonly side1Pane: MergeEditorSide1Pane;
|
|
127
|
+
|
|
128
|
+
@inject(MergeEditorSide2Pane)
|
|
129
|
+
readonly side2Pane: MergeEditorSide2Pane;
|
|
130
|
+
|
|
131
|
+
@inject(MergeEditorResultPane)
|
|
132
|
+
readonly resultPane: MergeEditorResultPane;
|
|
133
|
+
|
|
134
|
+
@inject(MergeEditorViewZoneComputer)
|
|
135
|
+
protected readonly viewZoneComputer: MergeEditorViewZoneComputer;
|
|
136
|
+
|
|
137
|
+
@inject(MergeEditorSettings)
|
|
138
|
+
protected readonly settings: MergeEditorSettings;
|
|
139
|
+
|
|
140
|
+
@inject(LabelProvider)
|
|
141
|
+
protected readonly labelProvider: LabelProvider;
|
|
142
|
+
|
|
143
|
+
protected readonly visibilityObservable = SettableObservable.create(true);
|
|
144
|
+
protected readonly currentPaneObservable = SettableObservable.create<MergeEditorPane | undefined>(undefined);
|
|
145
|
+
protected readonly layoutModeObservable = SettableObservable.create(MergeEditorLayoutMode.DEFAULT, {
|
|
146
|
+
isEqual: (a, b) => JSON.stringify(a) === JSON.stringify(b)
|
|
147
|
+
});
|
|
148
|
+
protected readonly currentMergeRangeObservable = this.createCurrentMergeRangeObservable();
|
|
149
|
+
protected readonly selectionInBaseObservable = this.createSelectionInBaseObservable();
|
|
150
|
+
|
|
151
|
+
protected verticalSplitPanel: SplitPanel;
|
|
152
|
+
protected horizontalSplitPanel: SplitPanel;
|
|
153
|
+
|
|
154
|
+
protected scrollSync: MergeEditorScrollSync;
|
|
155
|
+
|
|
156
|
+
@postConstruct()
|
|
157
|
+
protected init(): void {
|
|
158
|
+
this.addClass('theia-merge-editor');
|
|
159
|
+
|
|
160
|
+
const { baseUri, side1Uri, side2Uri, resultUri } = this;
|
|
161
|
+
|
|
162
|
+
this.id = MergeEditorUri.encode({ baseUri, side1Uri, side2Uri, resultUri }).toString();
|
|
163
|
+
|
|
164
|
+
const setLabels = () => {
|
|
165
|
+
this.title.label = nls.localizeByDefault('Merging: {0}', this.labelProvider.getName(resultUri));
|
|
166
|
+
this.title.iconClass = this.labelProvider.getIcon(resultUri) + ' file-icon';
|
|
167
|
+
this.resultPane.header.description = this.labelProvider.getLongName(resultUri);
|
|
168
|
+
};
|
|
169
|
+
setLabels();
|
|
170
|
+
this.toDispose.push(this.labelProvider.onDidChange(event => {
|
|
171
|
+
if (event.affects(resultUri)) {
|
|
172
|
+
setLabels();
|
|
173
|
+
}
|
|
174
|
+
}));
|
|
175
|
+
|
|
176
|
+
this.title.caption = resultUri.path.fsPath();
|
|
177
|
+
this.title.closable = true;
|
|
178
|
+
|
|
179
|
+
this.basePane.header.title.label = nls.localizeByDefault('Base');
|
|
180
|
+
this.side1Pane.header.title.label = nls.localizeByDefault('Input 1');
|
|
181
|
+
this.side2Pane.header.title.label = nls.localizeByDefault('Input 2');
|
|
182
|
+
this.resultPane.header.title.label = nls.localizeByDefault('Result');
|
|
183
|
+
|
|
184
|
+
this.panes.forEach(pane => pane.mergeEditor = this);
|
|
185
|
+
|
|
186
|
+
const layout = this.layout = new PanelLayout();
|
|
187
|
+
this.verticalSplitPanel = new SplitPanel({
|
|
188
|
+
spacing: 1, // --theia-border-width
|
|
189
|
+
orientation: 'vertical'
|
|
190
|
+
});
|
|
191
|
+
layout.addWidget(this.verticalSplitPanel);
|
|
192
|
+
|
|
193
|
+
this.horizontalSplitPanel = new SplitPanel({
|
|
194
|
+
spacing: 1, // --theia-border-width
|
|
195
|
+
orientation: 'horizontal'
|
|
196
|
+
});
|
|
197
|
+
this.verticalSplitPanel.addWidget(this.horizontalSplitPanel);
|
|
198
|
+
|
|
199
|
+
this.layoutMode = this.settings.layoutMode;
|
|
200
|
+
|
|
201
|
+
this.toDispose.push(this.scrollSync = this.createScrollSynchronizer());
|
|
202
|
+
|
|
203
|
+
this.initCurrentPaneTracker();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
protected createScrollSynchronizer(): MergeEditorScrollSync {
|
|
207
|
+
return new MergeEditorScrollSync(this);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
protected initCurrentPaneTracker(): void {
|
|
211
|
+
const focusTracker = new FocusTracker<MergeEditorPane>();
|
|
212
|
+
this.toDispose.push(focusTracker);
|
|
213
|
+
focusTracker.currentChanged.connect((_, { oldValue, newValue }) => {
|
|
214
|
+
oldValue?.removeClass('focused');
|
|
215
|
+
newValue?.addClass('focused');
|
|
216
|
+
this.currentPaneObservable.set(newValue || undefined);
|
|
217
|
+
});
|
|
218
|
+
this.panes.forEach(pane => focusTracker.add(pane));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
protected layoutInitialized = false;
|
|
222
|
+
|
|
223
|
+
protected ensureLayoutInitialized(): void {
|
|
224
|
+
if (!this.layoutInitialized) {
|
|
225
|
+
this.layoutInitialized = true;
|
|
226
|
+
this.doInitializeLayout();
|
|
227
|
+
this.onLayoutInitialized();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
protected doInitializeLayout(): void {
|
|
232
|
+
this.toDispose.push(Autorun.create(({ isFirstRun }) => {
|
|
233
|
+
const { layoutMode } = this;
|
|
234
|
+
|
|
235
|
+
const scrollState = this.scrollSync.storeScrollState();
|
|
236
|
+
const currentPane = this.currentPaneObservable.getUntracked();
|
|
237
|
+
|
|
238
|
+
this.applyLayoutMode(layoutMode);
|
|
239
|
+
|
|
240
|
+
const pane = currentPane?.isVisible ? currentPane : this.resultPane;
|
|
241
|
+
this.currentPaneObservable.set(pane);
|
|
242
|
+
pane.activate();
|
|
243
|
+
|
|
244
|
+
this.scrollSync.restoreScrollState(scrollState);
|
|
245
|
+
|
|
246
|
+
if (!isFirstRun) {
|
|
247
|
+
this.settings.layoutMode = layoutMode;
|
|
248
|
+
}
|
|
249
|
+
}));
|
|
250
|
+
let storedState: {
|
|
251
|
+
scrollState: unknown;
|
|
252
|
+
currentPane: MergeEditorPane | undefined;
|
|
253
|
+
} | undefined;
|
|
254
|
+
this.toDispose.push(ObservableUtils.autorunWithDisposables(({ toDispose }) => {
|
|
255
|
+
if (this.isShown) {
|
|
256
|
+
|
|
257
|
+
toDispose.push(this.createViewZones());
|
|
258
|
+
|
|
259
|
+
if (storedState) {
|
|
260
|
+
const { currentPane, scrollState } = storedState;
|
|
261
|
+
storedState = undefined;
|
|
262
|
+
|
|
263
|
+
const pane = currentPane ?? this.resultPane;
|
|
264
|
+
this.currentPaneObservable.set(pane);
|
|
265
|
+
pane.activate();
|
|
266
|
+
|
|
267
|
+
this.scrollSync.restoreScrollState(scrollState);
|
|
268
|
+
} else {
|
|
269
|
+
this.scrollSync.update();
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
storedState = {
|
|
273
|
+
scrollState: this.scrollSync.storeScrollState(),
|
|
274
|
+
currentPane: this.currentPaneObservable.getUntracked()
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
protected onLayoutInitialized(): void {
|
|
281
|
+
const shouldGoToInitialMergeRange = () => {
|
|
282
|
+
const { cursorPosition } = this.currentPane ?? this.resultPane;
|
|
283
|
+
return cursorPosition.line === 0 && cursorPosition.character === 0;
|
|
284
|
+
};
|
|
285
|
+
if (shouldGoToInitialMergeRange()) {
|
|
286
|
+
this.model.onInitialized.then(() => {
|
|
287
|
+
if (!this.isDisposed && shouldGoToInitialMergeRange()) {
|
|
288
|
+
this.goToFirstMergeRange(mergeRange => !this.model.isMergeRangeHandled(mergeRange));
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
protected override onResize(msg: Widget.ResizeMessage): void {
|
|
295
|
+
super.onResize(msg);
|
|
296
|
+
if (msg.width >= 0 && msg.height >= 0) {
|
|
297
|
+
// Don't try to initialize layout until the merge editor itself is positioned.
|
|
298
|
+
// Otherwise, SplitPanel.setRelativeSizes might not work properly when initializing layout.
|
|
299
|
+
this.ensureLayoutInitialized();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
get isShown(): boolean {
|
|
304
|
+
return this.visibilityObservable.get();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
get currentPane(): MergeEditorPane | undefined {
|
|
308
|
+
return this.currentPaneObservable.get();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
protected createCurrentMergeRangeObservable(): Observable<MergeRange | undefined> {
|
|
312
|
+
return DerivedObservable.create(() => {
|
|
313
|
+
const { currentPane } = this;
|
|
314
|
+
if (!currentPane) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
const { cursorLine } = currentPane;
|
|
318
|
+
return this.model.mergeRanges.find(mergeRange => {
|
|
319
|
+
const lineRange = currentPane.getLineRangeForMergeRange(mergeRange);
|
|
320
|
+
return lineRange.isEmpty ? lineRange.startLineNumber === cursorLine : lineRange.containsLine(cursorLine);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
get currentMergeRange(): MergeRange | undefined {
|
|
326
|
+
return this.currentMergeRangeObservable.get();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
protected createSelectionInBaseObservable(): Observable<Range[] | undefined> {
|
|
330
|
+
return DerivedObservable.create(() => {
|
|
331
|
+
const { currentPane } = this;
|
|
332
|
+
return currentPane?.selection?.map(range => {
|
|
333
|
+
if (currentPane === this.side1Pane) {
|
|
334
|
+
return this.model.translateSideRangeToBase(range, 1);
|
|
335
|
+
}
|
|
336
|
+
if (currentPane === this.side2Pane) {
|
|
337
|
+
return this.model.translateSideRangeToBase(range, 2);
|
|
338
|
+
}
|
|
339
|
+
if (currentPane === this.resultPane) {
|
|
340
|
+
return this.model.translateResultRangeToBase(range);
|
|
341
|
+
}
|
|
342
|
+
return range;
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
get selectionInBase(): Range[] | undefined {
|
|
348
|
+
return this.selectionInBaseObservable.get();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
get panes(): MergeEditorPane[] {
|
|
352
|
+
return [this.basePane, this.side1Pane, this.side2Pane, this.resultPane];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
get baseUri(): URI {
|
|
356
|
+
return this.basePane.editor.uri;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
get side1Uri(): URI {
|
|
360
|
+
return this.side1Pane.editor.uri;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
get side1Title(): string {
|
|
364
|
+
return this.side1Pane.header.title.label;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
get side2Uri(): URI {
|
|
368
|
+
return this.side2Pane.editor.uri;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
get side2Title(): string {
|
|
372
|
+
return this.side2Pane.header.title.label;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
get resultUri(): URI {
|
|
376
|
+
return this.resultPane.editor.uri;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
storeState(): MergeEditorWidgetState {
|
|
380
|
+
const getSideState = ({ header }: MergeEditorSidePane): MergeEditorSideWidgetState => ({
|
|
381
|
+
title: header.title.label,
|
|
382
|
+
description: header.description,
|
|
383
|
+
detail: header.detail
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
layoutMode: this.layoutMode,
|
|
387
|
+
side1State: getSideState(this.side1Pane),
|
|
388
|
+
side2State: getSideState(this.side2Pane)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
restoreState(state: MergeEditorWidgetState): void {
|
|
393
|
+
const { layoutMode, side1State, side2State } = state;
|
|
394
|
+
if (layoutMode) {
|
|
395
|
+
this.layoutMode = layoutMode;
|
|
396
|
+
}
|
|
397
|
+
const restoreSideState = ({ header }: MergeEditorSidePane, { title, description, detail }: MergeEditorSideWidgetState) => {
|
|
398
|
+
if (title) {
|
|
399
|
+
header.title.label = title;
|
|
400
|
+
}
|
|
401
|
+
if (description) {
|
|
402
|
+
header.description = description;
|
|
403
|
+
}
|
|
404
|
+
if (detail) {
|
|
405
|
+
header.detail = detail;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
if (side1State) {
|
|
409
|
+
restoreSideState(this.side1Pane, side1State);
|
|
410
|
+
}
|
|
411
|
+
if (side2State) {
|
|
412
|
+
restoreSideState(this.side2Pane, side2State);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
get saveable(): Saveable {
|
|
417
|
+
return this.resultPane.editor.document;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
getResourceUri(): URI | undefined {
|
|
421
|
+
return this.resultUri;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
createMoveToUri(resourceUri: URI): URI | undefined {
|
|
425
|
+
const { baseUri, side1Uri, side2Uri, resultUri } = this;
|
|
426
|
+
return MergeEditorUri.encode({ baseUri, side1Uri, side2Uri, resultUri: resultUri.withPath(resourceUri.path) });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
getTrackableWidgets(): Widget[] {
|
|
430
|
+
return this.panes.map(pane => pane.editorWidget);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
goToFirstMergeRange(predicate: (mergeRange: MergeRange) => boolean = () => true): void {
|
|
434
|
+
const firstMergeRange = this.model.mergeRanges.find(mergeRange => predicate(mergeRange));
|
|
435
|
+
if (firstMergeRange) {
|
|
436
|
+
const pane = this.currentPane ?? this.resultPane;
|
|
437
|
+
pane.goToMergeRange(firstMergeRange);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
goToNextMergeRange(predicate: (mergeRange: MergeRange) => boolean = () => true): void {
|
|
442
|
+
const pane = this.currentPane ?? this.resultPane;
|
|
443
|
+
const lineNumber = pane.cursorLine;
|
|
444
|
+
const nextMergeRange =
|
|
445
|
+
this.model.mergeRanges.find(mergeRange => predicate(mergeRange) && pane.getLineRangeForMergeRange(mergeRange).startLineNumber > lineNumber) ||
|
|
446
|
+
this.model.mergeRanges.find(mergeRange => predicate(mergeRange));
|
|
447
|
+
if (nextMergeRange) {
|
|
448
|
+
pane.goToMergeRange(nextMergeRange);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
goToPreviousMergeRange(predicate: (mergeRange: MergeRange) => boolean = () => true): void {
|
|
453
|
+
const pane = this.currentPane ?? this.resultPane;
|
|
454
|
+
const lineNumber = pane.cursorLine;
|
|
455
|
+
const previousMergeRange =
|
|
456
|
+
ArrayUtils.findLast(this.model.mergeRanges, mergeRange => predicate(mergeRange) && pane.getLineRangeForMergeRange(mergeRange).endLineNumberExclusive <= lineNumber) ||
|
|
457
|
+
ArrayUtils.findLast(this.model.mergeRanges, mergeRange => predicate(mergeRange));
|
|
458
|
+
if (previousMergeRange) {
|
|
459
|
+
pane.goToMergeRange(previousMergeRange);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
get layoutMode(): MergeEditorLayoutMode {
|
|
464
|
+
return this.layoutModeObservable.get();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
set layoutMode(value: MergeEditorLayoutMode) {
|
|
468
|
+
this.layoutModeObservable.set(value);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
get layoutKind(): MergeEditorLayoutKind {
|
|
472
|
+
return this.layoutMode.kind;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
set layoutKind(kind: MergeEditorLayoutKind) {
|
|
476
|
+
this.layoutMode = {
|
|
477
|
+
...this.layoutMode,
|
|
478
|
+
kind
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
get isShowingBase(): boolean {
|
|
483
|
+
return this.layoutMode.showBase;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
get isShowingBaseAtTop(): boolean {
|
|
487
|
+
const { layoutMode } = this;
|
|
488
|
+
return layoutMode.showBase && layoutMode.showBaseAtTop;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
toggleShowBase(): void {
|
|
492
|
+
const { layoutMode } = this;
|
|
493
|
+
this.layoutMode = {
|
|
494
|
+
...layoutMode,
|
|
495
|
+
showBase: !layoutMode.showBase
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
toggleShowBaseTop(): void {
|
|
500
|
+
const { layoutMode } = this;
|
|
501
|
+
const isToggled = layoutMode.showBase && layoutMode.showBaseAtTop;
|
|
502
|
+
this.layoutMode = {
|
|
503
|
+
...layoutMode,
|
|
504
|
+
showBaseAtTop: true,
|
|
505
|
+
showBase: !isToggled,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
toggleShowBaseCenter(): void {
|
|
510
|
+
const { layoutMode } = this;
|
|
511
|
+
const isToggled = layoutMode.showBase && !layoutMode.showBaseAtTop;
|
|
512
|
+
this.layoutMode = {
|
|
513
|
+
...layoutMode,
|
|
514
|
+
showBaseAtTop: false,
|
|
515
|
+
showBase: !isToggled,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
get shouldAlignResult(): boolean {
|
|
520
|
+
return this.layoutKind === 'columns';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
get shouldAlignBase(): boolean {
|
|
524
|
+
const { layoutMode } = this;
|
|
525
|
+
return layoutMode.kind === 'mixed' && layoutMode.showBase && !layoutMode.showBaseAtTop;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
protected applyLayoutMode(layoutMode: MergeEditorLayoutMode): void {
|
|
529
|
+
const oldVerticalSplitWidgets = [...this.verticalSplitPanel.widgets];
|
|
530
|
+
if (!layoutMode.showBase) {
|
|
531
|
+
// eslint-disable-next-line no-null/no-null
|
|
532
|
+
this.basePane.parent = null;
|
|
533
|
+
}
|
|
534
|
+
this.horizontalSplitPanel.insertWidget(0, this.side1Pane);
|
|
535
|
+
this.horizontalSplitPanel.insertWidget(2, this.side2Pane);
|
|
536
|
+
let horizontalSplitRatio = [50, 50];
|
|
537
|
+
let verticalSplitRatio: number[];
|
|
538
|
+
if (layoutMode.kind === 'columns') {
|
|
539
|
+
horizontalSplitRatio = [33, 34, 33];
|
|
540
|
+
verticalSplitRatio = [100];
|
|
541
|
+
this.horizontalSplitPanel.insertWidget(1, this.resultPane);
|
|
542
|
+
if (layoutMode.showBase) {
|
|
543
|
+
verticalSplitRatio = [30, 70];
|
|
544
|
+
this.verticalSplitPanel.insertWidget(0, this.basePane);
|
|
545
|
+
}
|
|
546
|
+
} else {
|
|
547
|
+
verticalSplitRatio = [45, 55];
|
|
548
|
+
if (layoutMode.showBase) {
|
|
549
|
+
if (layoutMode.showBaseAtTop) {
|
|
550
|
+
verticalSplitRatio = [30, 33, 37];
|
|
551
|
+
this.verticalSplitPanel.insertWidget(0, this.basePane);
|
|
552
|
+
} else {
|
|
553
|
+
horizontalSplitRatio = [33, 34, 33];
|
|
554
|
+
this.horizontalSplitPanel.insertWidget(1, this.basePane);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
this.verticalSplitPanel.insertWidget(2, this.resultPane);
|
|
558
|
+
}
|
|
559
|
+
this.horizontalSplitPanel.setRelativeSizes(horizontalSplitRatio);
|
|
560
|
+
// Keep the existing vertical split ratio if the layout mode change has not affected the vertical split layout.
|
|
561
|
+
if (!ArrayUtils.equals(oldVerticalSplitWidgets, this.verticalSplitPanel.widgets)) {
|
|
562
|
+
this.verticalSplitPanel.setRelativeSizes(verticalSplitRatio);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
protected createViewZones(): Disposable {
|
|
567
|
+
const { baseViewZones, side1ViewZones, side2ViewZones, resultViewZones } = this.viewZoneComputer.computeViewZones(this);
|
|
568
|
+
const toDispose = new DisposableCollection();
|
|
569
|
+
const addViewZones = (pane: MergeEditorPane, viewZones: readonly MergeEditorViewZone[]) => {
|
|
570
|
+
const editor = pane.editor.getControl();
|
|
571
|
+
const viewZoneIds: string[] = [];
|
|
572
|
+
toDispose.push(Disposable.create(() => {
|
|
573
|
+
editor.changeViewZones(accessor => {
|
|
574
|
+
for (const viewZoneId of viewZoneIds) {
|
|
575
|
+
accessor.removeZone(viewZoneId);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}));
|
|
579
|
+
editor.changeViewZones(accessor => {
|
|
580
|
+
const ctx: MergeEditorViewZone.CreationContext = {
|
|
581
|
+
createViewZone: viewZone => viewZoneIds.push(accessor.addZone(viewZone)),
|
|
582
|
+
register: disposable => toDispose.push(disposable)
|
|
583
|
+
};
|
|
584
|
+
for (const viewZone of viewZones) {
|
|
585
|
+
viewZone.create(ctx);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
addViewZones(this.basePane, baseViewZones);
|
|
590
|
+
addViewZones(this.side1Pane, side1ViewZones);
|
|
591
|
+
addViewZones(this.side2Pane, side2ViewZones);
|
|
592
|
+
addViewZones(this.resultPane, resultViewZones);
|
|
593
|
+
return toDispose;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
protected override onBeforeHide(msg: Message): void {
|
|
597
|
+
this.visibilityObservable.set(false);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
protected override onAfterShow(msg: Message): void {
|
|
601
|
+
this.visibilityObservable.set(true);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
protected override onActivateRequest(msg: Message): void {
|
|
605
|
+
super.onActivateRequest(msg);
|
|
606
|
+
const { currentPane } = this;
|
|
607
|
+
if (currentPane) {
|
|
608
|
+
currentPane.activate();
|
|
609
|
+
} else {
|
|
610
|
+
this.resultPane.activate();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export interface MergeEditorOpenerOptions extends WidgetOpenerOptions {
|
|
616
|
+
widgetState?: MergeEditorWidgetState;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
@injectable()
|
|
620
|
+
export class MergeEditorOpenHandler extends NavigatableWidgetOpenHandler<MergeEditor> {
|
|
621
|
+
|
|
622
|
+
static readonly ID = 'merge-editor-opener';
|
|
623
|
+
|
|
624
|
+
readonly id = MergeEditorOpenHandler.ID;
|
|
625
|
+
|
|
626
|
+
readonly label = nls.localizeByDefault('Merge Editor');
|
|
627
|
+
|
|
628
|
+
override canHandle(uri: URI, options?: MergeEditorOpenerOptions): number {
|
|
629
|
+
return MergeEditorUri.isMergeEditorUri(uri) ? 1000 : 0;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
override open(uri: URI, options?: MergeEditorOpenerOptions): Promise<MergeEditor> {
|
|
633
|
+
return super.open(uri, options);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
protected override async getOrCreateWidget(uri: URI, options?: MergeEditorOpenerOptions): Promise<MergeEditor> {
|
|
637
|
+
const widget = await super.getOrCreateWidget(uri, options);
|
|
638
|
+
if (options?.widgetState) {
|
|
639
|
+
widget.restoreState(options.widgetState);
|
|
640
|
+
}
|
|
641
|
+
return widget;
|
|
642
|
+
}
|
|
643
|
+
}
|