diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts index 72074bcfcc6e5..d01bb1368ae89 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingCodeEditorIntegration.ts @@ -851,6 +851,13 @@ class DiffHunkWidget implements IOverlayWidget, IModifiedFileEntryChangeHunk { } return this._diffInfo.keep(this._change); } + + getModifiedLineRange(): { startLineNumber: number; endLineNumberExclusive: number } | undefined { + return { + startLineNumber: this._change.modified.startLineNumber, + endLineNumberExclusive: this._change.modified.endLineNumberExclusive + }; + } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts index addad07920773..b5a801d031944 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingEditorActions.ts @@ -5,6 +5,7 @@ import { Codicon } from '../../../../../base/common/codicons.js'; import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js'; import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js'; +import { Range } from '../../../../../editor/common/core/range.js'; import { EditorContextKeys } from '../../../../../editor/common/editorContextKeys.js'; import { localize, localize2 } from '../../../../../nls.js'; import { Action2, IAction2Options, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js'; @@ -22,6 +23,7 @@ import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_FOCUSED } from '../../../no import { ChatContextKeys } from '../../common/actions/chatContextKeys.js'; import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, IChatEditingService, IChatEditingSession, IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState, parseChatMultiDiffUri } from '../../common/editing/chatEditingService.js'; import { CHAT_CATEGORY } from '../actions/chatActions.js'; +import { IChatWidgetService } from '../chat.js'; import { ctxCursorInChangeRange, ctxHasEditorModification, ctxIsCurrentlyBeingModified, ctxIsGlobalEditingSession, ctxReviewModeEnabled } from './chatEditingEditorContextKeys.js'; @@ -319,6 +321,40 @@ class ToggleDiffAction extends ChatEditingEditorAction { } } +class AddHunkToChatAction extends ChatEditingEditorAction { + constructor() { + super({ + id: 'chatEditor.action.addHunkToChat', + title: localize2('addHunkToChat', 'Add to Chat'), + icon: Codicon.attach, + precondition: ContextKeyExpr.and(ChatContextKeys.enabled, ctxHasEditorModification, ctxIsCurrentlyBeingModified.negate()), + menu: { + id: MenuId.ChatEditingEditorHunk, + order: 20 + } + }); + } + + override async runChatEditingCommand(accessor: ServicesAccessor, _session: IChatEditingSession, entry: IModifiedFileEntry, _integration: IModifiedFileEntryEditorIntegration, ...args: unknown[]): Promise { + const chatWidgetService = accessor.get(IChatWidgetService); + + const hunk = args[0] as IModifiedFileEntryChangeHunk | undefined; + const lineRange = hunk?.getModifiedLineRange(); + if (!lineRange) { + return; + } + + const widget = chatWidgetService.lastFocusedWidget ?? await chatWidgetService.revealWidget(); + if (!widget) { + return; + } + + const range = new Range(lineRange.startLineNumber, 1, lineRange.endLineNumberExclusive, 1); + widget.attachmentModel.addFile(entry.modifiedURI, range); + widget.focusInput(); + } +} + class ToggleAccessibleDiffViewAction extends ChatEditingEditorAction { constructor() { super({ @@ -447,6 +483,7 @@ export function registerChatEditorActions() { registerAction2(AcceptHunkAction); registerAction2(RejectHunkAction); registerAction2(ToggleDiffAction); + registerAction2(AddHunkToChatAction); registerAction2(ToggleAccessibleDiffViewAction); registerAction2(class extends MultiDiffAcceptDiscardAction { constructor() { super(true); } }); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts index 2eaea73e41585..33edc5980ff23 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/chatEditingNotebookEditorIntegration.ts @@ -371,6 +371,9 @@ class ChatEditingNotebookEditorWidgetIntegration extends Disposable implements I accessibilitySignalService.playSignal(AccessibilitySignal.editsUndone, { allowManyInParallel: true }); return Promise.resolve(true); }, + getModifiedLineRange() { + return undefined; // Not applicable for notebook cell deletions + }, } satisfies IModifiedFileEntryChangeHunk; } })); diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts index a684d819a8842..4fb6a72d5a01d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/notebook/overlayToolbarDecorator.ts @@ -118,6 +118,9 @@ export class OverlayToolbarDecorator extends Disposable { await change.undo(singleChange); } return true; + }, + getModifiedLineRange() { + return undefined; // Not applicable for notebook cell overlays } } satisfies IModifiedFileEntryChangeHunk, }, diff --git a/src/vs/workbench/contrib/chat/common/editing/chatEditingService.ts b/src/vs/workbench/contrib/chat/common/editing/chatEditingService.ts index c5db2d082329d..b667a41641248 100644 --- a/src/vs/workbench/contrib/chat/common/editing/chatEditingService.ts +++ b/src/vs/workbench/contrib/chat/common/editing/chatEditingService.ts @@ -316,6 +316,10 @@ export const enum ModifiedFileEntryState { export interface IModifiedFileEntryChangeHunk { accept(): Promise; reject(): Promise; + /** + * Get the modified line range of this hunk + */ + getModifiedLineRange(): { startLineNumber: number; endLineNumberExclusive: number } | undefined; } export interface IModifiedFileEntryEditorIntegration extends IDisposable {