diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 0a2ed0585ccaa..1463d518357d3 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -59,7 +59,7 @@ export function isCodeBlockActionContext(thing: unknown): thing is ICodeBlockAct } export function isCodeCompareBlockActionContext(thing: unknown): thing is ICodeCompareBlockActionContext { - return typeof thing === 'object' && thing !== null && 'element' in thing; + return typeof thing === 'object' && thing !== null && 'element' in thing && 'diffEditor' in thing && 'toggleDiffViewMode' in thing; } function isResponseFiltered(context: ICodeBlockActionContext) { @@ -635,11 +635,12 @@ export function registerChatCodeCompareBlockActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.gitPullRequestGoToChanges, - precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()), + precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate(), EditorContextKeys.readOnly.negate()), menu: { id: MenuId.ChatCompareBlock, group: 'navigation', - order: 1, + order: 10, + when: EditorContextKeys.readOnly.negate(), } }); } @@ -688,11 +689,12 @@ export function registerChatCodeCompareBlockActions() { f1: false, category: CHAT_CATEGORY, icon: Codicon.trash, - precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate()), + precondition: ContextKeyExpr.and(EditorContextKeys.hasChanges, ChatContextKeys.editApplied.negate(), EditorContextKeys.readOnly.negate()), menu: { id: MenuId.ChatCompareBlock, group: 'navigation', - order: 2, + order: 11, + when: EditorContextKeys.readOnly.negate(), } }); } @@ -704,4 +706,59 @@ export function registerChatCodeCompareBlockActions() { editor.discard(context.element, context.edit); } }); + + registerAction2(class ToggleDiffViewModeAction extends ChatCompareCodeBlockAction { + constructor() { + super({ + id: 'workbench.action.chat.toggleCompareBlockDiffViewMode', + title: localize2('interactive.compare.toggleDiffViewMode', "Toggle Inline/Side-by-Side Diff"), + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.diffSingle, + toggled: { + condition: EditorContextKeys.diffEditorInlineMode, + icon: Codicon.diff, + }, + menu: { + id: MenuId.ChatCompareBlock, + group: 'navigation', + order: 1, + } + }); + } + + runWithContext(_accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): void { + context.toggleDiffViewMode(); + } + }); + + registerAction2(class OpenCompareBlockInDiffEditor extends ChatCompareCodeBlockAction { + constructor() { + super({ + id: 'workbench.action.chat.openCompareBlockInDiffEditor', + title: localize2('interactive.compare.openInDiffEditor', "Open in Diff Editor"), + f1: false, + category: CHAT_CATEGORY, + icon: Codicon.goToFile, + menu: { + id: MenuId.ChatCompareBlock, + group: 'navigation', + order: 2, + } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: ICodeCompareBlockActionContext): Promise { + const editorService = accessor.get(IEditorService); + const model = context.diffEditor.getModel(); + if (!model) { + return; + } + + await editorService.openEditor({ + original: { resource: model.original.uri }, + modified: { resource: model.modified.uri }, + }); + } + }); } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/codeBlockPart.ts index a1bc014c7c0d4..bcaa53fee11d0 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/codeBlockPart.ts @@ -72,6 +72,7 @@ import { ChatEditorOptions } from '../chatOptions.js'; import { emptyProgressRunner, IEditorProgressService } from '../../../../../../platform/progress/common/progress.js'; import { SuggestController } from '../../../../../../editor/contrib/suggest/browser/suggestController.js'; import { SnippetController2 } from '../../../../../../editor/contrib/snippet/browser/snippetController2.js'; +import { EditorContextKeys } from '../../../../../../editor/common/editorContextKeys.js'; const $ = dom.$; @@ -548,6 +549,7 @@ export interface ICodeCompareBlockActionContext { readonly element: IChatResponseViewModel; readonly diffEditor: IDiffEditor; readonly edit: IChatTextEditGroup; + toggleDiffViewMode(): void; } export interface ICodeCompareBlockDiffData { @@ -585,6 +587,8 @@ export class CodeCompareBlockPart extends Disposable { private currentScrollWidth = 0; private currentHorizontalPadding = 0; + private lastLayoutWidth: number | undefined; + constructor( private readonly options: ChatEditorOptions, readonly menuId: MenuId, @@ -755,11 +759,10 @@ export class CodeCompareBlockPart extends Disposable { private _configureForScreenReader(): void { const toolbarElt = this.toolbar.getElement(); + // Always show toolbar, but add aria-label for screen readers + toolbarElt.style.display = 'block'; if (this.accessibilityService.isScreenReaderOptimized()) { - toolbarElt.style.display = 'block'; toolbarElt.ariaLabel = localize('chat.codeBlock.toolbar', 'Code block toolbar'); - } else { - toolbarElt.style.display = ''; } } @@ -777,7 +780,13 @@ export class CodeCompareBlockPart extends Disposable { }; } - layout(width: number): void { + layout(width = this.lastLayoutWidth): void { + if (width === undefined) { + return; // not yet in DOM + } + + this.lastLayoutWidth = width; + const editorBorder = 2; const toolbar = dom.getTotalHeight(this.editorHeader); @@ -900,6 +909,18 @@ export class CodeCompareBlockPart extends Disposable { edit: data.edit, element: data.element, diffEditor: this.diffEditor, + toggleDiffViewMode: () => { + const isCurrentlyInline = !!this.diffEditor.getModifiedEditor().contextKeyService.getContextKeyValue(EditorContextKeys.diffEditorInlineMode.key); + const renderSideBySide = isCurrentlyInline; + this.diffEditor.updateOptions({ + renderSideBySide, + // Make it not-compact in side by side mode, otherwise we may not actually + // show it side-by-side if it's a simple diff https://github.com/microsoft/vscode/blob/0632563332c7c08656fb47c97bc4328d62ee1d80/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts#L35-L39 + compactMode: !renderSideBySide, + useInlineViewWhenSpaceIsLimited: false, + }); + this.layout(); + }, } satisfies ICodeCompareBlockActionContext; } } diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/codeBlockPart.css b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/codeBlockPart.css index 00d5fc2d1bc11..9a70eec7939b9 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/codeBlockPart.css +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/media/codeBlockPart.css @@ -156,16 +156,50 @@ color: var(--vscode-textLink-foreground); } -.interactive-result-code-block.compare .interactive-result-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 3px; - box-sizing: border-box; - border-bottom: solid 1px var(--vscode-chat-requestBorder); -} - -.interactive-result-code-block.compare.no-diff .interactive-result-header, -.interactive-result-code-block.compare.no-diff .interactive-result-editor { - display: none; +.interactive-result-code-block.compare { + & .interactive-result-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 3px; + box-sizing: border-box; + border-bottom: solid 1px var(--vscode-chat-requestBorder); + + & .monaco-icon-label { + flex: 1; + min-width: 0; + + & > .monaco-icon-label-container { + display: flex; + align-items: baseline; + + & > .monaco-icon-name-container { + flex-shrink: 0; + + & > .label-name { + white-space: nowrap; + } + } + + & > .monaco-icon-description-container { + min-width: 0; + flex: 1; + + & > .label-description { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + } + } + } + } + } + + &.no-diff { + & .interactive-result-header, + & .interactive-result-editor { + display: none; + } + } }