From 04f0fb46ff7ee4cf2b36b7c88b8d6f196ec62683 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 12 Jan 2023 11:19:41 +0100 Subject: [PATCH] Separate quick diff API from SCM (#170544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Separate quick diff API from SCM Part 1: split up the API. upcoming in part 2: In editor UX for multiple quick diffs. Fixes #169012 * Respond to review feedback Co-authored-by: João Moreno --- .../api/browser/extensionHost.contribution.ts | 1 + .../api/browser/mainThreadQuickDiff.ts | 54 +++++++++++++++ src/vs/workbench/api/browser/mainThreadSCM.ts | 20 ++++-- .../workbench/api/common/extHost.api.impl.ts | 6 ++ .../workbench/api/common/extHost.protocol.ts | 11 ++++ .../workbench/api/common/extHostQuickDiff.ts | 44 +++++++++++++ .../contrib/format/browser/formatModified.ts | 6 +- .../contrib/scm/browser/dirtydiffDecorator.ts | 44 +++---------- .../contrib/scm/browser/scm.contribution.ts | 3 + .../workbench/contrib/scm/common/quickDiff.ts | 28 ++++++++ .../contrib/scm/common/quickDiffService.ts | 66 +++++++++++++++++++ .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.quickDiffProvider.d.ts | 13 ++++ 13 files changed, 255 insertions(+), 42 deletions(-) create mode 100644 src/vs/workbench/api/browser/mainThreadQuickDiff.ts create mode 100644 src/vs/workbench/api/common/extHostQuickDiff.ts create mode 100644 src/vs/workbench/contrib/scm/common/quickDiff.ts create mode 100644 src/vs/workbench/contrib/scm/common/quickDiffService.ts create mode 100644 src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index 38b87dac023..db147221848 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -45,6 +45,7 @@ import './mainThreadLogService'; import './mainThreadMessageService'; import './mainThreadOutputService'; import './mainThreadProgress'; +import './mainThreadQuickDiff'; import './mainThreadQuickOpen'; import './mainThreadRemoteConnectionData'; import './mainThreadSaveParticipant'; diff --git a/src/vs/workbench/api/browser/mainThreadQuickDiff.ts b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts new file mode 100644 index 00000000000..638977affc2 --- /dev/null +++ b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { ExtHostContext, ExtHostQuickDiffShape, MainContext, MainThreadQuickDiffShape } from 'vs/workbench/api/common/extHost.protocol'; +import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; +import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; + +@extHostNamedCustomer(MainContext.MainThreadQuickDiff) +export class MainThreadQuickDiff implements MainThreadQuickDiffShape { + + private readonly proxy: ExtHostQuickDiffShape; + private providers = new Map(); + private providerDisposables = new Map(); + + constructor( + extHostContext: IExtHostContext, + @IQuickDiffService private readonly quickDiffService: IQuickDiffService + ) { + this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostQuickDiff); + } + + async $registerQuickDiffProvider(handle: number, label: string, rootUri: UriComponents | undefined): Promise { + const provider: QuickDiffProvider = { + label, + rootUri: URI.revive(rootUri), + getOriginalResource: async (uri: URI) => { + return URI.revive(await this.proxy.$provideOriginalResource(handle, uri, new CancellationTokenSource().token)); + } + }; + this.providers.set(handle, provider); + const disposable = this.quickDiffService.addQuickDiffProvider(provider); + this.providerDisposables.set(handle, disposable); + } + + async $unregisterQuickDiffProvider(handle: number): Promise { + if (this.providers.has(handle)) { + this.providers.delete(handle); + } + if (this.providerDisposables.has(handle)) { + this.providerDisposables.delete(handle); + } + } + + dispose(): void { + this.providers.clear(); + dispose(this.providerDisposables.values()); + this.providerDisposables.clear(); + } +} diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 24aec5842d2..dd32595ee14 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -15,6 +15,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; class MainThreadSCMResourceGroup implements ISCMResourceGroup { @@ -89,7 +90,7 @@ class MainThreadSCMResource implements ISCMResource { } } -class MainThreadSCMProvider implements ISCMProvider { +class MainThreadSCMProvider implements ISCMProvider, QuickDiffProvider { private static ID_HANDLE = 0; private _id = `scm${MainThreadSCMProvider.ID_HANDLE++}`; @@ -133,12 +134,15 @@ class MainThreadSCMProvider implements ISCMProvider { private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = this._onDidChange.event; + private _quickDiff: IDisposable | undefined; + constructor( private readonly proxy: ExtHostSCMShape, private readonly _handle: number, private readonly _contextValue: string, private readonly _label: string, - private readonly _rootUri: URI | undefined + private readonly _rootUri: URI | undefined, + private readonly _quickDiffService: IQuickDiffService ) { } $updateSourceControl(features: SCMProviderFeatures): void { @@ -152,6 +156,13 @@ class MainThreadSCMProvider implements ISCMProvider { if (typeof features.statusBarCommands !== 'undefined') { this._onDidChangeStatusBarCommands.fire(this.statusBarCommands!); } + + if (features.hasQuickDiffProvider && !this._quickDiff) { + this._quickDiff = this._quickDiffService.addQuickDiffProvider(this); + } else if (features.hasQuickDiffProvider === false && this._quickDiff) { + this._quickDiff.dispose(); + this._quickDiff = undefined; + } } $registerGroups(_groups: [number /*handle*/, string /*id*/, string /*label*/, SCMGroupFeatures][]): void { @@ -284,7 +295,8 @@ export class MainThreadSCM implements MainThreadSCMShape { constructor( extHostContext: IExtHostContext, @ISCMService private readonly scmService: ISCMService, - @ISCMViewService private readonly scmViewService: ISCMViewService + @ISCMViewService private readonly scmViewService: ISCMViewService, + @IQuickDiffService private readonly quickDiffService: IQuickDiffService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSCM); } @@ -300,7 +312,7 @@ export class MainThreadSCM implements MainThreadSCMShape { } $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined): void { - const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri ? URI.revive(rootUri) : undefined); + const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri ? URI.revive(rootUri) : undefined, this.quickDiffService); const repository = this.scmService.registerSCMProvider(provider); this._repositories.set(handle, repository); diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 473b42e47bf..624768b40f4 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -96,6 +96,7 @@ import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debu import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions'; import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler'; +import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -175,6 +176,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostLogService, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, createExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); + const extHostQuickDiff = rpcProtocol.set(ExtHostContext.ExtHostQuickDiff, new ExtHostQuickDiff(rpcProtocol)); const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, createExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); const extHostLabelService = rpcProtocol.set(ExtHostContext.ExtHosLabelService, new ExtHostLabelService(rpcProtocol)); @@ -802,6 +804,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'profileContentHandlers'); return extHostProfileContentHandlers.registrProfileContentHandler(extension, id, handler); }, + async registerQuickDiffProvider(quickDiffProvider: vscode.QuickDiffProvider, label: string, rootUri?: vscode.Uri): Promise { + checkProposedApiEnabled(extension, 'quickDiffProvider'); + return extHostQuickDiff.registerQuickDiffProvider(quickDiffProvider, label, rootUri); + }, get tabGroups(): vscode.TabGroups { return extHostEditorTabs.tabGroups; } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 579752c1e9b..84d43e42da9 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1237,6 +1237,11 @@ export interface MainThreadSCMShape extends IDisposable { $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; } +export interface MainThreadQuickDiffShape extends IDisposable { + $registerQuickDiffProvider(handle: number, label: string, rootUri: UriComponents | undefined): Promise; + $unregisterQuickDiffProvider(handle: number): Promise; +} + export type DebugSessionUUID = string; export interface IDebugConfiguration { @@ -1869,6 +1874,10 @@ export interface ExtHostSCMShape { $setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise; } +export interface ExtHostQuickDiffShape { + $provideOriginalResource(sourceControlHandle: number, uri: UriComponents, token: CancellationToken): Promise; +} + export interface ExtHostTaskShape { $provideTasks(handle: number, validTypes: { [key: string]: boolean }): Promise; $resolveTask(handle: number, taskDTO: tasks.ITaskDTO): Promise; @@ -2333,6 +2342,7 @@ export const MainContext = { MainThreadMessageService: createProxyIdentifier('MainThreadMessageService'), MainThreadOutputService: createProxyIdentifier('MainThreadOutputService'), MainThreadProgress: createProxyIdentifier('MainThreadProgress'), + MainThreadQuickDiff: createProxyIdentifier('MainThreadQuickDiff'), MainThreadQuickOpen: createProxyIdentifier('MainThreadQuickOpen'), MainThreadStatusBar: createProxyIdentifier('MainThreadStatusBar'), MainThreadSecretState: createProxyIdentifier('MainThreadSecretState'), @@ -2385,6 +2395,7 @@ export const ExtHostContext = { ExtHostLanguages: createProxyIdentifier('ExtHostLanguages'), ExtHostLanguageFeatures: createProxyIdentifier('ExtHostLanguageFeatures'), ExtHostQuickOpen: createProxyIdentifier('ExtHostQuickOpen'), + ExtHostQuickDiff: createProxyIdentifier('ExtHostQuickDiff'), ExtHostExtensionService: createProxyIdentifier('ExtHostExtensionService'), ExtHostLogLevelServiceShape: createProxyIdentifier('ExtHostLogLevelServiceShape'), ExtHostTerminalService: createProxyIdentifier('ExtHostTerminalService'), diff --git a/src/vs/workbench/api/common/extHostQuickDiff.ts b/src/vs/workbench/api/common/extHostQuickDiff.ts new file mode 100644 index 00000000000..2c6b5459f53 --- /dev/null +++ b/src/vs/workbench/api/common/extHostQuickDiff.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type * as vscode from 'vscode'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { ExtHostQuickDiffShape, IMainContext, MainContext, MainThreadQuickDiffShape } from 'vs/workbench/api/common/extHost.protocol'; +import { asPromise } from 'vs/base/common/async'; + +export class ExtHostQuickDiff implements ExtHostQuickDiffShape { + private static handlePool: number = 0; + + private proxy: MainThreadQuickDiffShape; + private providers: Map = new Map(); + + constructor( + mainContext: IMainContext + ) { + this.proxy = mainContext.getProxy(MainContext.MainThreadQuickDiff); + } + + $provideOriginalResource(handle: number, uriComponents: UriComponents, token: CancellationToken): Promise { + const uri = URI.revive(uriComponents); + const provider = this.providers.get(handle); + + if (!provider) { + return Promise.resolve(null); + } + + return asPromise(() => provider.provideOriginalResource!(uri, token)) + .then(r => r || null); + } + + async registerQuickDiffProvider(quickDiffProvider: vscode.QuickDiffProvider, label: string, rootUri?: vscode.Uri): Promise { + const handle = ExtHostQuickDiff.handlePool++; + this.providers.set(handle, quickDiffProvider); + await this.proxy.$registerQuickDiffProvider(handle, label, rootUri); + return { + dispose: () => this.providers.delete(handle) + }; + } +} diff --git a/src/vs/workbench/contrib/format/browser/formatModified.ts b/src/vs/workbench/contrib/format/browser/formatModified.ts index 9ba04eb2b84..86977ca249e 100644 --- a/src/vs/workbench/contrib/format/browser/formatModified.ts +++ b/src/vs/workbench/contrib/format/browser/formatModified.ts @@ -18,7 +18,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Progress } from 'vs/platform/progress/common/progress'; import { getOriginalResource } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; -import { ISCMService } from 'vs/workbench/contrib/scm/common/scm'; +import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; registerEditorAction(class FormatModifiedAction extends EditorAction { @@ -50,11 +50,11 @@ registerEditorAction(class FormatModifiedAction extends EditorAction { export async function getModifiedRanges(accessor: ServicesAccessor, modified: ITextModel): Promise { - const scmService = accessor.get(ISCMService); + const quickDiffService = accessor.get(IQuickDiffService); const workerService = accessor.get(IEditorWorkerService); const modelService = accessor.get(ITextModelService); - const original = await getOriginalResource(scmService, modified.uri); + const original = await getOriginalResource(quickDiffService, modified.uri); if (!original) { return null; // let undefined signify no changes, null represents no source control (there's probably a better way, but I can't think of one rn) } diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 03c56df7e27..a3c135a0e2c 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import 'vs/css!./media/dirtydiffDecorator'; -import { ThrottledDelayer, first } from 'vs/base/common/async'; +import { ThrottledDelayer } from 'vs/base/common/async'; import { IDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as ext from 'vs/workbench/common/contributions'; @@ -15,7 +15,7 @@ import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/se import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; -import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IColorTheme, themeColorFromId, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { editorErrorForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; @@ -34,7 +34,7 @@ import { IDiffEditorOptions, EditorOption } from 'vs/editor/common/config/editor import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; import { IActionBarOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { basename, isEqualOrParent } from 'vs/base/common/resources'; +import { basename } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; @@ -51,13 +51,13 @@ import { TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys' import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IChange } from 'vs/editor/common/diff/smartLinesDiffComputer'; import { Color } from 'vs/base/common/color'; -import { Iterable } from 'vs/base/common/iterator'; import { ResourceMap } from 'vs/base/common/map'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; class DiffActionRunner extends ActionRunner { @@ -1095,37 +1095,10 @@ function compareChanges(a: IChange, b: IChange): number { return a.originalEndLineNumber - b.originalEndLineNumber; } -export function createProviderComparer(uri: URI): (a: ISCMProvider, b: ISCMProvider) => number { - return (a, b) => { - const aIsParent = isEqualOrParent(uri, a.rootUri!); - const bIsParent = isEqualOrParent(uri, b.rootUri!); - if (aIsParent && bIsParent) { - return a.rootUri!.fsPath.length - b.rootUri!.fsPath.length; - } else if (aIsParent) { - return -1; - } else if (bIsParent) { - return 1; - } else { - return 0; - } - }; -} - -export async function getOriginalResource(scmService: ISCMService, uri: URI): Promise { - const providers = Iterable.map(scmService.repositories, r => r.provider); - const rootedProviders = Array.from(Iterable.filter(providers, p => !!p.rootUri)); - - rootedProviders.sort(createProviderComparer(uri)); - - const result = await first(rootedProviders.map(p => () => p.getOriginalResource(uri))); - - if (result) { - return result; - } - - const nonRootedProviders = Iterable.filter(providers, p => !p.rootUri); - return first(Array.from(nonRootedProviders, p => () => p.getOriginalResource(uri))); +export async function getOriginalResource(quickDiffService: IQuickDiffService, uri: URI): Promise { + const quickDiffs = await quickDiffService.getQuickDiffs(uri); + return quickDiffs.length > 0 ? quickDiffs[0].originalResource : null; } export class DirtyDiffModel extends Disposable { @@ -1151,6 +1124,7 @@ export class DirtyDiffModel extends Disposable { constructor( textFileModel: IResolvedTextFileEditorModel, @ISCMService private readonly scmService: ISCMService, + @IQuickDiffService private readonly quickDiffService: IQuickDiffService, @IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITextModelService private readonly textModelResolverService: ITextModelService, @@ -1306,7 +1280,7 @@ export class DirtyDiffModel extends Disposable { } const uri = this._model.resource; - return getOriginalResource(this.scmService, uri); + return getOriginalResource(this.quickDiffService, uri); } findNextClosestChange(lineNumber: number, inclusive = true): number { diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 6f25635c96e..fed3eb0f08d 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -30,6 +30,8 @@ import { SCMRepositoriesViewPane } from 'vs/workbench/contrib/scm/browser/scmRep import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest'; import { MANAGE_TRUST_COMMAND_ID, WorkspaceTrustContext } from 'vs/workbench/contrib/workspace/common/workspace'; +import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; +import { QuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiffService'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -385,3 +387,4 @@ MenuRegistry.appendMenuItem(MenuId.SCMSourceControl, { registerSingleton(ISCMService, SCMService, InstantiationType.Delayed); registerSingleton(ISCMViewService, SCMViewService, InstantiationType.Delayed); +registerSingleton(IQuickDiffService, QuickDiffService, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/scm/common/quickDiff.ts b/src/vs/workbench/contrib/scm/common/quickDiff.ts new file mode 100644 index 00000000000..2229d5c2c20 --- /dev/null +++ b/src/vs/workbench/contrib/scm/common/quickDiff.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export const IQuickDiffService = createDecorator('quickDiff'); + +export interface QuickDiffProvider { + label: string; + rootUri: URI | undefined; + getOriginalResource(uri: URI): Promise; +} + +export interface QuickDiff { + label: string; + originalResource: URI; +} + +export interface IQuickDiffService { + readonly _serviceBrand: undefined; + + addQuickDiffProvider(quickDiff: QuickDiffProvider): IDisposable; + getQuickDiffs(uri: URI): Promise; +} diff --git a/src/vs/workbench/contrib/scm/common/quickDiffService.ts b/src/vs/workbench/contrib/scm/common/quickDiffService.ts new file mode 100644 index 00000000000..208c2ef0afd --- /dev/null +++ b/src/vs/workbench/contrib/scm/common/quickDiffService.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IQuickDiffService, QuickDiff, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; +import { isEqualOrParent } from 'vs/base/common/resources'; + +function createProviderComparer(uri: URI): (a: QuickDiffProvider, b: QuickDiffProvider) => number { + return (a, b) => { + if (a.rootUri && !b.rootUri) { + return -1; + } else if (!a.rootUri && b.rootUri) { + return 1; + } else if (!a.rootUri && !b.rootUri) { + return 0; + } + + const aIsParent = isEqualOrParent(uri, a.rootUri!); + const bIsParent = isEqualOrParent(uri, b.rootUri!); + + if (aIsParent && bIsParent) { + return a.rootUri!.fsPath.length - b.rootUri!.fsPath.length; + } else if (aIsParent) { + return -1; + } else if (bIsParent) { + return 1; + } else { + return 0; + } + }; +} + +export class QuickDiffService implements IQuickDiffService { + declare readonly _serviceBrand: undefined; + + private quickDiffProviders: Set = new Set(); + + addQuickDiffProvider(quickDiff: QuickDiffProvider): IDisposable { + this.quickDiffProviders.add(quickDiff); + return { + dispose: () => { + this.quickDiffProviders.delete(quickDiff); + } + }; + } + + private isQuickDiff(diff: { originalResource: URI | null; label: string }): diff is QuickDiff { + return !!diff.originalResource; + } + + async getQuickDiffs(uri: URI): Promise { + const sorted = Array.from(this.quickDiffProviders).sort(createProviderComparer(uri)); + + const diffs = await Promise.all(Array.from(sorted.values()).map(async (provider) => { + const diff = { + originalResource: await provider.getOriginalResource(uri), + label: provider.label + }; + return diff; + })); + return diffs.filter(this.isQuickDiff); + } +} diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index ac99ce280c4..9d3c19f01cc 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -50,6 +50,7 @@ export const allApiProposals = Object.freeze({ notebookMime: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.notebookMime.d.ts', portsAttributes: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.portsAttributes.d.ts', profileContentHandlers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts', + quickDiffProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts', quickPickSortByLabel: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts', resolvers: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.resolvers.d.ts', scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts', diff --git a/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts b/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts new file mode 100644 index 00000000000..589cf624cbb --- /dev/null +++ b/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/169012 + + export namespace window { + export function registerQuickDiffProvider(quickDiffProvider: QuickDiffProvider, label: string, rootUri?: Uri): Thenable; + } +}