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