Path: blob/main/src/vs/workbench/contrib/multiDiffEditor/browser/scmMultiDiffSourceResolver.ts
5241 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 { ISCMProvider, ISCMRepository, ISCMResourceGroup, ISCMService } from '../../scm/common/scm.js';18import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from './multiDiffSourceResolverService.js';1920export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver {21private static readonly _scheme = 'scm-multi-diff-source';2223public static getMultiDiffSourceUri(repositoryUri: string, groupId: string): URI {24return URI.from({25scheme: ScmMultiDiffSourceResolver._scheme,26query: JSON.stringify({ repositoryUri, groupId } satisfies UriFields),27});28}2930private static parseUri(uri: URI): { repositoryUri: URI; groupId: string } | undefined {31if (uri.scheme !== ScmMultiDiffSourceResolver._scheme) {32return undefined;33}3435let query: UriFields;36try {37query = JSON.parse(uri.query) as UriFields;38} catch (e) {39return undefined;40}4142if (typeof query !== 'object' || query === null) {43return undefined;44}4546const { repositoryUri, groupId } = query;47if (typeof repositoryUri !== 'string' || typeof groupId !== 'string') {48return undefined;49}5051return { repositoryUri: URI.parse(repositoryUri), groupId };52}5354constructor(55@ISCMService private readonly _scmService: ISCMService,56@IActivityService private readonly _activityService: IActivityService,57) {58}5960canHandleUri(uri: URI): boolean {61return ScmMultiDiffSourceResolver.parseUri(uri) !== undefined;62}6364async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {65const { repositoryUri, groupId } = ScmMultiDiffSourceResolver.parseUri(uri)!;6667const repository = await waitForState(observableFromEvent(this,68this._scmService.onDidAddRepository,69() => [...this._scmService.repositories].find(r => r.provider.rootUri?.toString() === repositoryUri.toString()))70);71const group = await waitForState(observableFromEvent(this,72repository.provider.onDidChangeResourceGroups,73() => repository.provider.groups.find(g => g.id === groupId)74));7576const scmActivities = observableFromEvent(77this._activityService.onDidChangeActivity,78() => [...this._activityService.getViewContainerActivities('workbench.view.scm')],79);80const scmViewHasNoProgressBadge = scmActivities.map(activities => !activities.some(a => a.badge instanceof ProgressBadge));81await waitForState(scmViewHasNoProgressBadge, v => v);8283return new ScmResolvedMultiDiffSource(group, repository);84}85}8687interface ScmHistoryItemUriFields {88readonly repositoryId: string;89readonly historyItemId: string;90readonly historyItemParentId?: string;91readonly historyItemDisplayId?: string;92}9394export class ScmHistoryItemResolver implements IMultiDiffSourceResolver {95static readonly scheme = 'scm-history-item';9697public static getMultiDiffSourceUri(provider: ISCMProvider, historyItemId: string, historyItemParentId: string | undefined, historyItemDisplayId: string | undefined): URI {98return URI.from({99scheme: ScmHistoryItemResolver.scheme,100path: provider.rootUri?.fsPath,101query: JSON.stringify({102repositoryId: provider.id,103historyItemId,104historyItemParentId,105historyItemDisplayId106} satisfies ScmHistoryItemUriFields)107}, true);108}109110public static parseUri(uri: URI): ScmHistoryItemUriFields | undefined {111if (uri.scheme !== ScmHistoryItemResolver.scheme) {112return undefined;113}114115let query: ScmHistoryItemUriFields;116try {117query = JSON.parse(uri.query) as ScmHistoryItemUriFields;118} catch (e) {119return undefined;120}121122if (typeof query !== 'object' || query === null) {123return undefined;124}125126const { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId } = query;127if (typeof repositoryId !== 'string' || typeof historyItemId !== 'string' ||128(typeof historyItemParentId !== 'string' && historyItemParentId !== undefined) ||129(typeof historyItemDisplayId !== 'string' && historyItemDisplayId !== undefined)) {130return undefined;131}132133return { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId };134}135136constructor(@ISCMService private readonly _scmService: ISCMService) { }137138canHandleUri(uri: URI): boolean {139return ScmHistoryItemResolver.parseUri(uri) !== undefined;140}141142async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {143const { repositoryId, historyItemId, historyItemParentId, historyItemDisplayId } = ScmHistoryItemResolver.parseUri(uri)!;144145const repository = this._scmService.getRepository(repositoryId);146const historyProvider = repository?.provider.historyProvider.get();147const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? [];148149const resources = ValueWithChangeEvent.const<readonly MultiDiffEditorItem[]>(150historyItemChanges.map(change => {151const goToFileEditorTitle = change.modifiedUri152? `${basename(change.modifiedUri.fsPath)} (${historyItemDisplayId ?? historyItemId})`153: undefined;154155return new MultiDiffEditorItem(change.originalUri, change.modifiedUri, change.modifiedUri, goToFileEditorTitle);156})157);158159return { resources };160}161}162163class ScmResolvedMultiDiffSource implements IResolvedMultiDiffSource {164private readonly _resources;165readonly resources;166167public readonly contextKeys: Record<string, ContextKeyValue>;168169constructor(170private readonly _group: ISCMResourceGroup,171private readonly _repository: ISCMRepository,172) {173this._resources = observableFromEvent<MultiDiffEditorItem[]>(174this._group.onDidChangeResources,175() => /** @description resources */ this._group.resources.map(e => new MultiDiffEditorItem(e.multiDiffEditorOriginalUri, e.multiDiffEditorModifiedUri, e.sourceUri))176);177this.resources = new ValueWithChangeEventFromObservable(this._resources);178this.contextKeys = {179scmResourceGroup: this._group.id,180scmProvider: this._repository.provider.providerId,181};182}183}184185interface UriFields {186repositoryUri: string;187groupId: string;188}189190export class ScmMultiDiffSourceResolverContribution extends Disposable {191192static readonly ID = 'workbench.contrib.scmMultiDiffSourceResolver';193194constructor(195@IInstantiationService instantiationService: IInstantiationService,196@IMultiDiffSourceResolverService multiDiffSourceResolverService: IMultiDiffSourceResolverService,197) {198super();199200this._register(multiDiffSourceResolverService.registerResolver(instantiationService.createInstance(ScmHistoryItemResolver)));201this._register(multiDiffSourceResolverService.registerResolver(instantiationService.createInstance(ScmMultiDiffSourceResolver)));202}203}204205interface OpenScmGroupActionOptions {206title: string;207repositoryUri: UriComponents;208resourceGroupId: string;209}210211export class OpenScmGroupAction extends Action2 {212public static async openMultiFileDiffEditor(editorService: IEditorService, label: string, repositoryRootUri: URI | undefined, resourceGroupId: string, options?: IMultiDiffEditorOptions) {213if (!repositoryRootUri) {214return;215}216217const multiDiffSource = ScmMultiDiffSourceResolver.getMultiDiffSourceUri(repositoryRootUri.toString(), resourceGroupId);218return await editorService.openEditor({ label, multiDiffSource, options });219}220221constructor() {222super({223id: '_workbench.openScmMultiDiffEditor',224title: localize2('openChanges', 'Open Changes'),225f1: false226});227}228229async run(accessor: ServicesAccessor, options: OpenScmGroupActionOptions): Promise<void> {230const editorService = accessor.get(IEditorService);231await OpenScmGroupAction.openMultiFileDiffEditor(editorService, options.title, URI.revive(options.repositoryUri), options.resourceGroupId);232}233}234235236