src/vs/workbench/api/common/extHostSCM.ts

1280 LOC · 430 covered · 850 uncovered · 86 ranges · 1 concepts · 1 introducers · 1 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- extHostSCM.ts ×86
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 { URI, UriComponents } from '../../../base/common/uri.js';
7 > import { Event, Emitter } from '../../../base/common/event.js';
8 > import { debounce } from '../../../base/common/decorators.js';
9 > import { DisposableMap, DisposableStore, IDisposable, MutableDisposable } from '../../../base/common/lifecycle.js';
10 > import { asPromise } from '../../../base/common/async.js';
11 > import { ExtHostCommands } from './extHostCommands.js';
12 > import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, ICommandDto, MainThreadTelemetryShape, SCMGroupFeatures, SCMHistoryItemDto, SCMHistoryItemChangeDto, SCMHistoryItemRefDto, SCMActionButtonDto, SCMArtifactGroupDto, SCMArtifactDto } from './extHost.protocol.js';
13 > import { sortedDiff, equals } from '../../../base/common/arrays.js';
14 > import { comparePaths } from '../../../base/common/comparers.js';
15 > import type * as vscode from 'vscode';
16 > import { ISplice } from '../../../base/common/sequence.js';
17 > import { ILogService } from '../../../platform/log/common/log.js';
18 > import { CancellationToken } from '../../../base/common/cancellation.js';
19 > import { ExtensionIdentifierMap, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';
20 > import { MarshalledId } from '../../../base/common/marshallingIds.js';
21 > import { ThemeIcon } from '../../../base/common/themables.js';
22 > import { IMarkdownString } from '../../../base/common/htmlContent.js';
23 > import { MarkdownString, SourceControlInputBoxValidationType } from './extHostTypeConverters.js';
24 > import { checkProposedApiEnabled, isProposedApiEnabled } from '../../services/extensions/common/extensions.js';
25 > import { ExtHostDocuments } from './extHostDocuments.js';
26 > import { Schemas } from '../../../base/common/network.js';
27 > import { isLinux } from '../../../base/common/platform.js';
28 > import { structuralEquals } from '../../../base/common/equals.js';
29 > import { Iterable } from '../../../base/common/iterator.js';
30 >
31 > type ProviderHandle = number;
32 > type GroupHandle = number;
33 > type ResourceStateHandle = number;
34 >
35 function isUri(thing: any): thing is vscode.Uri {
36 return thing instanceof URI;
37 }
39 function uriEquals(a: vscode.Uri, b: vscode.Uri): boolean {
40 if (a.scheme === Schemas.file && b.scheme === Schemas.file && isLinux) {
41 return a.toString() === b.toString();
42 }
43
44 return a.toString().toLowerCase() === b.toString().toLowerCase();
45 }
47 function getIconResource(decorations?: vscode.SourceControlResourceThemableDecorations): UriComponents | ThemeIcon | undefined {
48 if (!decorations) {
49 return undefined;
50 } else if (typeof decorations.iconPath === 'string') {
51 return URI.file(decorations.iconPath);
52 } else if (URI.isUri(decorations.iconPath)) {
53 return decorations.iconPath;
54 } else if (ThemeIcon.isThemeIcon(decorations.iconPath)) {
55 return decorations.iconPath;
56 } else {
57 return undefined;
58 }
59 }
61 > function getHistoryItemIconDto(icon: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined): UriComponents | { light: UriComponents; dark: UriComponents } | ThemeIcon | undefined {
62 > if (!icon) {
63 > return undefined;
64 > } else if (URI.isUri(icon)) {
65 return icon;
66 } else if (ThemeIcon.isThemeIcon(icon)) {
67 return icon;
68 } else {
69 const iconDto = icon as { light: URI; dark: URI };
70 return { light: iconDto.light, dark: iconDto.dark };
71 }
73 >
74 function toSCMHistoryItemDto(historyItem: vscode.SourceControlHistoryItem): SCMHistoryItemDto {
75 const authorIcon = getHistoryItemIconDto(historyItem.authorIcon);
76 const tooltip = Array.isArray(historyItem.tooltip)
77 ? MarkdownString.fromMany(historyItem.tooltip)
78 : historyItem.tooltip ? MarkdownString.from(historyItem.tooltip) : undefined;
79
80 const references = historyItem.references?.map(r => ({
81 ...r, icon: getHistoryItemIconDto(r.icon)
82 }));
83
84 return { ...historyItem, authorIcon, references, tooltip };
85 }
87 function toSCMHistoryItemRefDto(historyItemRef?: vscode.SourceControlHistoryItemRef): SCMHistoryItemRefDto | undefined {
88 return historyItemRef ? { ...historyItemRef, icon: getHistoryItemIconDto(historyItemRef.icon) } : undefined;
89 }
91 function compareResourceThemableDecorations(a: vscode.SourceControlResourceThemableDecorations, b: vscode.SourceControlResourceThemableDecorations): number {
92 if (!a.iconPath && !b.iconPath) {
93 return 0;
94 } else if (!a.iconPath) {
95 return -1;
96 } else if (!b.iconPath) {
97 return 1;
98 }
99
100 const aPath = typeof a.iconPath === 'string' ? a.iconPath : URI.isUri(a.iconPath) ? a.iconPath.fsPath : (a.iconPath as vscode.ThemeIcon).id;
101 const bPath = typeof b.iconPath === 'string' ? b.iconPath : URI.isUri(b.iconPath) ? b.iconPath.fsPath : (b.iconPath as vscode.ThemeIcon).id;
102 return comparePaths(aPath, bPath);
103 }
105 function compareResourceStatesDecorations(a: vscode.SourceControlResourceDecorations, b: vscode.SourceControlResourceDecorations): number {
106 let result = 0;
107
108 if (a.strikeThrough !== b.strikeThrough) {
109 return a.strikeThrough ? 1 : -1;
110 }
111
112 if (a.faded !== b.faded) {
113 return a.faded ? 1 : -1;
114 }
115
116 if (a.tooltip !== b.tooltip) {
117 return (a.tooltip || '').localeCompare(b.tooltip || '');
118 }
119
120 result = compareResourceThemableDecorations(a, b);
121
122 if (result !== 0) {
123 return result;
124 }
125
126 if (a.light && b.light) {
127 result = compareResourceThemableDecorations(a.light, b.light);
128 } else if (a.light) {
129 return 1;
130 } else if (b.light) {
131 return -1;
132 }
133
134 if (result !== 0) {
135 return result;
136 }
137
138 if (a.dark && b.dark) {
139 result = compareResourceThemableDecorations(a.dark, b.dark);
140 } else if (a.dark) {
141 return 1;
142 } else if (b.dark) {
143 return -1;
144 }
145
146 return result;
147 }
149 function compareCommands(a: vscode.Command, b: vscode.Command): number {
150 if (a.command !== b.command) {
151 return a.command < b.command ? -1 : 1;
152 }
153
154 if (a.title !== b.title) {
155 return a.title < b.title ? -1 : 1;
156 }
157
158 if (a.tooltip !== b.tooltip) {
159 if (a.tooltip !== undefined && b.tooltip !== undefined) {
160 return a.tooltip < b.tooltip ? -1 : 1;
161 } else if (a.tooltip !== undefined) {
162 return 1;
163 } else if (b.tooltip !== undefined) {
164 return -1;
165 }
166 }
167
168 if (a.arguments === b.arguments) {
169 return 0;
170 } else if (!a.arguments) {
171 return -1;
172 } else if (!b.arguments) {
173 return 1;
174 } else if (a.arguments.length !== b.arguments.length) {
175 return a.arguments.length - b.arguments.length;
176 }
177
178 for (let i = 0; i < a.arguments.length; i++) {
179 const aArg = a.arguments[i];
180 const bArg = b.arguments[i];
181
182 if (aArg === bArg) {
183 continue;
184 }
185
186 if (isUri(aArg) && isUri(bArg) && uriEquals(aArg, bArg)) {
187 continue;
188 }
189
190 return aArg < bArg ? -1 : 1;
191 }
192
193 return 0;
194 }
196 function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.SourceControlResourceState): number {
197 let result = comparePaths(a.resourceUri.fsPath, b.resourceUri.fsPath, true);
198
199 if (result !== 0) {
200 return result;
201 }
202
203 if (a.command && b.command) {
204 result = compareCommands(a.command, b.command);
205 } else if (a.command) {
206 return 1;
207 } else if (b.command) {
208 return -1;
209 }
210
211 if (result !== 0) {
212 return result;
213 }
214
215 if (a.decorations && b.decorations) {
216 result = compareResourceStatesDecorations(a.decorations, b.decorations);
217 } else if (a.decorations) {
218 return 1;
219 } else if (b.decorations) {
220 return -1;
221 }
222
223 if (result !== 0) {
224 return result;
225 }
226
227 if (a.multiFileDiffEditorModifiedUri && b.multiFileDiffEditorModifiedUri) {
228 result = comparePaths(a.multiFileDiffEditorModifiedUri.fsPath, b.multiFileDiffEditorModifiedUri.fsPath, true);
229 } else if (a.multiFileDiffEditorModifiedUri) {
230 return 1;
231 } else if (b.multiFileDiffEditorModifiedUri) {
232 return -1;
233 }
234
235 if (result !== 0) {
236 return result;
237 }
238
239 if (a.multiDiffEditorOriginalUri && b.multiDiffEditorOriginalUri) {
240 result = comparePaths(a.multiDiffEditorOriginalUri.fsPath, b.multiDiffEditorOriginalUri.fsPath, true);
241 } else if (a.multiDiffEditorOriginalUri) {
242 return 1;
243 } else if (b.multiDiffEditorOriginalUri) {
244 return -1;
245 }
246
247 return result;
248 }
250 function compareArgs(a: any[], b: any[]): boolean {
251 for (let i = 0; i < a.length; i++) {
252 if (a[i] !== b[i]) {
253 return false;
254 }
255 }
256
257 return true;
258 }
260 function commandEquals(a: vscode.Command, b: vscode.Command): boolean {
261 return a.command === b.command
262 && a.title === b.title
263 && a.tooltip === b.tooltip
264 && (a.arguments && b.arguments ? compareArgs(a.arguments, b.arguments) : a.arguments === b.arguments);
265 }
267 function commandListEquals(a: readonly vscode.Command[], b: readonly vscode.Command[]): boolean {
268 return equals(a, b, commandEquals);
269 }
271 > export interface IValidateInput {
272 > (value: string, cursorPosition: number): vscode.ProviderResult<vscode.SourceControlInputBoxValidation | undefined | null>;
273 > }
274 >
275 > export class ExtHostSCMInputBox implements vscode.SourceControlInputBox {
276 >
277 > #proxy: MainThreadSCMShape;
278 > #extHostDocuments: ExtHostDocuments;
279 >
280 > private _value: string = '';
281 >
282 > get value(): string {
283 return this._value;
284 }
286 > set value(value: string) {
287 value = value ?? '';
288 this.#proxy.$setInputBoxValue(this._sourceControlHandle, value);
289 this.updateValue(value);
290 }
292 > private readonly _onDidChange = new Emitter<string>();
293 >
294 > get onDidChange(): Event<string> {
295 return this._onDidChange.event;
296 }
298 > private _placeholder: string = '';
299 >
300 > get placeholder(): string {
301 return this._placeholder;
302 }
304 > set placeholder(placeholder: string) {
305 this.#proxy.$setInputBoxPlaceholder(this._sourceControlHandle, placeholder);
306 this._placeholder = placeholder;
307 }
309 > private _validateInput: IValidateInput | undefined;
310 >
311 > get validateInput(): IValidateInput | undefined {
312 checkProposedApiEnabled(this._extension, 'scmValidation');
313
314 return this._validateInput;
315 }
317 > set validateInput(fn: IValidateInput | undefined) {
318 checkProposedApiEnabled(this._extension, 'scmValidation');
319
320 if (fn && typeof fn !== 'function') {
321 throw new Error(`[${this._extension.identifier.value}]: Invalid SCM input box validation function`);
322 }
323
324 this._validateInput = fn;
325 this.#proxy.$setValidationProviderIsEnabled(this._sourceControlHandle, !!fn);
326 }
328 > private _enabled: boolean = true;
329 >
330 > get enabled(): boolean {
331 return this._enabled;
332 }
334 > set enabled(enabled: boolean) {
335 enabled = !!enabled;
336
337 if (this._enabled === enabled) {
338 return;
339 }
340
341 this._enabled = enabled;
342 this.#proxy.$setInputBoxEnablement(this._sourceControlHandle, enabled);
343 }
345 > private _visible: boolean = true;
346 >
347 > get visible(): boolean {
348 return this._visible;
349 }
351 > set visible(visible: boolean) {
352 visible = !!visible;
353
354 if (this._visible === visible) {
355 return;
356 }
357
358 this._visible = visible;
359 this.#proxy.$setInputBoxVisibility(this._sourceControlHandle, visible);
360 }
362 > get document(): vscode.TextDocument {
363 checkProposedApiEnabled(this._extension, 'scmTextDocument');
364
365 return this.#extHostDocuments.getDocument(this._documentUri);
366 }
368 > constructor(private _extension: IExtensionDescription, _extHostDocuments: ExtHostDocuments, proxy: MainThreadSCMShape, private _sourceControlHandle: number, private _documentUri: URI) {
369 > this.#extHostDocuments = _extHostDocuments;
370 > this.#proxy = proxy;
371 > }
372 >
373 > showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) {
374 checkProposedApiEnabled(this._extension, 'scmValidation');
375 this.#proxy.$showValidationMessage(this._sourceControlHandle, message, SourceControlInputBoxValidationType.from(type));
376 }
378 > $onInputBoxValueChange(value: string): void {
379 this.updateValue(value);
380 }
382 > private updateValue(value: string): void {
383 this._value = value;
384 this._onDidChange.fire(value);
385 }
387 >
388 > class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceGroup {
389 >
390 > private static _handlePool: number = 0;
391 > private _resourceHandlePool: number = 0;
392 > private _resourceStates: vscode.SourceControlResourceState[] = [];
393 >
394 > private _resourceStatesMap = new Map<ResourceStateHandle, vscode.SourceControlResourceState>();
395 > private _resourceStatesCommandsMap = new Map<ResourceStateHandle, vscode.Command>();
396 > private _resourceStatesDisposablesMap = new Map<ResourceStateHandle, IDisposable>();
397 >
398 > private readonly _onDidUpdateResourceStates = new Emitter<void>();
399 > readonly onDidUpdateResourceStates = this._onDidUpdateResourceStates.event;
400 >
401 > private _disposed = false;
402 > get disposed(): boolean { return this._disposed; }
403 > private readonly _onDidDispose = new Emitter<void>();
404 > readonly onDidDispose = this._onDidDispose.event;
405 >
406 > private _handlesSnapshot: number[] = [];
407 > private _resourceSnapshot: vscode.SourceControlResourceState[] = [];
408 >
409 > get id(): string { return this._id; }
410 >
411 > get label(): string { return this._label; }
412 > set label(label: string) {
413 this._label = label;
414 this._proxy.$updateGroupLabel(this._sourceControlHandle, this.handle, label);
415 }
417 > private _contextValue: string | undefined = undefined;
418 > get contextValue(): string | undefined {
419 return this._contextValue;
420 }
421 > set contextValue(contextValue: string | undefined) { extHostSCM.ts ×86
422 this._contextValue = contextValue;
423 this._proxy.$updateGroup(this._sourceControlHandle, this.handle, this.features);
424 }
426 > private _hideWhenEmpty: boolean | undefined = undefined;
427 > get hideWhenEmpty(): boolean | undefined { return this._hideWhenEmpty; }
428 > set hideWhenEmpty(hideWhenEmpty: boolean | undefined) {
429 this._hideWhenEmpty = hideWhenEmpty;
430 this._proxy.$updateGroup(this._sourceControlHandle, this.handle, this.features);
431 }
433 > get features(): SCMGroupFeatures {
434 return {
435 contextValue: this.contextValue,
436 hideWhenEmpty: this.hideWhenEmpty
437 };
438 }
440 > get resourceStates(): vscode.SourceControlResourceState[] { return [...this._resourceStates]; }
441 > set resourceStates(resources: vscode.SourceControlResourceState[]) {
442 this._resourceStates = [...resources];
443 this._onDidUpdateResourceStates.fire();
444 }
446 > readonly handle = ExtHostSourceControlResourceGroup._handlePool++;
447 >
448 > constructor(
449 private _proxy: MainThreadSCMShape,
450 private _commands: ExtHostCommands,
451 private _sourceControlHandle: number,
452 private _id: string,
453 private _label: string,
454 public readonly multiDiffEditorEnableViewChanges: boolean,
455 private readonly _extension: IExtensionDescription,
456 ) { }
458 > getResourceState(handle: number): vscode.SourceControlResourceState | undefined {
459 return this._resourceStatesMap.get(handle);
460 }
462 > $executeResourceCommand(handle: number, preserveFocus: boolean): Promise<void> {
463 const command = this._resourceStatesCommandsMap.get(handle);
464
465 if (!command) {
466 return Promise.resolve(undefined);
467 }
468
469 return asPromise(() => this._commands.executeCommand(command.command, ...(command.arguments || []), preserveFocus));
470 }
472 > _takeResourceStateSnapshot(): SCMRawResourceSplice[] {
473 const snapshot = [...this._resourceStates].sort(compareResourceStates);
474 const diffs = sortedDiff(this._resourceSnapshot, snapshot, compareResourceStates);
475
476 const splices = diffs.map<ISplice<{ rawResource: SCMRawResource; handle: number }>>(diff => {
477 const toInsert = diff.toInsert.map(r => {
478 const handle = this._resourceHandlePool++;
479 this._resourceStatesMap.set(handle, r);
480
481 const sourceUri = r.resourceUri;
482
483 let command: ICommandDto | undefined;
484 if (r.command) {
485 if (r.command.command === 'vscode.open' || r.command.command === 'vscode.diff' || r.command.command === 'vscode.changes') {
486 const disposables = new DisposableStore();
487 command = this._commands.converter.toInternal(r.command, disposables);
488 this._resourceStatesDisposablesMap.set(handle, disposables);
489 } else {
490 this._resourceStatesCommandsMap.set(handle, r.command);
491 }
492 }
493
494 const hasScmMultiDiffEditorProposalEnabled = isProposedApiEnabled(this._extension, 'scmMultiDiffEditor');
495 const multiFileDiffEditorOriginalUri = hasScmMultiDiffEditorProposalEnabled ? r.multiDiffEditorOriginalUri : undefined;
496 const multiFileDiffEditorModifiedUri = hasScmMultiDiffEditorProposalEnabled ? r.multiFileDiffEditorModifiedUri : undefined;
497
498 const icon = getIconResource(r.decorations);
499 const lightIcon = r.decorations && getIconResource(r.decorations.light) || icon;
500 const darkIcon = r.decorations && getIconResource(r.decorations.dark) || icon;
501 const icons: SCMRawResource[2] = [lightIcon, darkIcon];
502
503 const tooltip = (r.decorations && r.decorations.tooltip) || '';
504 const strikeThrough = r.decorations && !!r.decorations.strikeThrough;
505 const faded = r.decorations && !!r.decorations.faded;
506 const contextValue = r.contextValue || '';
507
508 const rawResource = [handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command, multiFileDiffEditorOriginalUri, multiFileDiffEditorModifiedUri] as SCMRawResource;
509
510 return { rawResource, handle };
511 });
512
513 return { start: diff.start, deleteCount: diff.deleteCount, toInsert };
514 });
515
516 const rawResourceSplices = splices
517 .map(({ start, deleteCount, toInsert }) => [start, deleteCount, toInsert.map(i => i.rawResource)] as SCMRawResourceSplice);
518
519 const reverseSplices = splices.reverse();
520
521 for (const { start, deleteCount, toInsert } of reverseSplices) {
522 const handles = toInsert.map(i => i.handle);
523 const handlesToDelete = this._handlesSnapshot.splice(start, deleteCount, ...handles);
524
525 for (const handle of handlesToDelete) {
526 this._resourceStatesMap.delete(handle);
527 this._resourceStatesCommandsMap.delete(handle);
528 this._resourceStatesDisposablesMap.get(handle)?.dispose();
529 this._resourceStatesDisposablesMap.delete(handle);
530 }
531 }
532
533 this._resourceSnapshot = snapshot;
534 return rawResourceSplices;
535 }
537 > dispose(): void {
538 this._disposed = true;
539 this._onDidDispose.fire();
540 this._onDidUpdateResourceStates.dispose();
541 this._onDidDispose.dispose();
542 }
544 >
545 > class ExtHostSourceControl implements vscode.SourceControl {
546 >
547 > private static _handlePool: number = 0;
548 >
549 > readonly onDidDisposeParent: Event<void>;
550 >
551 > private readonly _onDidDispose = new Emitter<void>();
552 > readonly onDidDispose = this._onDidDispose.event;
553 >
554 >
555 > #proxy: MainThreadSCMShape;
556 >
557 > private _groups: Map<GroupHandle, ExtHostSourceControlResourceGroup> = new Map<GroupHandle, ExtHostSourceControlResourceGroup>();
558 >
559 > get id(): string {
560 return this._id;
561 }
563 > get label(): string {
564 return this._label;
565 }
567 > get rootUri(): vscode.Uri | undefined {
568 return this._rootUri;
569 }
571 > private _contextValue: string | undefined = undefined;
572 >
573 > get contextValue(): string | undefined {
574 checkProposedApiEnabled(this._extension, 'scmProviderOptions');
575 return this._contextValue;
576 }
578 > set contextValue(contextValue: string | undefined) {
579 checkProposedApiEnabled(this._extension, 'scmProviderOptions');
580
581 if (this._contextValue === contextValue) {
582 return;
583 }
584
585 this._contextValue = contextValue;
586 this.#proxy.$updateSourceControl(this.handle, { contextValue });
587 }
589 > private _inputBox: ExtHostSCMInputBox;
590 > get inputBox(): ExtHostSCMInputBox { return this._inputBox; }
591 >
592 > private _count: number | undefined = undefined;
593 >
594 > get count(): number | undefined {
595 return this._count;
596 }
598 > set count(count: number | undefined) {
599 if (this._count === count) {
600 return;
601 }
602
603 this._count = count;
604 this.#proxy.$updateSourceControl(this.handle, { count });
605 }
607 > private _quickDiffProvider: vscode.QuickDiffProvider | undefined = undefined;
608 >
609 > get quickDiffProvider(): vscode.QuickDiffProvider | undefined {
610 return this._quickDiffProvider;
611 }
613 > set quickDiffProvider(quickDiffProvider: vscode.QuickDiffProvider | undefined) {
614 this._quickDiffProvider = quickDiffProvider;
615 let quickDiffLabel = undefined;
616 if (isProposedApiEnabled(this._extension, 'quickDiffProvider')) {
617 quickDiffLabel = quickDiffProvider?.label;
618 }
619 this.#proxy.$updateSourceControl(this.handle, { hasQuickDiffProvider: !!quickDiffProvider, quickDiffLabel });
620 }
622 > private _secondaryQuickDiffProvider: vscode.QuickDiffProvider | undefined = undefined;
623 >
624 > get secondaryQuickDiffProvider(): vscode.QuickDiffProvider | undefined {
625 checkProposedApiEnabled(this._extension, 'quickDiffProvider');
626 return this._secondaryQuickDiffProvider;
627 }
629 > set secondaryQuickDiffProvider(secondaryQuickDiffProvider: vscode.QuickDiffProvider | undefined) {
630 checkProposedApiEnabled(this._extension, 'quickDiffProvider');
631
632 this._secondaryQuickDiffProvider = secondaryQuickDiffProvider;
633 const secondaryQuickDiffLabel = secondaryQuickDiffProvider?.label;
634 this.#proxy.$updateSourceControl(this.handle, { hasSecondaryQuickDiffProvider: !!secondaryQuickDiffProvider, secondaryQuickDiffLabel });
635 }
637 > private _historyProvider: vscode.SourceControlHistoryProvider | undefined;
638 > private readonly _historyProviderDisposable = new MutableDisposable<DisposableStore>();
639 >
640 > get historyProvider(): vscode.SourceControlHistoryProvider | undefined {
641 checkProposedApiEnabled(this._extension, 'scmHistoryProvider');
642 return this._historyProvider;
643 }
645 > set historyProvider(historyProvider: vscode.SourceControlHistoryProvider | undefined) {
646 checkProposedApiEnabled(this._extension, 'scmHistoryProvider');
647
648 this._historyProvider = historyProvider;
649 this._historyProviderDisposable.value = new DisposableStore();
650
651 this.#proxy.$updateSourceControl(this.handle, { hasHistoryProvider: !!historyProvider });
652
653 if (historyProvider) {
654 this._historyProviderDisposable.value.add(historyProvider.onDidChangeCurrentHistoryItemRefs(() => {
655 const historyItemRef = toSCMHistoryItemRefDto(historyProvider?.currentHistoryItemRef);
656 const historyItemRemoteRef = toSCMHistoryItemRefDto(historyProvider?.currentHistoryItemRemoteRef);
657 const historyItemBaseRef = toSCMHistoryItemRefDto(historyProvider?.currentHistoryItemBaseRef);
658
659 this.#proxy.$onDidChangeHistoryProviderCurrentHistoryItemRefs(this.handle, historyItemRef, historyItemRemoteRef, historyItemBaseRef);
660 }));
661 this._historyProviderDisposable.value.add(historyProvider.onDidChangeHistoryItemRefs((e) => {
662 if (e.added.length === 0 && e.modified.length === 0 && e.removed.length === 0) {
663 return;
664 }
665
666 const added = e.added.map(ref => ({ ...ref, icon: getHistoryItemIconDto(ref.icon) }));
667 const modified = e.modified.map(ref => ({ ...ref, icon: getHistoryItemIconDto(ref.icon) }));
668 const removed = e.removed.map(ref => ({ ...ref, icon: getHistoryItemIconDto(ref.icon) }));
669
670 this.#proxy.$onDidChangeHistoryProviderHistoryItemRefs(this.handle, { added, modified, removed, silent: e.silent });
671 }));
672 }
673 }
675 > private _artifactProvider: vscode.SourceControlArtifactProvider | undefined;
676 > private readonly _artifactProviderDisposable = new MutableDisposable<DisposableStore>();
677 >
678 > get artifactProvider(): vscode.SourceControlArtifactProvider | undefined {
679 checkProposedApiEnabled(this._extension, 'scmArtifactProvider');
680 return this._artifactProvider;
681 }
683 > set artifactProvider(artifactProvider: vscode.SourceControlArtifactProvider | undefined) {
684 checkProposedApiEnabled(this._extension, 'scmArtifactProvider');
685
686 this._artifactProvider = artifactProvider;
687 this._artifactProviderDisposable.value = new DisposableStore();
688
689 this.#proxy.$updateSourceControl(this.handle, { hasArtifactProvider: !!artifactProvider });
690
691 if (artifactProvider) {
692 this._artifactProviderDisposable.value.add(artifactProvider.onDidChangeArtifacts((groups: string[]) => {
693 if (groups.length !== 0) {
694 this.#proxy.$onDidChangeArtifacts(this.handle, groups);
695 }
696 }));
697 }
698 }
700 > private _commitTemplate: string | undefined = undefined;
701 >
702 > get commitTemplate(): string | undefined {
703 return this._commitTemplate;
704 }
706 > set commitTemplate(commitTemplate: string | undefined) {
707 if (commitTemplate === this._commitTemplate) {
708 return;
709 }
710
711 this._commitTemplate = commitTemplate;
712 this.#proxy.$updateSourceControl(this.handle, { commitTemplate });
713 }
715 > private readonly _acceptInputDisposables = new MutableDisposable<DisposableStore>();
716 > private _acceptInputCommand: vscode.Command | undefined = undefined;
717 >
718 > get acceptInputCommand(): vscode.Command | undefined {
719 return this._acceptInputCommand;
720 }
722 > set acceptInputCommand(acceptInputCommand: vscode.Command | undefined) {
723 this._acceptInputDisposables.value = new DisposableStore();
724
725 this._acceptInputCommand = acceptInputCommand;
726
727 const internal = this._commands.converter.toInternal(acceptInputCommand, this._acceptInputDisposables.value);
728 this.#proxy.$updateSourceControl(this.handle, { acceptInputCommand: internal });
729 }
731 > // We know what we're doing here:
732 > // eslint-disable-next-line local/code-no-potentially-unsafe-disposables
733 > private _actionButtonDisposables = new DisposableStore();
734 > private _actionButton: vscode.SourceControlActionButton | undefined;
735 > get actionButton(): vscode.SourceControlActionButton | undefined {
736 checkProposedApiEnabled(this._extension, 'scmActionButton');
737 return this._actionButton;
738 }
740 > set actionButton(actionButton: vscode.SourceControlActionButton | undefined) {
741 checkProposedApiEnabled(this._extension, 'scmActionButton');
742
743 // We have to do this check before converting the command to it's internal
744 // representation since that would always create a command with a unique
745 // identifier
746 if (structuralEquals(this._actionButton, actionButton)) {
747 return;
748 }
749
750 // In order to prevent disposing the action button command that are still rendered in the UI
751 // until the next UI update, we ensure to dispose them after the update has been completed.
752 const oldActionButtonDisposables = this._actionButtonDisposables;
753 this._actionButtonDisposables = new DisposableStore();
754
755 this._actionButton = actionButton;
756
757 const actionButtonDto = actionButton !== undefined ?
758 {
759 command: {
760 ...this._commands.converter.toInternal(actionButton.command, this._actionButtonDisposables),
761 shortTitle: actionButton.command.shortTitle
762 },
763 secondaryCommands: actionButton.secondaryCommands?.map(commandGroup => {
764 return commandGroup.map(command => this._commands.converter.toInternal(command, this._actionButtonDisposables));
765 }),
766 enabled: actionButton.enabled
767 } satisfies SCMActionButtonDto : null;
768
769 this.#proxy.$updateSourceControl(this.handle, { actionButton: actionButtonDto })
770 .finally(() => oldActionButtonDisposables.dispose());
771 }
773 > // We know what we're doing here:
774 > // eslint-disable-next-line local/code-no-potentially-unsafe-disposables
775 > private _statusBarDisposables = new DisposableStore();
776 > private _statusBarCommands: vscode.Command[] | undefined = undefined;
777 >
778 > get statusBarCommands(): vscode.Command[] | undefined {
779 return this._statusBarCommands;
780 }
782 > set statusBarCommands(statusBarCommands: vscode.Command[] | undefined) {
783 if (this._statusBarCommands && statusBarCommands && commandListEquals(this._statusBarCommands, statusBarCommands)) {
784 return;
785 }
786
787 // In order to prevent disposing status bar commands that are still rendered in the UI
788 // until the next UI update, we ensure to dispose them after the update has been completed.
789 const oldStatusBarDisposables = this._statusBarDisposables;
790 this._statusBarDisposables = new DisposableStore();
791
792 this._statusBarCommands = statusBarCommands;
793
794 const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c, this._statusBarDisposables)) as ICommandDto[];
795
796 this.#proxy.$updateSourceControl(this.handle, { statusBarCommands: internal })
797 .finally(() => oldStatusBarDisposables.dispose());
798 }
800 > private _selected: boolean = false;
801 >
802 > get selected(): boolean {
803 return this._selected;
804 }
806 > private readonly _onDidChangeSelection = new Emitter<boolean>();
807 > readonly onDidChangeSelection = this._onDidChangeSelection.event;
808 >
809 > private readonly _artifactCommandsDisposables = new DisposableMap<string /* artifact group */, DisposableStore>();
810 >
811 > readonly handle: number = ExtHostSourceControl._handlePool++;
812 >
813 > constructor(
814 > private readonly _extension: IExtensionDescription,
815 > _extHostDocuments: ExtHostDocuments,
816 > proxy: MainThreadSCMShape,
817 > private _commands: ExtHostCommands,
818 > private _id: string,
819 > private _label: string,
820 > private _rootUri?: vscode.Uri,
821 > _iconPath?: vscode.IconPath,
822 > _isHidden?: boolean,
823 > _parent?: ExtHostSourceControl
824 > ) {
825 > this.#proxy = proxy;
826 >
827 > const inputBoxDocumentUri = URI.from({
828 > scheme: Schemas.vscodeSourceControl,
829 > path: `${_id}/scm${this.handle}/input`,
830 > query: _rootUri ? `rootUri=${encodeURIComponent(_rootUri.toString())}` : undefined
831 > });
832 >
833 > this._inputBox = new ExtHostSCMInputBox(_extension, _extHostDocuments, this.#proxy, this.handle, inputBoxDocumentUri);
834 > this.#proxy.$registerSourceControl(this.handle, _parent?.handle, _id, _label, _rootUri, getHistoryItemIconDto(_iconPath), _isHidden, inputBoxDocumentUri);
835 >
836 > this.onDidDisposeParent = _parent ? _parent.onDidDispose : Event.None;
837 > }
838 >
839 > private createdResourceGroups = new Map<ExtHostSourceControlResourceGroup, IDisposable>();
840 > private updatedResourceGroups = new Set<ExtHostSourceControlResourceGroup>();
841 >
842 > createResourceGroup(id: string, label: string, options?: { multiDiffEditorEnableViewChanges?: boolean }): ExtHostSourceControlResourceGroup {
843 const multiDiffEditorEnableViewChanges = isProposedApiEnabled(this._extension, 'scmMultiDiffEditor') && options?.multiDiffEditorEnableViewChanges === true;
844 const group = new ExtHostSourceControlResourceGroup(this.#proxy, this._commands, this.handle, id, label, multiDiffEditorEnableViewChanges, this._extension);
845 const disposable = Event.once(group.onDidDispose)(() => this.createdResourceGroups.delete(group));
846 this.createdResourceGroups.set(group, disposable);
847 this.eventuallyAddResourceGroups();
848 return group;
849 }
851 > @debounce(100)
852 > eventuallyAddResourceGroups(): void {
853 const groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures, /*multiDiffEditorEnableViewChanges*/ boolean][] = [];
854 const splices: SCMRawResourceSplices[] = [];
855
856 for (const [group, disposable] of this.createdResourceGroups) {
857 disposable.dispose();
858
859 const updateListener = group.onDidUpdateResourceStates(() => {
860 this.updatedResourceGroups.add(group);
861 this.eventuallyUpdateResourceStates();
862 });
863
864 Event.once(group.onDidDispose)(() => {
865 this.updatedResourceGroups.delete(group);
866 updateListener.dispose();
867 this._groups.delete(group.handle);
868 this.#proxy.$unregisterGroup(this.handle, group.handle);
869 });
870
871 groups.push([group.handle, group.id, group.label, group.features, group.multiDiffEditorEnableViewChanges]);
872
873 const snapshot = group._takeResourceStateSnapshot();
874
875 if (snapshot.length > 0) {
876 splices.push([group.handle, snapshot]);
877 }
878
879 this._groups.set(group.handle, group);
880 }
881
882 this.#proxy.$registerGroups(this.handle, groups, splices);
883 this.createdResourceGroups.clear();
884 }
886 > @debounce(100)
887 > eventuallyUpdateResourceStates(): void {
888 const splices: SCMRawResourceSplices[] = [];
889
890 this.updatedResourceGroups.forEach(group => {
891 const snapshot = group._takeResourceStateSnapshot();
892
893 if (snapshot.length === 0) {
894 return;
895 }
896
897 splices.push([group.handle, snapshot]);
898 });
899
900 if (splices.length > 0) {
901 this.#proxy.$spliceResourceStates(this.handle, splices);
902 }
903
904 this.updatedResourceGroups.clear();
905 }
907 > getResourceGroup(handle: GroupHandle): ExtHostSourceControlResourceGroup | undefined {
908 return this._groups.get(handle);
909 }
911 > setSelectionState(selected: boolean): void {
912 this._selected = selected;
913 this._onDidChangeSelection.fire(selected);
914 }
916 > async provideArtifacts(group: string, token: CancellationToken): Promise<SCMArtifactDto[] | undefined> {
917 const commandsDisposables = new DisposableStore();
918 const artifacts = await this.artifactProvider?.provideArtifacts(group, token);
919 const artifactsDto = artifacts?.map(artifact => ({
920 ...artifact,
921 icon: getHistoryItemIconDto(artifact.icon),
922 command: artifact.command ? this._commands.converter.toInternal(artifact.command, commandsDisposables) : undefined
923 }));
924
925 this._artifactCommandsDisposables.get(group)?.dispose();
926 this._artifactCommandsDisposables.set(group, commandsDisposables);
927
928 return artifactsDto;
929 }
931 > dispose(): void {
932 > this._acceptInputDisposables.dispose();
933 > this._actionButtonDisposables.dispose();
934 > this._statusBarDisposables.dispose();
935 > this._historyProviderDisposable.dispose();
936 > this._artifactProviderDisposable.dispose();
937 > this._artifactCommandsDisposables.dispose();
938 >
939 > this._groups.forEach(group => group.dispose());
940 > this.#proxy.$unregisterSourceControl(this.handle);
941 >
942 > this._onDidChangeSelection.dispose();
943 > this._onDidDispose.fire();
944 > this._onDidDispose.dispose();
945 > }
946 > }
947 >
948 > export class ExtHostSCM implements ExtHostSCMShape {
949 >
950 > private _proxy: MainThreadSCMShape;
951 > private readonly _telemetry: MainThreadTelemetryShape;
952 > private _sourceControls: Map<ProviderHandle, ExtHostSourceControl> = new Map<ProviderHandle, ExtHostSourceControl>();
953 > private _sourceControlsByExtension: ExtensionIdentifierMap<ExtHostSourceControl[]> = new ExtensionIdentifierMap<ExtHostSourceControl[]>();
954 >
955 > private readonly _onDidChangeActiveProvider = new Emitter<vscode.SourceControl>();
956 > get onDidChangeActiveProvider(): Event<vscode.SourceControl> { return this._onDidChangeActiveProvider.event; }
957 >
958 > private _selectedSourceControlHandle: number | undefined;
959 >
960 > constructor(
961 > mainContext: IMainContext,
962 > private _commands: ExtHostCommands,
963 > private _extHostDocuments: ExtHostDocuments,
964 > @ILogService private readonly logService: ILogService
965 > ) {
966 > this._proxy = mainContext.getProxy(MainContext.MainThreadSCM);
967 > this._telemetry = mainContext.getProxy(MainContext.MainThreadTelemetry);
968 >
969 > _commands.registerArgumentProcessor({
970 > processArgument: arg => {
971 if (arg && arg.$mid === MarshalledId.ScmResource) {
972 const sourceControl = this._sourceControls.get(arg.sourceControlHandle);
973
974 if (!sourceControl) {
975 return arg;
976 }
977
978 const group = sourceControl.getResourceGroup(arg.groupHandle);
979
980 if (!group) {
981 return arg;
982 }
983
984 return group.getResourceState(arg.handle);
985 } else if (arg && arg.$mid === MarshalledId.ScmResourceGroup) {
986 const sourceControl = this._sourceControls.get(arg.sourceControlHandle);
987
988 if (!sourceControl) {
989 return arg;
990 }
991
992 return sourceControl.getResourceGroup(arg.groupHandle);
993 } else if (arg && arg.$mid === MarshalledId.ScmProvider) {
994 const sourceControl = this._sourceControls.get(arg.handle);
995
996 if (!sourceControl) {
997 return arg;
998 }
999
1000 return sourceControl;
1001 }
1002
1003 return arg;
1004 }
1005 > }); extHostSCM.ts ×86
1006 > }
1007 >
1008 > createSourceControl(extension: IExtensionDescription, id: string, label: string, rootUri: vscode.Uri | undefined, iconPath: vscode.IconPath | undefined, isHidden: boolean | undefined, parent: vscode.SourceControl | undefined): vscode.SourceControl {
1009 > this.logService.trace('ExtHostSCM#createSourceControl', extension.identifier.value, id, label, rootUri);
1010 >
1011 > type TEvent = { extensionId: string };
1012 > type TMeta = {
1013 > owner: 'joaomoreno';
1014 > extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the extension contributing to the Source Control API.' };
1015 > comment: 'This is used to know what extensions contribute to the Source Control API.';
1016 > };
1017 > this._telemetry.$publicLog2<TEvent, TMeta>('api/scm/createSourceControl', {
1018 > extensionId: extension.identifier.value,
1019 > });
1020 >
1021 > const parentSourceControl = parent ? Iterable.find(this._sourceControls.values(), s => s === parent) : undefined;
1022 > const sourceControl = new ExtHostSourceControl(extension, this._extHostDocuments, this._proxy, this._commands, id, label, rootUri, iconPath, isHidden, parentSourceControl);
1023 > this._sourceControls.set(sourceControl.handle, sourceControl);
1024 >
1025 > const sourceControls = this._sourceControlsByExtension.get(extension.identifier) || [];
1026 > sourceControls.push(sourceControl);
1027 > this._sourceControlsByExtension.set(extension.identifier, sourceControls);
1028 >
1029 > Event.once(sourceControl.onDidDispose)(() => {
1030 > this.logService.trace('ExtHostSCM#disposeSourceControl', extension.identifier.value, id, label, rootUri);
1031 >
1032 > this._sourceControls.delete(sourceControl.handle);
1033 >
1034 > const sourceControls = this._sourceControlsByExtension.get(extension.identifier);
1035 > if (sourceControls) {
1036 > const index = sourceControls.indexOf(sourceControl);
1037 > if (index !== -1) {
1038 > sourceControls.splice(index, 1);
1039 > }
1040 >
1041 > if (sourceControls.length === 0) {
1042 > this._sourceControlsByExtension.delete(extension.identifier);
1043 > }
1044 > }
1045 > });
1046 >
1047 > return sourceControl;
1048 > }
1049 >
1050 > // Deprecated
1051 > getLastInputBox(extension: IExtensionDescription): ExtHostSCMInputBox | undefined {
1052 > this.logService.trace('ExtHostSCM#getLastInputBox', extension.identifier.value);
1053 >
1054 > const sourceControls = this._sourceControlsByExtension.get(extension.identifier);
1055 > const sourceControl = sourceControls && sourceControls[sourceControls.length - 1];
1056 > return sourceControl && sourceControl.inputBox;
1057 > }
1058 >
1059 > $provideOriginalResource(sourceControlHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<UriComponents | null> {
1060 const uri = URI.revive(uriComponents);
1061 this.logService.trace('ExtHostSCM#$provideOriginalResource', sourceControlHandle, uri.toString());
1062
1063 const sourceControl = this._sourceControls.get(sourceControlHandle);
1064
1065 if (!sourceControl || !sourceControl.quickDiffProvider || !sourceControl.quickDiffProvider.provideOriginalResource) {
1066 return Promise.resolve(null);
1067 }
1068
1069 return asPromise(() => sourceControl.quickDiffProvider!.provideOriginalResource!(uri, token))
1070 .then<UriComponents | null>(r => r || null);
1071 }
1073 > $provideSecondaryOriginalResource(sourceControlHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<UriComponents | null> {
1074 const uri = URI.revive(uriComponents);
1075 this.logService.trace('ExtHostSCM#$provideSecondaryOriginalResource', sourceControlHandle, uri.toString());
1076
1077 const sourceControl = this._sourceControls.get(sourceControlHandle);
1078
1079 if (!sourceControl || !sourceControl.secondaryQuickDiffProvider || !sourceControl.secondaryQuickDiffProvider.provideOriginalResource) {
1080 return Promise.resolve(null);
1081 }
1082
1083 return asPromise(() => sourceControl.secondaryQuickDiffProvider!.provideOriginalResource!(uri, token))
1084 .then<UriComponents | null>(r => r || null);
1085 }
1087 > $onInputBoxValueChange(sourceControlHandle: number, value: string): Promise<void> {
1088 this.logService.trace('ExtHostSCM#$onInputBoxValueChange', sourceControlHandle);
1089
1090 const sourceControl = this._sourceControls.get(sourceControlHandle);
1091
1092 if (!sourceControl) {
1093 return Promise.resolve(undefined);
1094 }
1095
1096 sourceControl.inputBox.$onInputBoxValueChange(value);
1097 return Promise.resolve(undefined);
1098 }
1100 > $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise<void> {
1101 this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle);
1102
1103 const sourceControl = this._sourceControls.get(sourceControlHandle);
1104
1105 if (!sourceControl) {
1106 return Promise.resolve(undefined);
1107 }
1108
1109 const group = sourceControl.getResourceGroup(groupHandle);
1110
1111 if (!group) {
1112 return Promise.resolve(undefined);
1113 }
1114
1115 return group.$executeResourceCommand(handle, preserveFocus);
1116 }
1118 > $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined> {
1119 this.logService.trace('ExtHostSCM#$validateInput', sourceControlHandle);
1120
1121 const sourceControl = this._sourceControls.get(sourceControlHandle);
1122
1123 if (!sourceControl) {
1124 return Promise.resolve(undefined);
1125 }
1126
1127 if (!sourceControl.inputBox.validateInput) {
1128 return Promise.resolve(undefined);
1129 }
1130
1131 return asPromise(() => sourceControl.inputBox.validateInput!(value, cursorPosition)).then(result => {
1132 if (!result) {
1133 return Promise.resolve(undefined);
1134 }
1135
1136 const message = MarkdownString.fromStrict(result.message);
1137 if (!message) {
1138 return Promise.resolve(undefined);
1139 }
1140
1141 return Promise.resolve<[string | IMarkdownString, number]>([message, result.type]);
1142 });
1143 }
1145 > $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise<void> {
1146 this.logService.trace('ExtHostSCM#$setSelectedSourceControl', selectedSourceControlHandle);
1147 if (this._selectedSourceControlHandle === selectedSourceControlHandle) {
1148 return Promise.resolve(undefined);
1149 }
1150
1151 if (selectedSourceControlHandle !== undefined) {
1152 this._sourceControls.get(selectedSourceControlHandle)?.setSelectionState(true);
1153 }
1154
1155 if (this._selectedSourceControlHandle !== undefined) {
1156 this._sourceControls.get(this._selectedSourceControlHandle)?.setSelectionState(false);
1157 }
1158
1159 this._selectedSourceControlHandle = selectedSourceControlHandle;
1160 return Promise.resolve(undefined);
1161 }
1163 > async $resolveHistoryItem(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise<SCMHistoryItemDto | undefined> {
1164 try {
1165 const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
1166 const historyItem = await historyProvider?.resolveHistoryItem(historyItemId, token);
1167
1168 return historyItem ? toSCMHistoryItemDto(historyItem) : undefined;
1169 }
1170 catch (err) {
1171 this.logService.error('ExtHostSCM#$resolveHistoryItem', err);
1172 return undefined;
1173 }
1174 }
1176 > async $resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise<string | undefined> {
1177 try {
1178 const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
1179 const chatContext = await historyProvider?.resolveHistoryItemChatContext(historyItemId, token);
1180
1181 return chatContext ?? undefined;
1182 }
1183 catch (err) {
1184 this.logService.error('ExtHostSCM#$resolveHistoryItemChatContext', err);
1185 return undefined;
1186 }
1187 }
1189 > async $resolveHistoryItemChangeRangeChatContext(sourceControlHandle: number, historyItemId: string, historyItemParentId: string, path: string, token: CancellationToken): Promise<string | undefined> {
1190 try {
1191 const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
1192 const chatContext = await historyProvider?.resolveHistoryItemChangeRangeChatContext?.(historyItemId, historyItemParentId, path, token);
1193
1194 return chatContext ?? undefined;
1195 }
1196 catch (err) {
1197 this.logService.error('ExtHostSCM#$resolveHistoryItemChangeRangeChatContext', err);
1198 return undefined;
1199 }
1200 }
1202 > async $resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise<string | undefined> {
1203 try {
1204 const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
1205 const ancestor = await historyProvider?.resolveHistoryItemRefsCommonAncestor(historyItemRefs, token);
1206
1207 return ancestor ?? undefined;
1208 }
1209 catch (err) {
1210 this.logService.error('ExtHostSCM#$resolveHistoryItemRefsCommonAncestor', err);
1211 return undefined;
1212 }
1213 }
1215 > async $provideHistoryItemRefs(sourceControlHandle: number, historyItemRefs: string[] | undefined, token: CancellationToken): Promise<SCMHistoryItemRefDto[] | undefined> {
1216 try {
1217 const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
1218 const refs = await historyProvider?.provideHistoryItemRefs(historyItemRefs, token);
1219
1220 return refs?.map(ref => ({ ...ref, icon: getHistoryItemIconDto(ref.icon) })) ?? undefined;
1221 }
1222 catch (err) {
1223 this.logService.error('ExtHostSCM#$provideHistoryItemRefs', err);
1224 return undefined;
1225 }
1226 }
1228 > async $provideHistoryItems(sourceControlHandle: number, options: vscode.SourceControlHistoryOptions, token: CancellationToken): Promise<SCMHistoryItemDto[] | undefined> {
1229 try {
1230 const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
1231 const historyItems = await historyProvider?.provideHistoryItems(options, token);
1232
1233 return historyItems?.map(item => toSCMHistoryItemDto(item)) ?? undefined;
1234 }
1235 catch (err) {
1236 this.logService.error('ExtHostSCM#$provideHistoryItems', err);
1237 return undefined;
1238 }
1239 }
1241 > async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemChangeDto[] | undefined> {
1242 try {
1243 const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
1244 const changes = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId, token);
1245
1246 return changes ?? undefined;
1247 }
1248 catch (err) {
1249 this.logService.error('ExtHostSCM#$provideHistoryItemChanges', err);
1250 return undefined;
1251 }
1252 }
1254 > async $provideArtifactGroups(sourceControlHandle: number, token: CancellationToken): Promise<SCMArtifactGroupDto[] | undefined> {
1255 try {
1256 const artifactProvider = this._sourceControls.get(sourceControlHandle)?.artifactProvider;
1257 const groups = await artifactProvider?.provideArtifactGroups(token);
1258
1259 return groups?.map(group => ({
1260 ...group,
1261 icon: getHistoryItemIconDto(group.icon)
1262 }));
1263 }
1264 catch (err) {
1265 this.logService.error('ExtHostSCM#$provideArtifactGroups', err);
1266 return undefined;
1267 }
1268 }
1270 > async $provideArtifacts(sourceControlHandle: number, group: string, token: CancellationToken): Promise<SCMArtifactDto[] | undefined> {
1271 try {
1272 const sourceControl = this._sourceControls.get(sourceControlHandle);
1273 return sourceControl?.provideArtifacts(group, token);
1274 }
1275 catch (err) {
1276 this.logService.error('ExtHostSCM#$provideArtifacts', err);
1277 return undefined;
1278 }
1279 }