SCM - add the capability to attach a history item as chat context (#249073)

* Saving my work

* Got the variable widget working

* Refactored the variable widget

* Clean up the attachment widget

* Content provider stub

* Add proposed API for chat context

* Add method to get the complete details of a commit
This commit is contained in:
Ladislau Szomoru
2025-05-16 01:09:12 -07:00
committed by GitHub
parent 7052ebfea0
commit daab3ffa1e
17 changed files with 274 additions and 34 deletions
+1
View File
@@ -395,6 +395,7 @@ export interface GitExtension {
export const enum GitErrorCodes {
BadConfigFile = 'BadConfigFile',
BadRevision = 'BadRevision',
AuthenticationFailed = 'AuthenticationFailed',
NoUserNameConfigured = 'NoUserNameConfigured',
NoUserEmailConfigured = 'NoUserEmailConfigured',
+13
View File
@@ -2927,6 +2927,19 @@ export class Repository {
return commits[0];
}
async showCommit(ref: string): Promise<string> {
try {
const result = await this.exec(['show', ref]);
return result.stdout.trim();
} catch (err) {
if (/^fatal: bad revision '.+'/.test(err.stderr || '')) {
err.gitErrorCode = GitErrorCodes.BadRevision;
}
throw err;
}
}
async revList(ref1: string, ref2: string): Promise<string[]> {
const result = await this.exec(['rev-list', `${ref1}..${ref2}`]);
if (result.stderr) {
+11
View File
@@ -344,6 +344,17 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec
return historyItemChanges;
}
async resolveHistoryItemChatContext(historyItemId: string): Promise<string | undefined> {
try {
const commitDetails = await this.repository.showCommit(historyItemId);
return commitDetails;
} catch (err) {
this.logger.error(`[GitHistoryProvider][resolveHistoryItemChatContext] Failed to resolve history item '${historyItemId}': ${err}`);
}
return undefined;
}
async resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[]): Promise<string | undefined> {
try {
if (historyItemRefs.length === 0) {
+4
View File
@@ -1705,6 +1705,10 @@ export class Repository implements Disposable {
return await this.repository.getCommit(ref);
}
async showCommit(ref: string): Promise<string> {
return await this.run(Operation.Show, () => this.repository.showCommit(ref));
}
async getEmptyTree(): Promise<string> {
if (!this._EMPTY_TREE) {
const result = await this.repository.exec(['hash-object', '-t', 'tree', '/dev/null']);
@@ -195,6 +195,10 @@ class MainThreadSCMHistoryProvider implements ISCMHistoryProvider {
constructor(private readonly proxy: ExtHostSCMShape, private readonly handle: number) { }
async resolveHistoryItemChatContext(historyItemId: string): Promise<string | undefined> {
return this.proxy.$resolveHistoryItemChatContext(this.handle, historyItemId, CancellationToken.None);
}
async resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[]): Promise<string | undefined> {
return this.proxy.$resolveHistoryItemRefsCommonAncestor(this.handle, historyItemRefs, CancellationToken.None);
}
@@ -2563,6 +2563,7 @@ export interface ExtHostSCMShape {
$provideHistoryItemRefs(sourceControlHandle: number, historyItemRefs: string[] | undefined, token: CancellationToken): Promise<SCMHistoryItemRefDto[] | undefined>;
$provideHistoryItems(sourceControlHandle: number, options: any, token: CancellationToken): Promise<SCMHistoryItemDto[] | undefined>;
$provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): Promise<SCMHistoryItemChangeDto[] | undefined>;
$resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise<string | undefined>;
$resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise<string | undefined>;
}
+13
View File
@@ -1046,6 +1046,19 @@ export class ExtHostSCM implements ExtHostSCMShape {
return Promise.resolve(undefined);
}
async $resolveHistoryItemChatContext(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise<string | undefined> {
try {
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
const chatContext = await historyProvider?.resolveHistoryItemChatContext(historyItemId, token);
return chatContext ?? undefined;
}
catch (err) {
this.logService.error('ExtHostSCM#$resolveHistoryItemChatContext', err);
return undefined;
}
}
async $resolveHistoryItemRefsCommonAncestor(sourceControlHandle: number, historyItemRefs: string[], token: CancellationToken): Promise<string | undefined> {
try {
const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider;
@@ -25,7 +25,7 @@ import { IOpenerService, OpenInternalOptions } from '../../../../platform/opener
import { IThemeService, FolderThemeIcon } from '../../../../platform/theme/common/themeService.js';
import { IResourceLabel, ResourceLabels, IFileLabelOptions } from '../../../browser/labels.js';
import { revealInSideBarCommand } from '../../files/browser/fileActions.contribution.js';
import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, OmittedState } from '../common/chatModel.js';
import { IChatRequestPasteVariableEntry, IChatRequestVariableEntry, IElementVariableEntry, INotebookOutputVariableEntry, ISCMHistoryItemVariableEntry, OmittedState } from '../common/chatModel.js';
import { ILanguageModelChatMetadataAndIdentifier, ILanguageModelsService } from '../common/languageModels.js';
import { chatAttachmentResourceContextKey } from './chatContentParts/chatAttachmentsContentPart.js';
import { KeyCode } from '../../../../base/common/keyCodes.js';
@@ -51,7 +51,6 @@ import { IContextMenuService } from '../../../../platform/contextview/browser/co
import { ResourceContextKey } from '../../../common/contextkeys.js';
import { Location, SymbolKind } from '../../../../editor/common/languages.js';
abstract class AbstractChatAttachmentWidget extends Disposable {
public readonly element: HTMLElement;
public readonly label: IResourceLabel;
@@ -597,6 +596,47 @@ export class ElementChatAttachmentWidget extends AbstractChatAttachmentWidget {
}
}
export class SCMHistoryItemAttachmentWidget extends AbstractChatAttachmentWidget {
constructor(
attachment: ISCMHistoryItemVariableEntry,
currentLanguageModel: ILanguageModelChatMetadataAndIdentifier | undefined,
options: { shouldFocusClearButton: boolean; supportsDeletion: boolean },
container: HTMLElement,
contextResourceLabels: ResourceLabels,
hoverDelegate: IHoverDelegate,
@ICommandService commandService: ICommandService,
@IOpenerService openerService: IOpenerService
) {
super(attachment, options, container, contextResourceLabels, hoverDelegate, currentLanguageModel, commandService, openerService);
this.label.setLabel(attachment.name, undefined);
this.element.style.cursor = 'pointer';
this.element.ariaLabel = localize('chat.attachment', "Attached context, {0}", attachment.name);
this._store.add(dom.addDisposableListener(this.element, dom.EventType.CLICK, (e: MouseEvent) => {
dom.EventHelper.stop(e, true);
this._openAttachment(attachment);
}));
this._store.add(dom.addDisposableListener(this.element, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) {
dom.EventHelper.stop(e, true);
this._openAttachment(attachment);
}
}));
this.attachClearButton();
}
private async _openAttachment(attachment: ISCMHistoryItemVariableEntry): Promise<void> {
await this.commandService.executeCommand('_workbench.openMultiDiffEditor', {
title: attachment.title, multiDiffSourceUri: attachment.value
});
}
}
export function hookUpResourceAttachmentDragAndContextMenu(accessor: ServicesAccessor, widget: HTMLElement, resource: URI): IDisposable {
const contextKeyService = accessor.get(IContextKeyService);
const instantiationService = accessor.get(IInstantiationService);
@@ -14,9 +14,9 @@ import { localize } from '../../../../../nls.js';
import { RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { ResourceLabels } from '../../../../browser/labels.js';
import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry } from '../../common/chatModel.js';
import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry } from '../../common/chatModel.js';
import { ChatResponseReferencePartStatusKind, IChatContentReference } from '../../common/chatService.js';
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget } from '../chatAttachmentWidgets.js';
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, SCMHistoryItemAttachmentWidget } from '../chatAttachmentWidgets.js';
export const chatAttachmentResourceContextKey = new RawContextKey<string>('chatAttachmentResource', undefined, { type: 'URI', description: localize('resource', "The full value of the chat attachment resource, including scheme and path") });
@@ -62,6 +62,8 @@ export class ChatAttachmentsContentPart extends Disposable {
widget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);
} else if (resource && isNotebookOutputVariableEntry(attachment)) {
widget = this.instantiationService.createInstance(NotebookCellOutputChatAttachmentWidget, resource, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);
} else if (isSCMHistoryItemVariableEntry(attachment)) {
widget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);
} else {
widget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, undefined, { shouldFocusClearButton: false, supportsDeletion: false }, container, this._contextResourceLabels, hoverDelegate);
}
@@ -71,7 +71,7 @@ import { getSimpleCodeEditorWidgetOptions, getSimpleEditorOptions, setupSimpleEd
import { IChatAgentService } from '../common/chatAgents.js';
import { ChatContextKeys } from '../common/chatContextKeys.js';
import { IChatEditingSession } from '../common/chatEditingService.js';
import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry } from '../common/chatModel.js';
import { IChatRequestVariableEntry, isElementVariableEntry, isImageVariableEntry, isNotebookOutputVariableEntry, isPasteVariableEntry, isSCMHistoryItemVariableEntry } from '../common/chatModel.js';
import { IChatFollowup } from '../common/chatService.js';
import { IChatVariablesService } from '../common/chatVariables.js';
import { IChatResponseViewModel } from '../common/chatViewModel.js';
@@ -84,7 +84,7 @@ import { PromptInstructionsAttachmentsCollectionWidget } from './attachments/pro
import { IChatWidget } from './chat.js';
import { ChatAttachmentModel } from './chatAttachmentModel.js';
import { toChatVariable } from './chatAttachmentModel/chatPromptAttachmentsCollection.js';
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget } from './chatAttachmentWidgets.js';
import { DefaultChatAttachmentWidget, ElementChatAttachmentWidget, FileAttachmentWidget, ImageAttachmentWidget, NotebookCellOutputChatAttachmentWidget, PasteAttachmentWidget, SCMHistoryItemAttachmentWidget } from './chatAttachmentWidgets.js';
import { IDisposableReference } from './chatContentParts/chatCollections.js';
import { CollapsibleListPool, IChatCollapsibleListItem } from './chatContentParts/chatReferencesContentPart.js';
import { ChatDragAndDrop } from './chatDragAndDrop.js';
@@ -1240,6 +1240,8 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
attachmentWidget = this.instantiationService.createInstance(ElementChatAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate);
} else if (isPasteVariableEntry(attachment)) {
attachmentWidget = this.instantiationService.createInstance(PasteAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate);
} else if (isSCMHistoryItemVariableEntry(attachment)) {
attachmentWidget = this.instantiationService.createInstance(SCMHistoryItemAttachmentWidget, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate);
} else {
attachmentWidget = this.instantiationService.createInstance(DefaultChatAttachmentWidget, resource, range, attachment, this._currentLanguageModel, options, container, this._contextResourceLabels, hoverDelegate);
}
@@ -203,10 +203,16 @@ export interface IPromptFileVariableEntry extends IBaseChatRequestVariableEntry
readonly kind: 'promptFile';
}
export interface ISCMHistoryItemVariableEntry extends IBaseChatRequestVariableEntry {
readonly kind: 'scmHistoryItem';
readonly title: string;
readonly value: URI;
}
export type IChatRequestVariableEntry = IGenericChatRequestVariableEntry | IChatRequestImplicitVariableEntry | IChatRequestPasteVariableEntry
| ISymbolVariableEntry | ICommandResultVariableEntry | IDiagnosticVariableEntry | IImageVariableEntry | IChatRequestToolEntry
| IChatRequestDirectoryEntry | IChatRequestFileEntry | INotebookOutputVariableEntry | IElementVariableEntry
| IPromptFileVariableEntry;
| IPromptFileVariableEntry | ISCMHistoryItemVariableEntry;
export function isImplicitVariableEntry(obj: IChatRequestVariableEntry): obj is IChatRequestImplicitVariableEntry {
return obj.kind === 'implicit';
@@ -244,6 +250,10 @@ export function isChatRequestVariableEntry(obj: unknown): obj is IChatRequestVar
typeof entry.name === 'string';
}
export function isSCMHistoryItemVariableEntry(obj: IChatRequestVariableEntry): obj is ISCMHistoryItemVariableEntry {
return obj.kind === 'scmHistoryItem';
}
export interface IChatRequestVariableData {
variables: IChatRequestVariableEntry[];
}
@@ -15,7 +15,7 @@ import { IInstantiationService, ServicesAccessor } from '../../../../platform/in
import { IActivityService, ProgressBadge } from '../../../services/activity/common/activity.js';
import { IEditorService } from '../../../services/editor/common/editorService.js';
import { ISCMHistoryItem } from '../../scm/common/history.js';
import { ISCMRepository, ISCMResourceGroup, ISCMService } from '../../scm/common/scm.js';
import { ISCMProvider, ISCMRepository, ISCMResourceGroup, ISCMService } from '../../scm/common/scm.js';
import { IMultiDiffSourceResolver, IMultiDiffSourceResolverService, IResolvedMultiDiffSource, MultiDiffEditorItem } from './multiDiffSourceResolverService.js';
export class ScmMultiDiffSourceResolver implements IMultiDiffSourceResolver {
@@ -92,42 +92,24 @@ interface ScmHistoryItemUriFields {
}
export class ScmHistoryItemResolver implements IMultiDiffSourceResolver {
private static readonly _scheme = 'scm-history-item';
static readonly scheme = 'scm-history-item';
public static getMultiDiffSourceUri(repositoryId: string, historyItem: ISCMHistoryItem): URI {
public static getMultiDiffSourceUri(provider: ISCMProvider, historyItem: ISCMHistoryItem): URI {
const historyItemParentId = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : undefined;
return URI.from({
scheme: ScmHistoryItemResolver._scheme,
scheme: ScmHistoryItemResolver.scheme,
path: provider.rootUri?.fsPath,
query: JSON.stringify({
repositoryId,
repositoryId: provider.id,
historyItemId: historyItem.id,
historyItemParentId
} satisfies ScmHistoryItemUriFields)
}, true);
}
constructor(@ISCMService private readonly _scmService: ISCMService) { }
canHandleUri(uri: URI): boolean {
return this._parseUri(uri) !== undefined;
}
async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {
const { repositoryId, historyItemId, historyItemParentId } = this._parseUri(uri)!;
const repository = this._scmService.getRepository(repositoryId);
const historyProvider = repository?.provider.historyProvider.get();
const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? [];
const resources = ValueWithChangeEvent.const<readonly MultiDiffEditorItem[]>(
historyItemChanges.map(change => new MultiDiffEditorItem(change.originalUri, change.modifiedUri, change.uri)));
return { resources };
}
private _parseUri(uri: URI): ScmHistoryItemUriFields | undefined {
if (uri.scheme !== ScmHistoryItemResolver._scheme) {
public static parseUri(uri: URI): ScmHistoryItemUriFields | undefined {
if (uri.scheme !== ScmHistoryItemResolver.scheme) {
return undefined;
}
@@ -150,6 +132,25 @@ export class ScmHistoryItemResolver implements IMultiDiffSourceResolver {
return { repositoryId, historyItemId, historyItemParentId };
}
constructor(@ISCMService private readonly _scmService: ISCMService) { }
canHandleUri(uri: URI): boolean {
return ScmHistoryItemResolver.parseUri(uri) !== undefined;
}
async resolveDiffSource(uri: URI): Promise<IResolvedMultiDiffSource> {
const { repositoryId, historyItemId, historyItemParentId } = ScmHistoryItemResolver.parseUri(uri)!;
const repository = this._scmService.getRepository(repositoryId);
const historyProvider = repository?.provider.historyProvider.get();
const historyItemChanges = await historyProvider?.provideHistoryItemChanges(historyItemId, historyItemParentId) ?? [];
const resources = ValueWithChangeEvent.const<readonly MultiDiffEditorItem[]>(
historyItemChanges.map(change => new MultiDiffEditorItem(change.originalUri, change.modifiedUri, change.uri)));
return { resources };
}
}
class ScmResolvedMultiDiffSource implements IResolvedMultiDiffSource {
@@ -45,6 +45,7 @@ import { RemoteNameContext } from '../../../common/contextkeys.js';
import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
import { SCMAccessibilityHelp } from './scmAccessibilityHelp.js';
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
import { SCMHistoryItemContextContribution } from './scmHistoryChatContext.js';
ModesRegistry.registerLanguage({
id: 'scminput',
@@ -164,6 +165,12 @@ registerWorkbenchContribution2(
WorkbenchPhase.AfterRestored
);
registerWorkbenchContribution2(
SCMHistoryItemContextContribution.ID,
SCMHistoryItemContextContribution,
WorkbenchPhase.AfterRestored
);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
id: 'scm',
order: 5,
@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { coalesce } from '../../../../base/common/arrays.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { URI } from '../../../../base/common/uri.js';
import { ITextModel } from '../../../../editor/common/model.js';
import { IModelService } from '../../../../editor/common/services/model.js';
import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js';
import { localize } from '../../../../nls.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IWorkbenchContribution } from '../../../common/contributions.js';
import { IChatWidget } from '../../chat/browser/chat.js';
import { IChatContextPickerItem, IChatContextPickerPickItem, IChatContextPickService } from '../../chat/browser/chatContextPickService.js';
import { ISCMHistoryItemVariableEntry } from '../../chat/common/chatModel.js';
import { ScmHistoryItemResolver } from '../../multiDiffEditor/browser/scmMultiDiffSourceResolver.js';
import { ISCMService, ISCMViewService } from '../common/scm.js';
import { getHistoryItemEditorTitle } from './util.js';
export class SCMHistoryItemContextContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.chat.scmHistoryItemContextContribution';
constructor(
@IChatContextPickService contextPickService: IChatContextPickService,
@IInstantiationService instantiationService: IInstantiationService,
@ITextModelService textModelResolverService: ITextModelService
) {
super();
this._store.add(contextPickService.registerChatContextItem(
instantiationService.createInstance(SCMHistoryItemContext)));
this._store.add(textModelResolverService.registerTextModelContentProvider(
ScmHistoryItemResolver.scheme,
instantiationService.createInstance(SCMHistoryItemContextContentProvider)));
}
}
class SCMHistoryItemContext implements IChatContextPickerItem {
readonly type = 'pickerPick';
readonly label = localize('chatContext.scmHistoryItems', 'Source Control History Items...');
readonly icon = Codicon.gitCommit;
constructor(
@ISCMViewService private readonly _scmViewService: ISCMViewService
) { }
isEnabled(_widget: IChatWidget): Promise<boolean> | boolean {
const activeRepository = this._scmViewService.activeRepository.get();
return activeRepository?.provider.historyProvider.get() !== undefined;
}
asPicker(_widget: IChatWidget) {
return {
placeholder: localize('chatContext.scmHistoryItems.placeholder', 'Select a source control history item'),
picks: async (_query: string) => {
const activeRepository = this._scmViewService.activeRepository.get();
const historyProvider = activeRepository?.provider.historyProvider.get();
if (!activeRepository || !historyProvider) {
return [];
}
const historyItemRefs = coalesce([
historyProvider.historyItemRef.get(),
historyProvider.historyItemRemoteRef.get(),
historyProvider.historyItemBaseRef.get(),
]).map(ref => ref.id);
const historyItems = await historyProvider.provideHistoryItems({ historyItemRefs, limit: 100 }) ?? [];
return historyItems.map(historyItem => ({
iconClass: ThemeIcon.asClassName(Codicon.gitCommit),
label: historyItem.subject,
description: historyItem.displayId ?? historyItem.id,
asAttachment: () => {
const historyItemTitle = getHistoryItemEditorTitle(historyItem);
const multiDiffSourceUri = ScmHistoryItemResolver.getMultiDiffSourceUri(activeRepository.provider, historyItem);
const attachmentName = `$(${Codicon.repo.id})\u00A0${activeRepository.provider.name}\u00A0$(${Codicon.gitCommit.id})\u00A0${historyItem.displayId ?? historyItem.id}`;
return {
id: historyItem.id,
name: attachmentName,
value: multiDiffSourceUri,
title: historyItemTitle,
kind: 'scmHistoryItem'
} satisfies ISCMHistoryItemVariableEntry;
}
}) satisfies IChatContextPickerPickItem);
}
};
}
}
class SCMHistoryItemContextContentProvider implements ITextModelContentProvider {
constructor(
@IModelService private readonly _modelService: IModelService,
@ISCMService private readonly _scmService: ISCMService
) { }
async provideTextContent(resource: URI): Promise<ITextModel | null> {
const uriFields = ScmHistoryItemResolver.parseUri(resource);
if (!uriFields) {
return null;
}
const textModel = this._modelService.getModel(resource);
if (textModel) {
return textModel;
}
const { repositoryId, historyItemId } = uriFields;
const repository = this._scmService.getRepository(repositoryId);
const historyProvider = repository?.provider.historyProvider.get();
if (!repository || !historyProvider) {
return null;
}
const historyItemContext = await historyProvider.resolveHistoryItemChatContext(historyItemId);
if (!historyItemContext) {
return null;
}
return this._modelService.createModel(historyItemContext, null, resource, false);
}
}
@@ -286,7 +286,7 @@ registerAction2(class extends Action2 {
getHistoryItemEditorTitle(historyItem) :
localize('historyItemChangesEditorTitle', "All Changes ({0} ↔ {1})", historyItemLast.displayId ?? historyItemLast.id, historyItem.displayId ?? historyItem.id);
const multiDiffSourceUri = ScmHistoryItemResolver.getMultiDiffSourceUri(provider.id, historyItem);
const multiDiffSourceUri = ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem);
commandService.executeCommand('_workbench.openMultiDiffEditor', { title, multiDiffSourceUri });
}
});
@@ -24,6 +24,7 @@ export interface ISCMHistoryProvider {
provideHistoryItemRefs(historyItemsRefs?: string[]): Promise<ISCMHistoryItemRef[] | undefined>;
provideHistoryItems(options: ISCMHistoryOptions): Promise<ISCMHistoryItem[] | undefined>;
provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined): Promise<ISCMHistoryItemChange[] | undefined>;
resolveHistoryItemChatContext(historyItemId: string): Promise<string | undefined>;
resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[]): Promise<string | undefined>;
}
@@ -30,6 +30,7 @@ declare module 'vscode' {
provideHistoryItems(options: SourceControlHistoryOptions, token: CancellationToken): ProviderResult<SourceControlHistoryItem[]>;
provideHistoryItemChanges(historyItemId: string, historyItemParentId: string | undefined, token: CancellationToken): ProviderResult<SourceControlHistoryItemChange[]>;
resolveHistoryItemChatContext(historyItemId: string, token: CancellationToken): ProviderResult<string>;
resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[], token: CancellationToken): ProviderResult<string>;
}