Path: blob/main/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { ValueWithChangeEvent } from '../../../../base/common/event.js';6import { Disposable } from '../../../../base/common/lifecycle.js';7import { observableFromEvent, ValueWithChangeEventFromObservable, waitForState } from '../../../../base/common/observable.js';8import { basename } from '../../../../base/common/path.js';9import { URI, UriComponents } from '../../../../base/common/uri.js';10import { IMultiDiffEditorOptions } from '../../../../editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.js';11import { localize2 } from '../../../../nls.js';12import { Action2 } from '../../../../platform/actions/common/actions.js';13import { ContextKeyValue } from '../../../../platform/contextkey/common/contextkey.js';14import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';15import { IActivityService, ProgressBadge } from '../../../services/activity/common/activity.js';16import { IEditorService } from '../../../services/editor/common/editorService.js';17import { ISCMHistoryItem } from '../../scm/common/history.js';18import { ISCMProvider, ISCMRepository, ISCMResourceGroup, ISCMService } from '../../scm/common/scm.js';19import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from './multiDiffSourceResolverService.js';2021export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver {22private static readonly _scheme = 'scm-multi-diff-source';2324public static getMultiDiffSourceUri(repositoryUri: string, groupId: string): URI {25return URI.from({26scheme: ScmMultiDiffSourceResolver._scheme,27query: JSON.stringify({ repositoryUri, groupId } satisfies UriFields),28});29}3031private static parseUri(uri: URI): { repositoryUri: URI; groupId: string } | undefined {32if (uri.scheme !== ScmMultiDiffSourceResolver._scheme) {33return undefined;34}3536let query: UriFields;37try {38query = JSON.parse(uri.query) as UriFields;39} catch (e) {40return undefined;41}4243if (typeof query !== 'object' || query === null) {44return undefined;45}4647const { repositoryUri, groupId } = query;48if (typeof repositoryUri !== 'string' || typeof groupId !== 'string') {49return undefined;50}5152return { repositoryUri: URI.parse(repositoryUri), groupId };53}5455constructor(56@ISCMService private readonly _scmService: ISCMService,57@IActivityService private readonly _activityService: IActivityService,58) {59}6061canHandleUri(uri: URI): boolean {62return ScmMultiDiffSourceResolver.parseUri(uri) !== undefined;63}6465async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {66const { repositoryUri, groupId } = ScmMultiDiffSourceResolver.parseUri(uri)!;6768const repository = await waitForState(observableFromEvent(this,69this._scmService.onDidAddRepository,70() => [...this._scmService.repositories].find(r => r.provider.rootUri?.toString() === repositoryUri.toString()))71);72const group = await waitForState(observableFromEvent(this,73repository.provider.onDidChangeResourceGroups,74() => repository.provider.groups.find(g => g.id === groupId)75));7677const scmActivities = observableFromEvent(78this._activityService.onDidChangeActivity,79() => [...this._activityService.getViewContainerActivities('workbench.view.scm')],80);81const scmViewHasNoProgressBadge = scmActivities.map(activities => !activities.some(a => a.badge instanceof ProgressBadge));82await waitForState(scmViewHasNoProgressBadge, v => v);8384return new ScmResolvedMultiDiffSource(group, repository);85}86}8788interface ScmHistoryItemUriFields {89readonly repositoryId: string;90readonly historyItemId: string;91readonly historyItemParentId?: string;92readonly historyItemDisplayId?: string;93}9495export class ScmHistoryItemResolver implements IMultiDiffSourceResolver {96static readonly scheme = 'scm-history-item';9798public static getMultiDiffSourceUri(provider: ISCMProvider, historyItem: ISCMHistoryItem): URI {99return URI.from({100scheme: ScmHistoryItemResolver.scheme,101path: provider.rootUri?.fsPath,102query: JSON.stringify({103repositoryId: provider.id,104historyItemId: historyItem.id,105historyItemParentId: historyItem.parentIds.length > 0106? historyItem.parentIds[0]107: undefined,108historyItemDisplayId: historyItem.displayId109} satisfies ScmHistoryItemUriFields)110}, true);111}112113public static parseUri(uri: URI): ScmHistoryItemUriFields | undefined {114if (uri.scheme !== ScmHistoryItemResolver.scheme) {115return undefined;116}117118let query: ScmHistoryItemUriFields;119try {120query = JSON.parse(uri.query) as ScmHistoryItemUriFields;121} catch (e) {122return undefined;123}124125if (typeof query !== 'object' || query === null) {126return undefined;127}128129const { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId } = query;130if (typeof repositoryId !== 'string' || typeof historyItemId !== 'string' ||131(typeof historyItemParentId !== 'string' && historyItemParentId !== undefined) ||132(typeof historyItemDisplayId !== 'string' && historyItemDisplayId !== undefined)) {133return undefined;134}135136return { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId };137}138139constructor(@ISCMService private readonly _scmService: ISCMService) { }140141canHandleUri(uri: URI): boolean {142return ScmHistoryItemResolver.parseUri(uri) !== undefined;143}144145async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {146const { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId } = ScmHistoryItemResolver.parseUri(uri)!;147148const repository = this._scmService.getRepository(repositoryId);149const historyProvider = repository?.provider.historyProvider.get();150const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? [];151152const resources = ValueWithChangeEvent.const<readonly MultiDiffEditorItem[]>(153historyItemChanges.map(change => {154const goToFileEditorTitle = change.modifiedUri155? `${basename(change.modifiedUri.fsPath)} (${historyItemDisplayId ?? historyItemId})`156: undefined;157158return new MultiDiffEditorItem(change.originalUri, change.modifiedUri, change.modifiedUri, goToFileEditorTitle);159})160);161162return { resources };163}164}165166class ScmResolvedMultiDiffSource implements IResolvedMultiDiffSource {167private readonly _resources;168readonly resources;169170public readonly contextKeys: Record<string, ContextKeyValue>;171172constructor(173private readonly _group: ISCMResourceGroup,174private readonly _repository: ISCMRepository,175) {176this._resources = observableFromEvent<MultiDiffEditorItem[]>(177this._group.onDidChangeResources,178() => /** @description resources */ this._group.resources.map(e => new MultiDiffEditorItem(e.multiDiffEditorOriginalUri, e.multiDiffEditorModifiedUri, e.sourceUri))179);180this.resources = new ValueWithChangeEventFromObservable(this._resources);181this.contextKeys = {182scmResourceGroup: this._group.id,183scmProvider: this._repository.provider.providerId,184};185}186}187188interface UriFields {189repositoryUri: string;190groupId: string;191}192193export class ScmMultiDiffSourceResolverContribution extends Disposable {194195static readonly ID = 'workbench.contrib.scmMultiDiffSourceResolver';196197constructor(198@IInstantiationService instantiationService: IInstantiationService,199@IMultiDiffSourceResolverService multiDiffSourceResolverService: IMultiDiffSourceResolverService,200) {201super();202203this._register(multiDiffSourceResolverService.registerResolver(instantiationService.createInstance(ScmHistoryItemResolver)));204this._register(multiDiffSourceResolverService.registerResolver(instantiationService.createInstance(ScmMultiDiffSourceResolver)));205}206}207208interface OpenScmGroupActionOptions {209title: string;210repositoryUri: UriComponents;211resourceGroupId: string;212}213214export class OpenScmGroupAction extends Action2 {215public static async openMultiFileDiffEditor(editorService: IEditorService, label: string, repositoryRootUri: URI | undefined, resourceGroupId: string, options?: IMultiDiffEditorOptions) {216if (!repositoryRootUri) {217return;218}219220const multiDiffSource = ScmMultiDiffSourceResolver.getMultiDiffSourceUri(repositoryRootUri.toString(), resourceGroupId);221return await editorService.openEditor({ label, multiDiffSource, options });222}223224constructor() {225super({226id: '_workbench.openScmMultiDiffEditor',227title: localize2('openChanges', 'Open Changes'),228f1: false229});230}231232async run(accessor: ServicesAccessor, options: OpenScmGroupActionOptions): Promise<void> {233const editorService = accessor.get(IEditorService);234await OpenScmGroupAction.openMultiFileDiffEditor(editorService, options.title, URI.revive(options.repositoryUri), options.resourceGroupId);235}236}237238239