Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.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 { ValueWithChangeEvent } from '../../../../base/common/event.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { observableFromEvent, ValueWithChangeEventFromObservable, waitForState } from '../../../../base/common/observable.js';
9
import { basename } from '../../../../base/common/path.js';
10
import { URI, UriComponents } from '../../../../base/common/uri.js';
11
import { IMultiDiffEditorOptions } from '../../../../editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.js';
12
import { localize2 } from '../../../../nls.js';
13
import { Action2 } from '../../../../platform/actions/common/actions.js';
14
import { ContextKeyValue } from '../../../../platform/contextkey/common/contextkey.js';
15
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
16
import { IActivityService, ProgressBadge } from '../../../services/activity/common/activity.js';
17
import { IEditorService } from '../../../services/editor/common/editorService.js';
18
import { ISCMHistoryItem } from '../../scm/common/history.js';
19
import { ISCMProvider, ISCMRepository, ISCMResourceGroup, ISCMService } from '../../scm/common/scm.js';
20
import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from './multiDiffSourceResolverService.js';
21
22
export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver {
23
private static readonly _scheme = 'scm-multi-diff-source';
24
25
public static getMultiDiffSourceUri(repositoryUri: string, groupId: string): URI {
26
return URI.from({
27
scheme: ScmMultiDiffSourceResolver._scheme,
28
query: JSON.stringify({ repositoryUri, groupId } satisfies UriFields),
29
});
30
}
31
32
private static parseUri(uri: URI): { repositoryUri: URI; groupId: string } | undefined {
33
if (uri.scheme !== ScmMultiDiffSourceResolver._scheme) {
34
return undefined;
35
}
36
37
let query: UriFields;
38
try {
39
query = JSON.parse(uri.query) as UriFields;
40
} catch (e) {
41
return undefined;
42
}
43
44
if (typeof query !== 'object' || query === null) {
45
return undefined;
46
}
47
48
const { repositoryUri, groupId } = query;
49
if (typeof repositoryUri !== 'string' || typeof groupId !== 'string') {
50
return undefined;
51
}
52
53
return { repositoryUri: URI.parse(repositoryUri), groupId };
54
}
55
56
constructor(
57
@ISCMService private readonly _scmService: ISCMService,
58
@IActivityService private readonly _activityService: IActivityService,
59
) {
60
}
61
62
canHandleUri(uri: URI): boolean {
63
return ScmMultiDiffSourceResolver.parseUri(uri) !== undefined;
64
}
65
66
async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {
67
const { repositoryUri, groupId } = ScmMultiDiffSourceResolver.parseUri(uri)!;
68
69
const repository = await waitForState(observableFromEvent(this,
70
this._scmService.onDidAddRepository,
71
() => [...this._scmService.repositories].find(r => r.provider.rootUri?.toString() === repositoryUri.toString()))
72
);
73
const group = await waitForState(observableFromEvent(this,
74
repository.provider.onDidChangeResourceGroups,
75
() => repository.provider.groups.find(g => g.id === groupId)
76
));
77
78
const scmActivities = observableFromEvent(
79
this._activityService.onDidChangeActivity,
80
() => [...this._activityService.getViewContainerActivities('workbench.view.scm')],
81
);
82
const scmViewHasNoProgressBadge = scmActivities.map(activities => !activities.some(a => a.badge instanceof ProgressBadge));
83
await waitForState(scmViewHasNoProgressBadge, v => v);
84
85
return new ScmResolvedMultiDiffSource(group, repository);
86
}
87
}
88
89
interface ScmHistoryItemUriFields {
90
readonly repositoryId: string;
91
readonly historyItemId: string;
92
readonly historyItemParentId?: string;
93
readonly historyItemDisplayId?: string;
94
}
95
96
export class ScmHistoryItemResolver implements IMultiDiffSourceResolver {
97
static readonly scheme = 'scm-history-item';
98
99
public static getMultiDiffSourceUri(provider: ISCMProvider, historyItem: ISCMHistoryItem): URI {
100
return URI.from({
101
scheme: ScmHistoryItemResolver.scheme,
102
path: provider.rootUri?.fsPath,
103
query: JSON.stringify({
104
repositoryId: provider.id,
105
historyItemId: historyItem.id,
106
historyItemParentId: historyItem.parentIds.length > 0
107
? historyItem.parentIds[0]
108
: undefined,
109
historyItemDisplayId: historyItem.displayId
110
} satisfies ScmHistoryItemUriFields)
111
}, true);
112
}
113
114
public static parseUri(uri: URI): ScmHistoryItemUriFields | undefined {
115
if (uri.scheme !== ScmHistoryItemResolver.scheme) {
116
return undefined;
117
}
118
119
let query: ScmHistoryItemUriFields;
120
try {
121
query = JSON.parse(uri.query) as ScmHistoryItemUriFields;
122
} catch (e) {
123
return undefined;
124
}
125
126
if (typeof query !== 'object' || query === null) {
127
return undefined;
128
}
129
130
const { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId } = query;
131
if (typeof repositoryId !== 'string' || typeof historyItemId !== 'string' ||
132
(typeof historyItemParentId !== 'string' && historyItemParentId !== undefined) ||
133
(typeof historyItemDisplayId !== 'string' && historyItemDisplayId !== undefined)) {
134
return undefined;
135
}
136
137
return { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId };
138
}
139
140
constructor(@ISCMService private readonly _scmService: ISCMService) { }
141
142
canHandleUri(uri: URI): boolean {
143
return ScmHistoryItemResolver.parseUri(uri) !== undefined;
144
}
145
146
async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {
147
const { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId } = ScmHistoryItemResolver.parseUri(uri)!;
148
149
const repository = this._scmService.getRepository(repositoryId);
150
const historyProvider = repository?.provider.historyProvider.get();
151
const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? [];
152
153
const resources = ValueWithChangeEvent.const<readonly MultiDiffEditorItem[]>(
154
historyItemChanges.map(change => {
155
const goToFileEditorTitle = change.modifiedUri
156
? `${basename(change.modifiedUri.fsPath)} (${historyItemDisplayId ?? historyItemId})`
157
: undefined;
158
159
return new MultiDiffEditorItem(change.originalUri, change.modifiedUri, change.modifiedUri, goToFileEditorTitle);
160
})
161
);
162
163
return { resources };
164
}
165
}
166
167
class ScmResolvedMultiDiffSource implements IResolvedMultiDiffSource {
168
private readonly _resources;
169
readonly resources;
170
171
public readonly contextKeys: Record<string, ContextKeyValue>;
172
173
constructor(
174
private readonly _group: ISCMResourceGroup,
175
private readonly _repository: ISCMRepository,
176
) {
177
this._resources = observableFromEvent<MultiDiffEditorItem[]>(
178
this._group.onDidChangeResources,
179
() => /** @description resources */ this._group.resources.map(e => new MultiDiffEditorItem(e.multiDiffEditorOriginalUri, e.multiDiffEditorModifiedUri, e.sourceUri))
180
);
181
this.resources = new ValueWithChangeEventFromObservable(this._resources);
182
this.contextKeys = {
183
scmResourceGroup: this._group.id,
184
scmProvider: this._repository.provider.providerId,
185
};
186
}
187
}
188
189
interface UriFields {
190
repositoryUri: string;
191
groupId: string;
192
}
193
194
export class ScmMultiDiffSourceResolverContribution extends Disposable {
195
196
static readonly ID = 'workbench.contrib.scmMultiDiffSourceResolver';
197
198
constructor(
199
@IInstantiationService instantiationService: IInstantiationService,
200
@IMultiDiffSourceResolverService multiDiffSourceResolverService: IMultiDiffSourceResolverService,
201
) {
202
super();
203
204
this._register(multiDiffSourceResolverService.registerResolver(instantiationService.createInstance(ScmHistoryItemResolver)));
205
this._register(multiDiffSourceResolverService.registerResolver(instantiationService.createInstance(ScmMultiDiffSourceResolver)));
206
}
207
}
208
209
interface OpenScmGroupActionOptions {
210
title: string;
211
repositoryUri: UriComponents;
212
resourceGroupId: string;
213
}
214
215
export class OpenScmGroupAction extends Action2 {
216
public static async openMultiFileDiffEditor(editorService: IEditorService, label: string, repositoryRootUri: URI | undefined, resourceGroupId: string, options?: IMultiDiffEditorOptions) {
217
if (!repositoryRootUri) {
218
return;
219
}
220
221
const multiDiffSource = ScmMultiDiffSourceResolver.getMultiDiffSourceUri(repositoryRootUri.toString(), resourceGroupId);
222
return await editorService.openEditor({ label, multiDiffSource, options });
223
}
224
225
constructor() {
226
super({
227
id: '_workbench.openScmMultiDiffEditor',
228
title: localize2('openChanges', 'Open Changes'),
229
f1: false
230
});
231
}
232
233
async run(accessor: ServicesAccessor, options: OpenScmGroupActionOptions): Promise<void> {
234
const editorService = accessor.get(IEditorService);
235
await OpenScmGroupAction.openMultiFileDiffEditor(editorService, options.title, URI.revive(options.repositoryUri), options.resourceGroupId);
236
}
237
}
238
239