Skip to content

Commit e7a0cb4

Browse files
authored
Merge branch 'main' into tyriar/term-image
2 parents c65f503 + 9084e08 commit e7a0cb4

File tree

4 files changed

+150
-31
lines changed

4 files changed

+150
-31
lines changed

src/vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionCodeblockActions.ts

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
2020
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
2121
import { IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor';
2222
import { INTERACTIVE_SESSION_CATEGORY } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionActions';
23-
import { codeBlockInfoByModelUri } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer';
23+
import { IInteractiveSessionWidgetService } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSession';
24+
import { CONTEXT_IN_INTERACTIVE_SESSION } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys';
2425
import { IInteractiveSessionCopyAction, IInteractiveSessionService, IInteractiveSessionUserActionEvent, InteractiveSessionCopyKind } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
25-
import { IInteractiveResponseViewModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
26+
import { IInteractiveResponseViewModel, isResponseVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
2627
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
2728
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
2829
import { CellKind, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -97,7 +98,7 @@ export function registerInteractiveSessionCodeBlockActions() {
9798
return false;
9899
}
99100

100-
const context = getContextFromEditor(editor);
101+
const context = getContextFromEditor(editor, accessor);
101102
if (!context) {
102103
return false;
103104
}
@@ -155,7 +156,7 @@ export function registerInteractiveSessionCodeBlockActions() {
155156
override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
156157
let context = args[0];
157158
if (!isCodeBlockActionContext(context)) {
158-
context = getContextFromEditor(editor);
159+
context = getContextFromEditor(editor, accessor);
159160
if (!isCodeBlockActionContext(context)) {
160161
return;
161162
}
@@ -264,7 +265,7 @@ export function registerInteractiveSessionCodeBlockActions() {
264265
override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
265266
let context = args[0];
266267
if (!isCodeBlockActionContext(context)) {
267-
context = getContextFromEditor(editor);
268+
context = getContextFromEditor(editor, accessor);
268269
if (!isCodeBlockActionContext(context)) {
269270
return;
270271
}
@@ -316,7 +317,7 @@ export function registerInteractiveSessionCodeBlockActions() {
316317
override async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
317318
let context = args[0];
318319
if (!isCodeBlockActionContext(context)) {
319-
context = getContextFromEditor(editor);
320+
context = getContextFromEditor(editor, accessor);
320321
if (!isCodeBlockActionContext(context)) {
321322
return;
322323
}
@@ -357,15 +358,94 @@ export function registerInteractiveSessionCodeBlockActions() {
357358
});
358359
}
359360
});
361+
362+
function navigateCodeBlocks(accessor: ServicesAccessor, reverse?: boolean): void {
363+
const codeEditorService = accessor.get(ICodeEditorService);
364+
const interactiveSessionWidgetService = accessor.get(IInteractiveSessionWidgetService);
365+
const widget = interactiveSessionWidgetService.lastFocusedWidget;
366+
if (!widget) {
367+
return;
368+
}
369+
370+
const editor = codeEditorService.getFocusedCodeEditor();
371+
const editorUri = editor?.getModel()?.uri;
372+
const curCodeBlockInfo = editorUri ? widget.getCodeBlockInfoForEditor(editorUri) : undefined;
373+
374+
const focusResponse = curCodeBlockInfo ?
375+
curCodeBlockInfo.element :
376+
widget.viewModel?.getItems().reverse().find((item): item is IInteractiveResponseViewModel => isResponseVM(item));
377+
if (!focusResponse) {
378+
return;
379+
}
380+
381+
const responseCodeblocks = widget.getCodeBlockInfosForResponse(focusResponse);
382+
const focusIdx = curCodeBlockInfo ?
383+
(curCodeBlockInfo.codeBlockIndex + (reverse ? -1 : 1) + responseCodeblocks.length) % responseCodeblocks.length :
384+
reverse ? responseCodeblocks.length - 1 : 0;
385+
386+
responseCodeblocks[focusIdx]?.focus();
387+
}
388+
389+
registerAction2(class NextCodeBlockAction extends Action2 {
390+
constructor() {
391+
super({
392+
id: 'workbench.action.interactiveSession.nextCodeBlock',
393+
title: {
394+
value: localize('interactive.nextCodeBlock.label', "Next Code Block"),
395+
original: 'Next Code Block'
396+
},
397+
keybinding: {
398+
primary: KeyCode.F9,
399+
weight: KeybindingWeight.WorkbenchContrib,
400+
when: CONTEXT_IN_INTERACTIVE_SESSION,
401+
},
402+
f1: true,
403+
category: INTERACTIVE_SESSION_CATEGORY,
404+
});
405+
}
406+
407+
run(accessor: ServicesAccessor, ...args: any[]) {
408+
navigateCodeBlocks(accessor);
409+
}
410+
});
411+
412+
registerAction2(class PreviousCodeBlockAction extends Action2 {
413+
constructor() {
414+
super({
415+
id: 'workbench.action.interactiveSession.previousCodeBlock',
416+
title: {
417+
value: localize('interactive.previousCodeBlock.label', "Previous Code Block"),
418+
original: 'Previous Code Block'
419+
},
420+
keybinding: {
421+
primary: KeyMod.Shift | KeyCode.F9,
422+
weight: KeybindingWeight.WorkbenchContrib,
423+
when: CONTEXT_IN_INTERACTIVE_SESSION,
424+
},
425+
f1: true,
426+
category: INTERACTIVE_SESSION_CATEGORY,
427+
});
428+
}
429+
430+
run(accessor: ServicesAccessor, ...args: any[]) {
431+
navigateCodeBlocks(accessor, true);
432+
}
433+
});
360434
}
361435

362-
function getContextFromEditor(editor: ICodeEditor): IInteractiveSessionCodeBlockActionContext | undefined {
436+
function getContextFromEditor(editor: ICodeEditor, accessor: ServicesAccessor): IInteractiveSessionCodeBlockActionContext | undefined {
437+
const interactiveSessionWidgetService = accessor.get(IInteractiveSessionWidgetService);
363438
const model = editor.getModel();
364439
if (!model) {
365440
return;
366441
}
367442

368-
const codeBlockInfo = codeBlockInfoByModelUri.get(model.uri);
443+
const widget = interactiveSessionWidgetService.lastFocusedWidget;
444+
if (!widget) {
445+
return;
446+
}
447+
448+
const codeBlockInfo = widget.getCodeBlockInfoForEditor(model.uri);
369449
if (!codeBlockInfo) {
370450
return;
371451
}

src/vs/workbench/contrib/interactiveSession/browser/interactiveSession.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
77
import { IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
8-
import { IInteractiveSessionViewModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
8+
import { IInteractiveResponseViewModel, IInteractiveSessionViewModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
99
import { Event } from 'vs/base/common/event';
1010
import { URI } from 'vs/base/common/uri';
1111
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -29,6 +29,12 @@ export interface IInteractiveSessionWidgetService {
2929
getWidgetByInputUri(uri: URI): IInteractiveSessionWidget | undefined;
3030
}
3131

32+
export interface IInteractiveSessionCodeBlockInfo {
33+
codeBlockIndex: number;
34+
element: IInteractiveResponseViewModel;
35+
focus(): void;
36+
}
37+
3238
export type IInteractiveSessionWidgetViewContext = { viewId: string } | { resource: boolean };
3339

3440
export interface IInteractiveSessionWidget {
@@ -42,6 +48,8 @@ export interface IInteractiveSessionWidget {
4248
focusLastMessage(): void;
4349
focusInput(): void;
4450
getSlashCommands(): Promise<IInteractiveSlashCommand[] | undefined>;
51+
getCodeBlockInfoForEditor(uri: URI): IInteractiveSessionCodeBlockInfo | undefined;
52+
getCodeBlockInfosForResponse(response: IInteractiveResponseViewModel): IInteractiveSessionCodeBlockInfo[];
4553
}
4654

4755
export interface IInteractiveSessionViewPane {

src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer.ts

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ import { Codicon } from 'vs/base/common/codicons';
1616
import { Emitter, Event } from 'vs/base/common/event';
1717
import { FuzzyScore } from 'vs/base/common/filters';
1818
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
19-
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
19+
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
2020
import { ResourceMap } from 'vs/base/common/map';
2121
import { FileAccess } from 'vs/base/common/network';
2222
import { ThemeIcon } from 'vs/base/common/themables';
2323
import { withNullAsUndefined } from 'vs/base/common/types';
24+
import { URI } from 'vs/base/common/uri';
2425
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
2526
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
2627
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
@@ -49,6 +50,8 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
4950
import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer';
5051
import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard';
5152
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
53+
import { IInteractiveSessionCodeBlockActionContext } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionCodeblockActions';
54+
import { IInteractiveSessionCodeBlockInfo } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSession';
5255
import { InteractiveSessionFollowups } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionFollowups';
5356
import { InteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionOptions';
5457
import { CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContextKeys';
@@ -87,6 +90,9 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
8790
static readonly cursorCharacter = '\u258c';
8891
static readonly ID = 'item';
8992

93+
private readonly codeBlocksByResponseId = new Map<string, IInteractiveSessionCodeBlockInfo[]>();
94+
private readonly codeBlocksByEditorUri = new ResourceMap<IInteractiveSessionCodeBlockInfo>();
95+
9096
private readonly renderer: MarkdownRenderer;
9197

9298
protected readonly _onDidClickFollowup = this._register(new Emitter<IInteractiveSessionReplyFollowup>());
@@ -152,6 +158,15 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
152158
return 8;
153159
}
154160

161+
getCodeBlockInfosForResponse(response: IInteractiveResponseViewModel): IInteractiveSessionCodeBlockInfo[] {
162+
const codeBlocks = this.codeBlocksByResponseId.get(response.id);
163+
return codeBlocks ?? [];
164+
}
165+
166+
getCodeBlockInfoForEditor(uri: URI): IInteractiveSessionCodeBlockInfo | undefined {
167+
return this.codeBlocksByEditorUri.get(uri);
168+
}
169+
155170
setVisible(visible: boolean): void {
156171
this._isVisible = visible;
157172
}
@@ -385,10 +400,13 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
385400
const usedSlashCommand = slashCommands.find(s => markdown.value.startsWith(`/${s.command} `));
386401
const toRender = usedSlashCommand ? markdown.value.slice(usedSlashCommand.command.length + 2) : markdown.value;
387402
markdown = new MarkdownString(toRender);
403+
404+
const codeblocks: IInteractiveSessionCodeBlockInfo[] = [];
388405
const result = this.renderer.render(markdown, {
389406
fillInIncompleteTokens,
390407
codeBlockRendererSync: (languageId, text) => {
391-
const ref = this.renderCodeBlock({ languageId, text, codeBlockIndex: codeBlockIndex++, element, parentContextKeyService: templateData.contextKeyService }, disposables);
408+
const data = { languageId, text, codeBlockIndex: codeBlockIndex++, element, parentContextKeyService: templateData.contextKeyService };
409+
const ref = this.renderCodeBlock(data, disposables);
392410

393411
// Attach this after updating text/layout of the editor, so it should only be fired when the size updates later (horizontal scrollbar, wrapping)
394412
// not during a renderElement OR a progressive render (when we will be firing this event anyway at the end of the render)
@@ -397,11 +415,28 @@ export class InteractiveListItemRenderer extends Disposable implements ITreeRend
397415
this._onDidChangeItemHeight.fire({ element, height: templateData.rowContainer.offsetHeight });
398416
}));
399417

418+
if (isResponseVM(element)) {
419+
const info = {
420+
codeBlockIndex: data.codeBlockIndex,
421+
element,
422+
focus() {
423+
ref.object.focus();
424+
}
425+
};
426+
codeblocks.push(info);
427+
this.codeBlocksByEditorUri.set(ref.object.textModel.uri, info);
428+
disposables.add(toDisposable(() => this.codeBlocksByEditorUri.delete(ref.object.textModel.uri)));
429+
}
400430
disposablesList.push(ref);
401431
return ref.object.element;
402432
}
403433
});
404434

435+
if (isResponseVM(element)) {
436+
this.codeBlocksByResponseId.set(element.id, codeblocks);
437+
disposables.add(toDisposable(() => this.codeBlocksByResponseId.delete(element.id)));
438+
}
439+
405440
if (usedSlashCommand) {
406441
const slashCommandElement = $('span.interactive-slash-command', { title: usedSlashCommand.detail }, `/${usedSlashCommand.command} `);
407442
if (result.element.firstChild?.nodeName.toLowerCase() === 'p') {
@@ -522,17 +557,10 @@ interface IInteractiveResultCodeBlockPart {
522557
readonly textModel: ITextModel;
523558
layout(width: number): void;
524559
render(data: IInteractiveResultCodeBlockData, width: number): void;
560+
focus(): void;
525561
dispose(): void;
526562
}
527563

528-
export interface IInteractiveSessionCodeBlockInfo {
529-
codeBlockIndex: number;
530-
element: IInteractiveResponseViewModel;
531-
}
532-
533-
// Enable actions to look this up by editor URI. An alternative would be writing lots of details to element attributes.
534-
export const codeBlockInfoByModelUri = new ResourceMap<IInteractiveSessionCodeBlockInfo>();
535-
536564
const defaultCodeblockPadding = 10;
537565

538566
class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPart {
@@ -620,6 +648,10 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
620648
this.editor.setModel(this.textModel);
621649
}
622650

651+
focus(): void {
652+
this.editor.focus();
653+
}
654+
623655
private updatePaddingForLayout() {
624656
// scrollWidth = "the width of the content that needs to be scrolled"
625657
// contentWidth = "the width of the area where content is displayed"
@@ -669,16 +701,7 @@ class CodeBlockPart extends Disposable implements IInteractiveResultCodeBlockPar
669701

670702
this.layout(width);
671703

672-
if (isResponseVM(data.element) && data.element.providerResponseId) {
673-
codeBlockInfoByModelUri.set(this.textModel.uri, {
674-
element: data.element,
675-
codeBlockIndex: data.codeBlockIndex,
676-
});
677-
} else {
678-
codeBlockInfoByModelUri.delete(this.textModel.uri);
679-
}
680-
681-
this.toolbar.context = <IInteractiveSessionCodeBlockInfo>{
704+
this.toolbar.context = <IInteractiveSessionCodeBlockActionContext>{
682705
code: data.text,
683706
codeBlockIndex: data.codeBlockIndex,
684707
element: data.element,

src/vs/workbench/contrib/interactiveSession/browser/interactiveSessionWidget.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
2222
import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
2323
import { IViewsService } from 'vs/workbench/common/views';
2424
import { clearChatSession } from 'vs/workbench/contrib/interactiveSession/browser/actions/interactiveSessionClear';
25-
import { IInteractiveSessionWidget, IInteractiveSessionWidgetService, IInteractiveSessionWidgetViewContext } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSession';
25+
import { IInteractiveSessionCodeBlockInfo, IInteractiveSessionWidget, IInteractiveSessionWidgetService, IInteractiveSessionWidgetViewContext } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSession';
2626
import { InteractiveSessionInputPart } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionInputPart';
2727
import { IInteractiveSessionRendererDelegate, InteractiveListItemRenderer, InteractiveSessionAccessibilityProvider, InteractiveSessionListDelegate, InteractiveTreeItem } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionListRenderer';
2828
import { InteractiveSessionEditorOptions } from 'vs/workbench/contrib/interactiveSession/browser/interactiveSessionOptions';
@@ -31,7 +31,7 @@ import { CONTEXT_INTERACTIVE_REQUEST_IN_PROGRESS, CONTEXT_IN_INTERACTIVE_SESSION
3131
import { IInteractiveSessionContributionService } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionContributionService';
3232
import { IInteractiveSessionModel } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionModel';
3333
import { IInteractiveSessionReplyFollowup, IInteractiveSessionService, IInteractiveSlashCommand } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionService';
34-
import { InteractiveSessionViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
34+
import { IInteractiveResponseViewModel, InteractiveSessionViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/interactiveSession/common/interactiveSessionViewModel';
3535

3636
const $ = dom.$;
3737

@@ -386,6 +386,14 @@ export class InteractiveSessionWidget extends Disposable implements IInteractive
386386
}
387387
}
388388

389+
getCodeBlockInfosForResponse(response: IInteractiveResponseViewModel): IInteractiveSessionCodeBlockInfo[] {
390+
return this.renderer.getCodeBlockInfosForResponse(response);
391+
}
392+
393+
getCodeBlockInfoForEditor(uri: URI): IInteractiveSessionCodeBlockInfo | undefined {
394+
return this.renderer.getCodeBlockInfoForEditor(uri);
395+
}
396+
389397
focusLastMessage(): void {
390398
if (!this.viewModel) {
391399
return;

0 commit comments

Comments
 (0)